Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
144 views
in Technique[技术] by (71.8m points)

ruby on rails - How to chain search params and use them as objects?

In my project I have a department model. I want to add employees to the department by using a search. I want to add the result of the search to a list, then submit the list and add all searched employees in one go at the end, all in the same view.

Search function in departments_controller

  def add_employees

    employees = Employee.all
    @searched_employee = Employee.where('name LIKE ?', "#{params[:search_by_name]}")
    
    @searched_employee.each do |employee|
        
        @searched_employee_name = employee.name
    end

   end

add_employees-view:

h1 Add employees
= form_for @department, :url => add_employees_path(:param1 =>    @searched_employee_name, :param2 => request.query_parameters), method: :post do

= label_tag :search_by_name
br
= search_field_tag :search_by_name, params[:name]

= submit_tag "Search"

= form_for @department, :url => add_employee_path, html: {method: "post"} do |f|

    - if params[:search_by_name].present?
    - @searched_employee.each do |employee|
            li = employee.name
    br
                                                        
    table
        h5 Employees
        thead
            tr Name
            tr Email
        tbody
            - @searched_employee.each do |employee|
                tr
                    td = employee.name

            td = request.query_parameters

Single search works fine, so I hoped to add a second param which stores the first request to be passed on for the next search and so forth. Now I am stuck with splitting up the long query string into its unique search results and their objects, as to add them to a list where I can then work further with them (checkboxes etc). Request.query_parameters is nested, but does not react to dig, because it says it is a string. Any ideas on how to approach this or maybe a better solution, without the use of additional gems?

question from:https://stackoverflow.com/questions/65923181/how-to-chain-search-params-and-use-them-as-objects

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here is how I would solve it if I had to do it without JS.

Create an M-2-M association with a join model between deparments and employees. Let departments accept nested attributes for the join model:

class Department < ApplicationRecord
  has_many :positions
  has_many :employees, through: :positions
  accepts_nested_attributes_for :positions, reject_if: :reject_position?
  
  private

  def reject_position?(attributes)
    !ActiveModel::Type::Boolean.new.cast(attributes['_keep'])
  end
end
class Employee < ApplicationRecord
  has_many :departments
  has_many :positions, through: :departments
end
# rails g model position employee:belongs_to department:belongs_to
class Position < ApplicationRecord
  belongs_to :employee
  belongs_to :department
  attribute :_keep, :boolean
end

Setup the routes:

# config/routes.rb
Rails.application.routes.draw do
 
  # ...
  # @todo merge this with your existing routes
  resources :departments, only: [] do
    resources :employees, only: [], module: :departments do
      collection do
        get :search
        patch '/',
          action: :update_collection,
          as: :update
      end
    end
  end
end

Now lets create the search form:

# /app/views/departments/employees/search.rb
<%= form_with(
    url: search_department_employees_path(@department),
    local: true,
    method: :get
  ) do |form| %>
  <div class="field">
    <%= form.label :search_by_name %>
    <%= form.text_field :search_by_name %>
  </div>
  <% @results&.each do |employee| %>
    <%= hidden_field_tag('stored_employee_ids[]', employee.id) %>
  <% end %>
  <%= form.submit("Search") %>
<% end %>

Note that we are using GET instead of POST. Since this action is idempotent (it does not actually alter anything) you can use GET.

Note <%= hidden_field_tag('stored_employee_ids[]', employee.id) %>. Rack will merge any pairs where the key ends with [] into an array.

Now lets setup the controller:

module  Departments
  # Controller that handles employees on a per department level
  class EmployeesController < ApplicationController
    before_action :set_department

    # Search employees by name
    # This route is nested in the department since we want to exclude employees
    # that belong to the department
    # GET /departments/1/employees?search_by_name=john
    def search
      @search_term = params[:search_by_name]
      @stored_ids = params[:stored_employee_ids]
      if @search_term.present?
        @results = not_employed.where('employees.name LIKE ?', @search_term)
      end
      # merge the search results with the "stored" employee ids we are passing along
      if @stored_ids.present?
        @results = not_employed.or(Employee.where(id: @stored_ids))
      end
      @positions = (@results||[]).map do |employee|
        Position.new(employee: employee)
      end
    end

    private

    def not_employed
      @results ||= Employee.where.not(id: @department.employees)
    end

    def set_department
      @department = Department.find(params[:department_id])
    end
  end
end

This just creates a form that "loops back on itself" and just keeps adding more ids to the query string - without all that hackery.

Now lets create a second form where we actually do something with the search results as a partial:

# app/views/departments/employees/_update_collection_form.html.erb
<%= form_with(
  model: @department,
  url: update_department_employees_path(@department),
  local: true,
  method: :patch
) do |form |%>
  <legend>Add to <%= form.object.name %></legend>
  <table>
    <thead>
      <tr>
        <td>Add?</td>
        <td>Name</td>
      <tr>
    </thead>
    <tbody>
      <%= form.fields_for(:positions, @positions) do |p_fields| %>
      <tr>
        <td>
          <%= p_fields.label :_keep, class: 'aria-hidden' %>
          <%= p_fields.check_box :_keep %>
        </td>
        <td>
          <%= p_fields.object.employee.name %>
          <%= p_fields.hidden_field :employee_id %>
        </td>
      </tr>
      <% end %>
    </tbody>
  </table>
  <%= form.submit 'Add employees to department' %>
<% end %>

form.fields_for(:positions, @positions) loops through the array and creates inputs for each position.

And render the partial in app/views/departments/employees/search.html.erb:

# ...

<%= render partial: 'update_collection_form' if @positions.any? %>

You should not nest this form inside another form. That will result in invalid HTML and will not work properly.

Unlike your solution I'm not cramming everything and the bathtub into a single endoint. This form sends a PATCH request to /departments/1/employees. Using PATCH on an entire collection like this is somewhat rare as we usually just use it for individual members. But here we really are adding a bunch of stuff to the collection itself.

Now lets add the action to the controller:

module  Departments
  # Controller that handles employees on a per department level
  class EmployeesController < ApplicationController
    
    # ...
    
    # Adds a bunch of employees to a department
    # PATCH /departments/:department_id/employees 
    def update_collection
      if @department.update(nested_attributes)
        redirect_to action: :search, 
                    flash: 'Employees added'
      else
        @postions = @department.positions.select(&:new_record?)
        render :search, 
                   flash: 'Some employees could not be added'
      end
    end

    private

    # ...

    def update_collection_attributes
      params.require(:department)
            .permit(
                positions_attributes: [
                  :keep,
                  :employee_id
                ]
            )
    end
  end
end

There is almost nothing to it since accepts_nested_attributes is doing all the work on the controller layer.

I'll leave it up to you to convert this ERB to Slim or Haml and adapt it to your existing code base.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...