Can I add logic to a rails 3 hidden field? - ruby-on-rails-3

I have a form to submit data and I want to automatically set one field depending on whether all the other fields are filled out or not. If they are all completed, the field will be "complete", if not it will be set to "draft".
So I have the hidden field like this:
<%= f.hidden_field :status, :value => "draft" %>
to make it default to draft. BUT, can I add logic that says it will be "complete" if all the other fields are filled out and if so how?

Here is how to do it on the client-side with jquery, assuming your model is named foo:
<script type='text/javascript'>
$(document).ready(function() {
$('input[name*="otherfields"]').on('change', function() {
var othercount = 0;
$('input[name*="otherfields"]').each(function() {
if ( $(this).is(':checked') )
othercount += 1;
});
if ( othercount == 2 )
$('#foo_status').attr('checked',true)
else
$('#foo_status').attr('checked',false)
});
});
</script>
<%= check_box_tag :item1 , '1', false, :name=>'otherfields[1]' %>
<%= check_box_tag :item2 , '2', false, :name=>'otherfields[2]' %>
<%= f.hidden_field :status, :value => "draft" %>

Assuming no other client-side events have to take place when the the status changes, it would be best practice to place this kind of business logic inside of your model as a callback, e.g. (replace Foo and fieldx with your model and field names):
class Foo < ActiveRecord::Base
before_save :default_status
def default_status
if field1 && field2 && field3 && field4
self.status = 'completed'
else
self.status = 'draft'
end
end
end

Yes, you can do that in the controller.
Lets say the form directs you to the create action.
In the create action of the controller, you can check if all the fields are completed by looking at params and then use if statement to assign appropriate value to status before saving

This would be done with javascript, possibly jQuery. However, why would you design it this way? Could you not do this on the server side?

Related

Search for multiple possible param values

At the moment I have a form in which the user can input price per person and/or duration and/or team_size. What I would like to accomplish is to retrieve all records from a table that match the user input and for this I set scope in the model:
scope :filter_by_team_size, -> (team_size) { where("team_size = ?", team_size) }
scope :filter_by_duration, -> (duration) { where("duration = ?", duration) }
scope :filter_by_price, -> (price) { where("price = ?", price) }
And then in the controller use that again to retrieve the records by doing so:
#experiences = policy_scope(Experience).order(team_size: :desc).geocoded.filter_by_team_size(params[:team_size]) if params[:team_size].present?
#experiences = policy_scope(Experience).order(duration: :desc).geocoded.filter_by_duration(params[:duration]) if params[:duration].present?
#experiences = policy_scope(Experience).order(price: :desc).geocoded.filter_by_price(params[:price]) if params[:price].present?
However, this only gives me only the records for which the first input value matches but ignores all other values. Additionally, when you are viewing the search results and use the filter again it should apply the filter only for the records it found already.
Any suggestion on how to solve this would be much appreciated!
One way to handle this is to use a virtual model that handles binding parameters to and from the form:
class SearchQuery
include ActiveModel::Model
include ActiveModel::Attributes
attribute :team_size, :integer
attribute :duration
attribute :price
end
You can then setup the form:
<%= form_with(model: (#search_query || SearchQuery.new), url: '/experiences', method: :get) %>
<div>
<%= f.label :team_size %>
<%= f.number_field :team_size %>
</div>
# ..
<% end %>
And then you can just bind the params to the model with ActionController::Parameters#permit just like you would with a normal ActiveRecord model:
class ExperiencesController
before_action :set_search_query, only: :index, if: ->{ params[:search_query].present? }
# ...
def index
#experiences = if #search_query
#search_query.build_scope(policy_scope(Experience))
else
policy_scope(Experience)
end.geocoded
end
private
def set_search_query
#search_query = SearchQuery.new(search_query_params)
end
def search_query_params
params.fetch(:search_query).permit(:team_size, :duration, :price)
end
end
This loopback will make the form stateful just like your normal CRUD forms. We have not actually implemented #build_scope yes so lets do so:
class SearchQuery
include ActiveModel::Model
include ActiveModel::Attributes
attribute :team_size, :integer
attribute :duration
attribute :price
def build_scope(base_scope)
compacted_attributes = attributes.reject { value.nil? || value.empty? }
compacted_attributes.each_with_object(base_scope) do |(k,v), base|
if base.respond_to? "filter_by_#{k}"
# lets you customize the logic with a scope
base.send("filter_by_#{k}", v) # the scope is responsible for ordering
else
# convention over configuration!
base.where(Hash[k,v]).order(Hash[k,:desc])
end
end
end
end
Since this uses convention over configuration you can get rid of those pointless scopes in your model.

Setting multiple attributes select form Rails 3

I have a drop-down menu that lists a collection of locks. I would like to set two params when an option is selected:
:name => l.name (which works with the code below)
:device_id => l.id
<%= f.select(:name, Lock.all.collect {|l| [ l.name ] } ,{:include_blank => true} ) %>
I've tried passing the value in a hidden_field, but the field isn't aware of the lock that was selected. Any input is much appreciated.
Hi you can do this:
<%= select_tag(:name, options_from_collection_for_select(Lock.all, :id, :name, params[:name]) )%>
or in your controller add this code:
#locks = Lock.find(:all)
and in your views
<%= select_tag(:name, options_from_collection_for_select(#locks, :id, :name, params[:name]) )%>
when you submit your form that contains this select_tag the params[:name] will get the selected name from the url of your app.
Hope it helps.
So You need to get two values in controller side ?
Ex:-
I have below values in my db
ID Name
4 gggg
5 tttt
2 iiii
So dropdown will show the all names gggg,tttt,iiii if you select tttt from dropdown in server you required both 5, tttt right?
Then you need to use below code
In controller
#locks = Lock.all.map{|l| [l.name, "#{l.id};#{l.name}"]}
In your view
<%= select_tag(:name, options_from_collection_for_select(#locks) )%>
So it will send the both id and name values saparated by ";" .
In your controller you need to split by ";"

rails OR query based on multiple checkbox selections

This seems like it should be a common problem but I'm having trouble finding an answer. Basically I want to have a form with 10 or so checkboxes which I'm creating with check_box_tag. When the form is submitted I want to generate a query that return all records that match ANY of the checked selections. So, the number of checked selections will vary.
So, for example, if I have
class Book < ActiveRecord::Base
belongs_to :author
end
I want to generate something like
Book.where("author_id = ? or author_id = ?", params[authors[0]], params[authors[1]]) if there are two boxes checked, etc.
Thanks for any insight.
Will this work for you?
Book.where(author_id: [array_of_author_ids])
You need to collect author_ids from params first
I recently had to do something similar, this is how I achieved this. It's pretty clever (at least I think so. :))
I created a query model that serializes the query column (text field) in JSON. I use a form to get the query data from the user with selection fields.
class BookQuery < ActiveRecord::Base
has_many :books
# loop through each foreign key of the Book table and create a hash with empty selection
def self.empty_query
q = {}
Book.column_names.each do |column_name|
next unless column_name.ends_with?("_id")
q.merge column_name => []
end
end
end
I'm using Author as an example below:
<%= form_for #book_query do |f| %>
<% for author in Author.all %>
<%= check_box_tag "book_query[query][author_ids][]", author.id, false%>
<%= author.name %>
<% end %>
<%= f.submit "Save Query" %>
<% end %>
When this form is submitted you ended up with parameters like this:
When the form is submitted it generates this parameter:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXXXXXXXXXX", "book_query"=>{"query"=>{"author_ids"=>["2", "3"]}}, "commit"=>"Save Query"}
Now in the BookQuery controller's create action you can just do what create function always does:
def create
#book_query = BookQuery.build(params[:book_query])
if #book_query.save
flash[:success] = "Book query successfully saved."
redirect_to ...
else
flash[:error] = "Failed to save book query."
render :new
end
end
But by default rails serializes the data in hash type:
1.9.3p194 :015 > pp BookQuery.find(9).query
BookQuery Load (0.7ms) SELECT "book_queries".* FROM "book_queries" WHERE "book_queries"."id" = $1 LIMIT 1 [["id", 9]]
"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nauthor_ids:\n- '2'\n- '3'\n"
=> "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nauthor_ids:\n- '2'\n- '3'\n"
In BookQuery model, add the following:
serialize :query, JSON
But rail would change the IDs to string:
1.9.3p194 :018 > query = JSON.parse(BookQuery.find(10).query)
BookQuery Load (0.5ms) SELECT "book_queries".* FROM "book_queries" WHERE "book_queries"."id" = $1 LIMIT 1 [["id", 10]]
=> {"author_ids"=>["2", "3"]}
1.9.3p194 :019 > query["author_ids"]
=> ["2", "3"]
What I did then is override the attribute accessors in BookQuery model:
The below has to be done because the hash returns strings, not ids in integer.
def query=(query)
query.each_pair do |k, v|
if query[k].first.present?
query[k].map!(&:to_i)
else
query.except!(k)
end
end
write_attribute(:query, query)
end
# just want to avoid getting nil query's
def query
read_attribute(:query) || {}
end
To find book with this query, you can simply add this function to your Book model:
def self.find_by_book_query(book_query, options = {})
options[:conditions] = book_query.query
find(:all, options)
end
Now you get a customizable query string based on the model definition Book and everything works like the Rails way. :)

How can I pass an object via params in rails?

I'm sure there is a better way to do this, but in my inexperience, I'm having trouble sorting that better way out. I have a link that needs to send and object to controller for processing. It isn't working properly:
views/home/index.html.erb
<% search_term = "pizza" %>
<% #tag = Tag.find(:all, :conditions => ["name = ?", search_term ]).first %>
<li> <%= link_to(search_term, {:controller => "restaurants", :action => "index", :search_item => #tag}) %> </li>
controllers/restaurants.rb
def index
search_tag = params[:search_item]
#restaurants = Restaurant.search_by_tag(search_tag)
models/restaurant.rb
Class Restauarant < ActiveRecord::Base
def self.search_by_tag(search_tag)
search_condition = search_tag.name
#tags = Tag.find(:all, :conditions => [" name = ?", search_condition ])
#tag = #tags.first
#Restaurants = #tag.restaurants
end
end
This causes a NoMethodError in ResataurantsController#index
parameters:
{"searchitem" => "15"}
For some reason, the tag object isn't being passed properly from the home/index.html.erb and is only passing along the Tag-object :id to the restaurants controller. Isn't possible to pass a full object this way. what am I doing wrong?
You can't submit an object through get params like that. Typically, you'll just pass the object's id (which you are already doing), and then do a lookup in the controller:
#tag = Tag.find(params[:search_item])
It would make more sense to rename the "search_item" param to "tag_id".

Selected option not working for select

I have this select which works fine, but default the select is empty and doesn't show the selected value (which is filled correctly):
<%= f.select(:relationgroup, options_for_select(#relationgroups), { :selected => #relation.relationgroup, :include_blank => true}) %>
Any idea why? Thanks!
Try it that way:
<%= f.select(
:relationgroup,
options_for_select(#relationgroups, #relation.relationgroup),
:include_blank => true
) %>
Not sure, but maybe it'll work better.
Anyway, assuming Relationgroup is some model with id and name (or any other attribute that you want to be visible in select options) attributes, and you're using default relationgroup_id foreign key in your model you'd better construct your select like that:
<% f.select(
:relationgroup_id,
options_from_collection_for_select(#relationgroups, :id, :name),
:include_blank => true
) %>
It'll choose selected value based on object.relationgroup_id where object is the model you're building form for. See docs for more information.