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

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?

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.

So, if anyone faces a similar problem, what I cam up with is this:
- Hash[CGI::parse(request.query_string).map{|k,v| [k,v]}].values.each do |value|
- valuename = value[0]
- employees = Employee.all
- found_employees = employees.where('name LIKE ?', "#{valuename}")
- found_employees.each do |employee|
tr
td = employee.name
First line: gets the whole query string, parses over it to look for the parameters and puts them in a hash, then reads out the key value pairs, which then are arrays. Those arrays' values are then iterated over.
Second line: since the values are still in an array, I ask the arrays to just put out the pure values with value[0], and assign them to valuename.
Folowing lines: Just querying the database to find all employee-names that match a valuename, receive objects so I can further work on them.

Related

Rails - Dynamic query all records from has_many association with link_to

I have a model Building that has many residents. My index page list all buildings with a link to list all residents from that building. I'm learning ROR again, and I'm struggling to create this view.
Here is what I got so far.
../models/building.rb
class Building < ApplicationRecord
has_many :residents
end
../models/resident.rb
class Resident < ApplicationRecord
belongs_to :building
end
..controllers/buildings_controller.rb
Here is where I start to get lost. In my show action I don't know how to pass the params to generate a view with all residents from a building using link_to. I'm not sure how I can do it.
Or how to do a query like r = Resident.all.where(:building_id => 1), but dynamically.
class BuildingsController < ApplicationController
def index
#building = Building.all
end
def show
#building = Building.find(params[:id])
#buildings = Building.all
end
..views/buildings/index.rb
<% #building.each do |b| %>
<div>
<%= link_to b.name, '/buildings/show/'b.id %>
</div>
<% end %>
I'm open to different approaches...
Thank you community!
You should take a look at the named routes feature in Ruby on Rails: https://guides.rubyonrails.org/routing.html#path-and-url-helpers
Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of resources :photos:
photos_path returns /photos
new_photo_path returns /photos/new
edit_photo_path(:id) returns /photos/:id/edit (for instance, edit_photo_path(10) returns /photos/10/edit)
photo_path(:id) returns /photos/:id (for instance, photo_path(10) returns /photos/10)
Each of these helpers has a corresponding _url helper (such as photos_url) which returns the same path prefixed with the current host, port, and path prefix.
So to be able to reach your controller, you have to add some routes to your routes.rb file and use it in your view:
# routes.rb
resources :buildings
resources :residents
# index view
<% #building.each do |b| %>
<div>
<%= link_to b.name, building_path(b) %>
</div>
<% end %>
# show view to show all residents, belonging to a building
<% #building.residents.each do |resident| %>
<div>
<%= link_to resident.name, resident_path(resident) %>
</div>
<% end %>

How to code controller to use both search and filter in rails app

I'm building a rails app, and I'm having some trouble getting the search function and filter option to work in unison.
#app/models/raffle.rb
class Raffle < ApplicationRecord
has_many :tickets
has_many :users, through: :tickets
def self.filter(filter)
if filter
raffle = Raffle.where(category: filter)
else
Raffle.all
end
end
def self.search(key)
if key.nil?
raffle = Raffle.where(product_name: search)
else
keys = key.split('+')
Raffle.where((['product_name LIKE ?'] * keys.size).join(' OR '), *keys.map{ |key| "%#{key}%" })
end
end
end
#app/controllers/raffles_controller.rb
def index
#raffles = Raffle.filter(params[:filter]) #something for search functionality here
end
#app/views/raffles/index.html.erb
<%= form_tag(raffles_path, method: :get) do %>
<%= text_field :search, params[:search]%>
<%= submit_tag ("Search") %>
<% end %>
<%= form_tag(raffles_path, method: 'get') do %>
<%= select_tag(:filter, options_for_select(Raffle.pluck(:category).uniq, params[:filter])) %>
<%= submit_tag ("Filter") %>
<% end %>
I'm certain the issue lies in the controller, since I've tried #raffles = Raffle.filter(params[:filter]).search(params[:search]), but I get an error 'wrong number of arguments (given 0, expected 1)' being triggered by the line def self.search(key)
Any help greatly appreciated!
Here's an excerpt from your code.
def self.search(key)
if key.nil?
raffle = Raffle.where(product_name: search)
Once execution reaches the end of it, it calls product_name: search. search is the name of the method you've defined that requires one argument and you're passing none. I assume you wanted to call something else here.
If you would have looked at the stack trace you would have gotten it yourself.

Create Parent with existing children

Does anyone know how to create a new parent with existing children?
class Order < ActiveRecord::Base
has_many :jobs
accepts_nested_attributes_for :jobs
end
class Job < ActiveRecord::Base
belongs_to :quote
belongs_to :order
end
With nested variables, I can build one or more jobs (children) with a new order (parent) and the create method then automatically creates the new parent with it's new children. What I have though, is a group of existing children (jobs that were created in a quote), and I want to include them in a new parent form. When hitting submit to create the new order, I want it to create the new parent, and update the selected children with that new parent ID.
I have messy code that will do this in the create method by saving a new order (parent), then updating each of the jobs (children) with the new order_id, but is there any way to do this automatically?
The actual code is complex, so here's the crux of it.
Let's assume there is a quote with several jobs, therefore the quote_id variable in all these jobs point back to the quote and #quote.jobs = array of these jobs.
Here's what I have now:
In the Order model I added:
attr_accessor :quote_id
In the Order controller:
def new quote_id
#quote = Quote.find(quote_id)
#order = Order.new
and in the view
<%= simple_form_for #order, :html => { :class => 'form-horizontal' } do |f| %>
<%= f.input :quote_id, value: #quote.id, as: :hidden %>
<%= f.input :account_number %>
...
<table>
<thead><tr> ... column headings here ... </tr></thead>
<tbody>
<% #quote.jobs.each do |q| %>
<tr>
<td><%= q.description %></td>
<td><%= q.price %></td>
</tr>
<% end %>
<tr><%= ... add a row with the total price ... %></tr>
</tbody>
</table>
<%= f.button :submit, "Place My Order" %>
<% end $>
Then in the Order controller
def create
...
if #order.save
Quote.find(params[quote_id]).jobs.each do |job|
job.update(order_id: #order.id)
end
end
...
What I would prefer is for the job parameters to be handled as nested objects in the form so they can be automatically updated on an Order create with the order.id and perhaps a couple of other parameters. I've tried (in the controller)
#order = Order.new
#quote.jobs.each do |job|
#order.jobs << job
end
but they're not showing up in a form with the usual nested format and if that worked, I don't know if they would actually be updated.
Since an Order accepts nested attributes for a Job, I suppose you could just manually create and populate the nested jobs fields - as hidden fields - when you build your order form.
<% #quote.jobs.each_with_index do |job, index| %>
<input type='hidden' name='order[jobs_attributes][<% index %>][FIELDNAME]' id='order_jobs_attributes_<% index %>_FIELDNAME' value='<% job.something %>' />
<% end %>
I'm sure there's a cleaner way with simple_form/form helpers to use = f.input to build the hidden fields but I was admittedly being lazy above!

Rails Form element to convey two values

I have a form that takes bookings for an event for people. The form displays events vertically, and a name & checkbox for each of the possible people next to each event.
How should I best convey the two pieces of information that i need per checkbox? that is, the event_id and the person_id
I'm not totally sure wether I got you right. This is the model I assume you're talking about:
# event.rb
class Event
has_many :people
scope :possible_people, -> { #whatever .. }
end
# person.rb
class Person
belongs_to :event
end
# events_controller.rb
class EventsController
def index
#events = Event.all
end
end
And this might be a possible solution to change an events relation to people:
# index.html.erb
<ul id="events">
<% #events.each do |event| %>
<li class="event">
<%= form_for #event do |form| %>
<% event.possible_people.each do |person| %>
<%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %>
<% end %>
<%= f.submit_tag 'Save Event' %>
<% end %>
</li>
<% end %>
</ul>
The important part is <%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %> where you actually change the the relation of a specific person to the event.
Good luck ;)
Well, you can try out something like below line, I am assuming you have a multiselect checkboxes and i am passing a Hash of event_id => plate_id as value to checkbox.
<%= check_box_tag 'booking[event_people_ids][]', {booking.event_id => booking.plate_id} %>
You will get the value in params as:
booking => {event_people_ids =>["{"72"=>"3"}}
I ended up doing this:
<%= check_box_tag "booking[]", "#{event.id}-#{person.id}" %>
and then in then to process them:
params[:booking].each do |booking|
booking = booking.split('-')
a = {
:booking_id => #booking.id,
:person_id => booking[1],
:event_id => booking[0]
}
Appointment.create(a)
end
I was hoping for a more railish way to do it but this works.

How to view RoR controller instance variables in view?

I have a table Projects each with 0 or more Categories. On my view, I want to display 0 projects until a JQuery click event associated with each category--i.e. when the user clicks "Food," I want to display all projects with category Food; when the user clicks "Photos," I want to display BOTH food and photos-related projects.
So on the jQuery click event I define an ajax call:
params = 'category_name=' + cat;
$.ajax({
url: "/projects_controller/filter_list",
data: params
})
where "cat" is the names of the Categories selected (in the format "Food Photography Journal etc")
In my projects_controller I started a filter_list method:
def filter_list
#categories = []
words = params[:category_name].split(/\W+/)
words.each { |word| #categories.push(Category.where("name = ?", word)) }
#projects = ...
end
But now I'm stuck. 1) How do I get all the projects associated with any of the categories in #categories? and 2) How do I display the #projects variable on my view? Right now I just display all like this:
<% Project.all.each do |project| %>
<tr style="display:none" class="project <% project.categories.all.each do |cat| %><%= cat.name %> <% end %>">
<td><%= project.filename %></td>
<td><a href='project/<%= project.id %>'><%= project.location %></a>
<td><% project.categories.all.each do |cat| %><%= cat.name %>, <% end %></td>
<% end %>
Your instance variables $categories, #projects are already available in the view. So in the view you can use #project rather than accessing the Class Project itself.
<% #projects.each do |project| %>
...
<% end %>
But probably you did not design your models correctly. Establish the correct relationships in your model. If a project belongs to a category, you can associate them as follows:
#models/category.rb
class Category < ActiveRecord::Base
has_many :projects
end
#models/project.rb
class Project < ActiveRecord::Base
belongs_to :category
end
#controllers/categories_controller.rb
def index
#categories = Category.all #or use your own query
end
#views/categories/index.erb
<% #categories.each do |category| %>
# here you can get all projects under this category using
# category.projects
<% end %>
Note: i'm used to HAML, so sorry if my ERB syntax is wrong
The according view(s) to a controller class have have access to the class level instance variables (i.e. #my_variable).
Simply let #projects = Project.all.each in your controller and substitute Project.all with #projects in your view.

Resources