Extract search functionality to Form Object in Rails - ruby-on-rails

In my Rails application I have simple search functionality. I want to extract to Form Object but don't know how to do. I have search form which looks like this:
.row
= horizontal_simple_form_for :cars, {url: cars_path, method: :get} do |f|
.col-md-4
.row
.col-md-12
= f.input :handover_location, label: I18n.t('.handover'), collection: Location.all.map{|hl| [hl.location_address, hl.id]}
= f.input :return_location, label: I18n.t('.return') ,collection: Location.all.map{|rl| [rl.location_address, rl.id]}
= f.input :car_class, label: I18n.t('.car_class') ,collection: CarClass.all.map { |c| [c.name, c.id] }, include_blank: true
.col-md-4
= f.input :handover_date, as: :string, label: false
= f.input :return_date, as: :string, label: false
= f.submit class: 'btn btn-success'
Cars controller:
class CarsController < ApplicationController
skip_authorization_check
def index
#cars = Car.search(params)
end
def show
end
end
And class method in Car model which search correct cars:
def self.search(params)
self.joins(:reservations).where.not("reservations.reception_time <= ? AND reservations.return_time >= ?",
params[:cars][:return_date], params[:cars][:handover_date]).
joins(:car_class).where("car_classes.id= ?", params[:cars][:car_class])
.cars_at_both_locations(params[:cars][:handover_location], params[:cars][:return_location])
end
Now I'm trying to extract this to Form Object. I've created a file search_form.rb:
class SearchForm
include ActiveModel::Model
attr_accessor :handover_date, :return_date, :handover_location, :return_location, :car_class
end
But now I don't know how to handle my params to this form object. Thank's in advance.

I wish I could help you with the Form Object stuff, but I need to learn more about classes & modules
I can help you with the search functionality, as we've done it before here
Here's the code we used:
#View
<%= form_tag search_path, :method => :post, :id => "SearchForm" do %>
<%= text_field_tag :search, params[:search], placeholder: 'Search your favourite products or brands', :autocomplete => :off %>
<%= image_submit_tag 'nav_bar/search.png' %>
<% end %>
#config/routes.rb
match 'search(/:search)', :to => 'products#search', :as => :search, via: [:get, :post]
#app/controllers/products_controller.rb
def search
#products = Product.search(params[:search])
respond_to do |format|
format.js { render :partial => "elements/livesearch", :locals => {:search => #products, :query => params[:search]} }
format.html { render :index }
end
end
Notice the form_tag we used?
Simple form does not work with form_tag currently (it requires an object) - we just send the data with a GET request to the controller & that then sends the data to the Product model
I think your problem will be caused by the use of your SearchForm object. You only need this because your use of simple form means you have to pass an object. Problem being this is not necessary for search
A better way will be to use a standard form_tag, and send the request directly to your controller. This will allow you to process the data as params, which you'll be able to send directly to your Car model
--
I can write some code specific to you if you want

I found solution on my own.
Cars controller:
def index
#search_form = SearchForm.new(params[:search_form])
#cars = #search_form.submit(params[:search_form])
end
search_form.rb:
class SearchForm
include ActiveModel::Model
attr_accessor :handover_date, :return_date, :handover_location, :return_location, :car_class
def submit(params)
Car.search(params)
end
end
Search form in view:
.row
= horizontal_simple_form_for SearchForm.new, {url: cars_path, method: :get} do |f|
.col-md-4
.row
.col-md-12
= f.input :handover_location, label: I18n.t('.handover'), collection: Location.all.map{|hl| [hl.name, hl.id]}
= f.input :return_location, label: I18n.t('.return') ,collection: Location.all.map{|rl| [rl.name, rl.id]}
= f.input :car_class, label: I18n.t('.car_class') ,collection: CarClass.all.map { |c| [c.name, c.id] }, include_blank: true
.col-md-4
= f.input :handover_date, as: :string, label: false
= f.input :return_date, as: :string, label: false
= f.submit class: 'btn btn-success'
search method in car model:
def self.search(params)
self.joins(:reservations).where.not("reservations.reception_time <= ? AND reservations.return_time >= ?",
params[:return_date], params[:handover_date]).
joins(:car_class).where("car_classes.id= ?", params[:car_class])
.cars_at_both_locations(params[:handover_location], params[:return_location])
end

Related

Ruby on Rail Nested Attributes do not save to database

I am trying to create a form that updates 2 tables - commission_type and commission_tier.
I created the models, controller and form but when I submit it, my commission_tier table does not update. Only my commission_type table updates.
Can someone take a look at my code and tell me what I am doing wrong? I have combed through my code trying to find the mistake, and I cannot find it.
My models
class CommissionType < ApplicationRecord
has_many :commission_tiers
accepts_nested_attributes_for :commission_tiers
end
class CommissionTier < ApplicationRecord
belongs_to :commission_types, optional: true
end
My controller
class Admin::CommissionTypesController < Admin::BaseController
def index
#commission_types = CommissionType.all
end
def new
#commission_type = CommissionType.new
#commission_type.commission_tiers.build
end
def create
#commission_type = CommissionType.new(commission_type_params)
if #commission_type.save
redirect_to admin_commission_types_index_path
else
render "new"
end
private
def commission_type_params
params.require(:commission_type).permit(:name, :active, :allow_mass_update, :plan,
commission_tiers_attributes: [:id, :increment_value, :rate, :commission_type_id])
end
end
My form
<%= simple_form_for #commission_type, url: admin_commission_types_index_path, wrapper: :bootstrap2, :html => { :class => 'form-horizontal' } do |f| %>
<fieldset>
<legend>Properties</legend>
<%= f.input :name, label: 'Commission Name' %>
<%= f.input :active, as: :boolean, label: 'Active?', label_html: { class: 'padding-top' } %>
<%= f.input :allow_mass_update, as: :boolean, label: 'Allow mass update?', label_html: { class: 'padding-top' } %>
<%= f.input :plan, input_html: {id: 'dropdown'},
label: 'Commission Type',
collection: [ ['Select One..', 'select'], ['Flat', 'flat'], ['Flat +', 'flat_plus' ], ['Promotional', 'promotional'], ['Straight', 'straight'], ['Waterfall', 'waterfall'], ['Sliding Scale', 'sliding_scale'] ],
selected: 'select'
%>
</fieldset>
<fieldset id="flat">
<legend>Flat Commission</legend>
<%= f.simple_fields_for :commission_tiers do |builder| %>
<%= builder.input :rate %>
<%= builder.input :increment_value %>
<% end %>
</fieldset>
My form is displaying and working
UPDATE
Some additional details
CommissionType column values = [:name, :active, :allow_mass_update, :plan]
CommissionTier column values = [:id, :increment_value, :rate, :commission_type_id]
Also, when I submit my form, here is an example of what my params are
<ActionController::Parameters {"name"=>"asdf", "active"=>"1", "allow_mass_update"=>"1", "plan"=>"flat", "commission_tiers_attributes"=><ActionController::Parameters {"0"=><ActionController::Parameters {"rate"=>"45"} permitted: true>} permitted: true>} permitted: true>

Filtering multiple columns with Pg_Search in Ruby on Rails

I am working on a project where we have to filter event based on location, date, sport, and skill level. At the moment each filter works individually but I I cannot chain them to get even more specific results.
This is what I have in my model.
include PgSearch::Model
pg_search_scope :global_search,
against: [:location, :sport, :level, :date]
pg_search_scope :date_search,
against: [:date]
pg_search_scope :sport_search,
against: [:sport],
using: {
tsearch: { prefix: true }
}
pg_search_scope :location_search,
against: [:location],
using: {
tsearch: { prefix: true }
}
pg_search_scope :level_search,
against: [:level]
enum level: { Beginner: 1, Intermediate: 2, Advanced: 3, Pro: 4 }
And this is in my controller.
def index
if params[:search][:location].present?
#events = policy_scope(Event).location_search(params[:search][:location])
elsif params[:search][:sport].present?
#events = policy_scope(Event).sport_search(params[:search][:sport])
elsif params[:search][:date].present?
#events = policy_scope(Event).date_search(params[:search][:date])
elsif params[:search][:level].present?
#events = policy_scope(Event).level_search(params[:search][:level])
else
#events = policy_scope(Event)
end
#markers = #events.geocoded.map do |event|
{
lat: event.latitude,
lng: event.longitude
}
end
end
And these are my filters
<div id="collapseFilters" class="container collapse form">
<%= simple_form_for :search, url: events_path, method: "GET", html: { class: 'form-block' } do |f| %>
<%= f.input :date, as: :string, required: false, input_html: {class: "datepicker"} %>
<%= f.input :level, collection: Event.levels.map{ |l| [l.first, l.second] }, required: false, label_method: :first, value_method: :second%>
<%= f.input :location, required: false, placeholder: 'Enter a location for your event' %>
<%= f.button :submit, 'Search', class: "btn btn-primary btn-lg btn-form mt-3" %>
<% end %>
</div>
How could I chain them in the params so I could use more than one filter at once?
You can update your controller:
def index
#events = policy_scope(Event)
#events = #events.location_search(params[:search][:location]) if params[:search][:location].present?
#events = #events.sport_search(params[:search][:sport]) if params[:search][:sport].present?
#events = #events.date_search(params[:search][:date]) if params[:search][:date].present?
#events = #events.level_search(params[:search][:level]) if params[:search][:level].present?
#markers # ...
end
or even (if it is clear enough for you)
def index
#events = policy_scope(Event)
%i[location sport date level].each do |filter|
next unless params[:search][filter].present?
#events = #events.public_send("#{filter}_search", params[:search][filter])
end
#markers # ...
end
if I get how this gem works right.
You should use multisearchable option as described in documentation https://github.com/Casecommons/pg_search

first element in form can't be blank

I've got this problem
.container.weekdays
.row
.col-sm-3
.panel-default data-day="#{#weekdays[0]}"
.h3
|Today's menu.
=#weekdays[0]
br
=link_to "Today's menu", admin_menu_path("#{#weekdays[0]}"), class: 'today', remote: true
hr
hr
fieldset
legend Update menu here
=form_for [:admin, #menu] ,method: :patch, remote: true do |f|
p
= f.select(:type, options_for_select(["", :First, :Second, :Drink], include_blank: true))
p
=f.label 'Name'
=f.text_field :name
p
=f.label 'Price'
=f.number_field :price
= f.submit
and menus controller, where show action is working right
class Admin::MenusController < Admin::BaseController
before_action :set_menu , only:[:update]
def show
#menu = Menu.where(day: params[:id])
end
def update
#menu = Menu.where(day: DateTime.now.strftime("%A"))
#item = menu_params.type.constantize.new(name: menu_parms.name, price: params[:menu][:price], menu: #menu)
#item.save
end
private
def menu_params
params.require(:menu).permit(:type, :name, :price)
end
end
I am recieving first argument cn't be nill in form update, need some help n that. I do undestand that code is awful but still I am a newcommer it should work first than I'll try to refact
To better understand the problem here is menu model
class Menu < ApplicationRecord
has_many :dishes
delegate :firstmeals, :secondmeals, :drinks, to: :dishes
validates :day, presence: true
validates :day, uniqueness: true
def self.get_menu day
Menu.where(day: day).first
end
private
def to_param
day
end
end
So I do not actually need to update menu, I need only to create a dish. But the same problemm still exist
fieldset
legend Update menu here
=form_for [:admin, #dish], method: :patch, remote: true do |f|
p
= f.select(:type, options_for_select(["", :First, :Second, :Drink], include_blank: true))
p
=f.label 'Name'
=f.text_field :name
p
=f.label 'Price'
=f.number_field :price
= f.submit
include_blank should be outside of options_for_select
= f.select(:type, options_for_select([:First, :Second, :Drink]), { include_blank: true })
If you are passing include_blank option you don't need to add empty string manually
documentation for select

Rails simple_form create form when column type is JSON

I have a model (FooBar) with three columns:
Foo -> String
Bar -> JSON
Baz -> String
I want to create a form for this model
Bar has default attributes of: {zing: {}, zaz: {}, laz: {}}
I would like to have the following inputs:
f.input :foo
f.input :zing
f.input :zaz
f.input :laz
f.input :baz
I tried to do this using fields_for and passing in each key and converting it to a symbol:
bar.each do |k,v|
f.input k.to_sym
end
but the error I'm getting is that FooBar has undefined method of :zaz
Any ideas would be appreciated, thanks.
You should be able to do it like this:
f.simple_fields_for :bar do |bar_f|
bar.each do |k,v|
bar_f.input k.to_sym
end
end
Don't forget to allow the parameters in the controller.
You can do something like this:
class User < ActiveRecord::Base
serialize :preferences, HashSerializer
store_accessor :preferences, :blog, :github, :twitter
end
And then you will have access to blog, github and twitter just as if they were normal properties in the model and your form is going to look something like this:
= simple_form_for(#user, html: { class: "form" }) do |f|
= f.input :blog
= f.input :github
= f.input :twitter
You have more info in this link! https://github.com/plataformatec/simple_form/wiki/Nested-inputs-for-key-value-hash-attributes
Hope it helps!
Set #temp variable
#temp = FooBar.new
#temp.data = {zing: "", zaz: "", laz: ""}
This code works for me
<%= simple_form_for #temp do |f| %>
<%= f.simple_fields_for :data do |data_f| %>
<% #temp.data.each do |k,v| %>
<%= data_f.input k.to_sym %>
<% end %>
<% end %>
<%= f.button :submit %>
<% end %>
Don't forget about permission params
params.require(:temp).permit(data: [:zing, :zaz, :laz])
If you don't want to define accessors, you could do something like:
= simple_form_for(#foo_bar) do |f|
= f.simple_fields_for :bar do |bf|
= bf.input :zing, input_html: { value: f.object.bar[:zing] }
= bf.input :zaz, input_html: { value: f.object.bar[:zaz] }
= bf.input :laz, input_html: { value: f.object.bar[:laz] }
You would need to initialise bar with {} in your controller

Active Admin checkboxes not selected when edit model

I'm using a custom collection to display checkboxes with schedule. It saves, but when I try to edit it returns to me unchecked. Why?
f.inputs for: :schedule, name: 'Employee Schedule' do |sf|
sf.input :sunday, as: :check_boxes, collection: available_hours, method: :to_s
sf.input :monday, as: :check_boxes, collection: available_hours, method: :to_s
sf.input :tuesday, as: :check_boxes, collection: available_hours, method: :to_s
sf.input :wednesday, as: :check_boxes, collection: available_hours, method: :to_s
sf.input :thursday, as: :check_boxes, collection: available_hours, method: :to_s
sf.input :friday, as: :check_boxes, collection: available_hours, method: :to_s
sf.input :saturday, as: :check_boxes, collection: available_hours, method: :to_s
end
def available_hours
(0..23).map { |h| ["#{h}h às #{h.next}h", h] }
end
helper_method :available_hours
I found a solution for this question
My collection remains unaltered
def available_hours
Array(0..23)
end
And my form will have a :member_label parameter receiving a Proc that will change it after collection already gathered
member_label: Proc.new { |h| "#{h}h às #{h.next}h" }
After modifications:
sf.input :sunday, as: :check_boxes, collection: available_hours, member_label: Proc.new { |h| "#{h}h às #{h.next}h" } , method: :to_s
and so on...
You need to determine which checkbox is selected like this: ["#{h}h às #{h.next}h", h, :selected]
def available_hours(_h)
(0..23).map { |h| ["#{h}h às #{h.next}h", h, h == _h ? :selected : ''] }
end
sf.input :sunday, as: :check_boxes, collection: available_hours(sh.object.sunday), method: :to_s
...or something similar.
It could be a different situation/need, but I think what I did for one of my project is possible a solution. I created a custom FormStatic input class that can be used for an ActiveAdmin edit form.
module ActiveAdmin
module Inputs
class ProductsInput < ::Formtastic::Inputs::CheckBoxesInput
def choice_html(choice)
html_options = label_html_options.merge(
:for => choice_input_dom_id(choice),
:class => checked?(choice[1]) ? 'checked' : nil
)
template.content_tag(:label, choice_label(choice), html_options)
end
def collection
super.sort {|a, b| a[0] <=> b[0]}
end
def choice_label(choice)
name, id = choice
product = Product.find(id)
name = ''
name << template.content_tag(:span, product.human_state_name, class: 'status_tag important') + ' ' unless product.on_sale?
name << product.name
(hidden_fields? ?
check_box_with_hidden_input(choice) :
check_box_without_hidden_input(choice)) + \
template.image_tag(product.listing.thumb, width: 60).html_safe + \
template.content_tag(:span, name.html_safe, class: 'choice_label')
end
end
end
end
Then you can use it in an edit block like this:
ActiveAdmin.register Collection do
form do |f|
f.inputs 'Products' do
f.input :products, as: :products
end
end
end
Collection has_many products through collection_products.

Resources