rails validation of presence not failing on nil - ruby-on-rails

I want to make sure an attibute exists, but it seems to still slip thru and I'm not sure how better to check for it.
This should work, but doesn't. It's a attr_accessor and not a real attribute if that makes a difference.
validates_presence_of :confirmed, :rooms
{"commit"=>"Make Booking",
"place_id"=>"the-kosmonaut",
"authenticity_token"=>"Tkd9bfGqYFfYUv0n/Kqp6psXHjLU7CmX+D4UnCWMiMk=",
"utf8"=>"✓",
"booking"=>{"place_id"=>"6933",
"bookdate"=>"2010-11-22",
"rooms"=>[{}],
"no_days"=>"2"}}
Not sure why my form_for returns a blank hash in an array...
<% form_for :booking, :url => place_bookings_path(#place) do |f| %>
<%= f.hidden_field :bookdate, { :value => user_cart.getDate } %>
<%= f.hidden_field :no_days, { :value => user_cart.getDays } %>
<% for room in pricing_table(#place.rooms,#valid_dates) %>
<%= select_tag("booking[rooms][][#{room.id}]", available_beds(room)) %>
<% end %>
<% end %>

Override validate method and write your custom validation check there. Something like
def validate
if rooms.blank? || rooms.first.blank? # first because it seems to be an array that holds only one Hash.
errors.add_to_base "Rooms can't be blank."
end
end
By the way, why is rooms structured to be an array that holds a single hash? For a more sensible solution, you might want to explain that.

Building on Chirantan's answer, isn't rooms a child of the booking hash? So shouldn't it be:
def validate
if booking[:rooms].blank? || booking[:rooms].first.blank?
errors.add_to_base "Rooms can't be blank."
end
end

validates_presence_of checks whether a field is blank. The validation would fail if your rooms array was empty ( set to [] ), but since your array contains a hash it is not empty, so the validation does not fail.
To demonstrate, try this from the console:
a = []
a.empty?
This will return true.
a = [{}]
a.empty?
Returns false.

Try removing a dimension from your array:
<%= select_tag("booking[rooms][#{room.id}]", available_beds(room)) %>
instead of
<%= select_tag("booking[rooms][][#{room.id}]", available_beds(room)) %>

Related

Rails custom validation for required fields

I have an app where a user has to fill in all survey questions (radio buttons below each question). Sample params which I'm getting from the view when the user answered only one question are:
{"answer_11"=>"", "answer_12"=>"", "answer_16"=>"", "answer_9"=>"Velit assumenda id.", "answer_10"=>""}
I know I should use the required options inside of a form but it won't worked with my simple form views:
<%= simple_form_for :test_results, url: test_results_path do |f| %>
<% #randomize_questions.map do |q| %>
<%= q[:question] %>
<%= f.input "answer_#{q[:id]}", required: true, collection: q[:answers], as: :radio_buttons, value: { question: q[:question], answer: q[:answer]} %>
<% end %>
<%= f.button :submit %>
<% end %>
create action
def create
#test_result = TestResult.new(
answer: test_result_params,
)
#test_result.save
end
def test_result_params
params.require(:appropriateness_test_results).permit!
end
How to write such validation to prevent creation of a new record if a user did not answer all questions?
It would be helpful to see the schema of DB for that model (TestResult). I am assuming it has a json or somehow serialized field called answer that stores that hash {"answer_11"=>"", "answer_12"=>"", "answer_16"=>"", "answer_9"=>"", "answer_10"=>""}. And requirement is to validate that there are no blank values. you can have following validation in TestResult model (assuming TestResult#answer returns the answer hash)
validate :no_blank_answers
def no_blank_answers
if answer.values.any?(&:blank?)
errors.add(:answer, "cannot have blank answers")
end
end
have not tested in IRB but should work.
You can write a validator for TestResult model.
validates :answer, presence: true - and if your result don't have a answer(field will be null) this return a error, you can saw his on #test_result.errors
https://guides.rubyonrails.org/active_record_validations.html

How to deal with serialized field in rails form

I have a serialized field in my model
class Screen < ActiveRecord::Base
serialize :options
end
The user should be able to add / edit n number of options for each record. I saw this SO question and tried
<%= f.fields_for :options do |o| %>
<%= o.label :axis_y %>
<%= o.text_field :axis_y %>
<%= o.label :axis_x %>
<%= o.text_field :axis_x %>
<% end %>
but my problem is I don't know what are the fields user want to add, and user can specify variable number of attributes foroptions. What is the best/proper way to to do this ? Any help much appreciated. Thanks
I've never seen serialize before, so I looked it up. Here's a tutorial; apparently you need to specify the type of the serialized object as well:
serialize :options, Hash
To whitelist the hash attrributes, you have a couple options.
You could create a custom validator (see here for instructions)
You can also overwrite the options= method:
def options=(val)
whitelisted_keys = ["some", "keys"]
if val.is_a?(Hash) && val.keys.all? { |key| whitelisted_keys.include? key }
super
else
errors.add(:options, "is invalid")
self
end
end
You also might need to configure your screen_params method, so if things aren't working show that code in your question.

How to access nested child element in fields_for

I'm trying to access a Hash type of mongoid in fieds_for and I already have a relationship with a model and want to access a hash of that model. Something like:
class Leave
field :leaves_types, :type => Hash
end
class User
has_many :leaves
end
Want to do something like:
form_for #user do |f|
f.fields_for :leaves.leave_types...
How I can achieve this? Thanks in advance.
You should give a block to fields_for. For more information on that method see docs. In your case, first, add this line to your User model:
class User
has_many :leaves
accepts_nested_attributes_for :leaves
end
This is required so that when your form is posted, the attributes coming from the form fields for leaves via params were handled correctly.
Now your template should look like this (for simplicity by now I assume that your Leave also has a simple text field named foo):
<%= form_for #user do |f| %>
...
<%= f.fields_for :leaves do |leave_fields| %>
# Fields for a leave here ----v
Foo: <%= leaves_fields.text_field :foo %>
<% end %>
<% end %>
Or, if you #user.leaves already initialized and you want form builder to put its values to form fields, you have to iterate over #user.leaves, passing each of them to fields_for as second argument:
<%= form_for #user do |f| %>
...
<% #user.leaves.each do |leave| %>
<%= f.fields_for :leaves, leave do |leave_fields| %>
# Note the addition here --^
Foo: <%= leave_fields.text_field :foo %>
<% end %>
<% end %>
<% end %>
But your question has another one inside: you have not a text field, but a hash, and there is no default form input for it (i.e. there is no f.hash_field :leaves_types). So you may want to make it by yourself like suggested in these questions: [1], [2] and [3].
Anyway, having a Hash field seems rather uncommon to me, so maybe Hash can be somehow replaced, say, with another has_many association (not sure), and in this case you will only need another nested fields_for.

Editing a single attribute with more than one form field

Background: Users and communities share a
has_many :through
relationship. Each community has a "community_type" string that identifies it (ie "Gender", "City", etc.).
Objective: In my edit form, I'd like to allow the user to edit his :community_ids based on community type. Something like:
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_ids, Community.filtered_by("Gender"), :id, :name) %>
<%= f.collection_select(:community_ids, Community.filtered_by("City"), :id, :name) %>
<% end %>
The issue is that the form only accepts the last form field value for :community_ids - in this case being the "City" - rather than merging all of them as one big :community_ids array.
Solution:
For those interested, I ended up refactoring my model code from the answers below to this:
%W[ community1 community2 community3 community4 ].each do |name|
define_method "#{name}" do
self.communities.filtered_by("#{name}").map(&:name)
end
define_method "#{name}_ids" do
self.communities.filtered_by("#{name}").map(&:id)
end
define_method "#{name}_ids=" do |val|
self.community_ids += val
end
end
Is there a reason you're using select boxes for a has_many relationship? It seems checkboxes would be more appropriate. If you want to go with select boxes, I don't think you can use FormHelper#select, because as far as I know, it's expecting a single value, and your community_ids is an array. This is why it's only picking one of the values you give it.
For a select box (or any field), you can combine the values across fields by adding [] to the parameter name which tells Rails that the parameter is an array of values. You can do this by using regular select_tag to create the fields, and setting the parameter name as follows:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
You could also go with Ryan's approach of sending separate parameters, though one downside is your User model will have to be very aware of the types of communities that exist, and you'll have to write separate logic in the User model for each type of community. This will make your resources less modular. But if you do go that way, I'd suggest using pseudo-attributes instead of a before_save so that your community_ids get updated automatically from the params:
class User < ActiveRecord::Base
...
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
...
end
And then your select_tag calls would look something like this:
<%= form_for current_user do |f| %>
<%= select_tag("user[community_gender_ids][]", options_for_select(Community.filtered_by("Gender").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("Gender").first.id) %>
<%= select_tag("user[community_city_ids][]", options_for_select(Community.filtered_by("City").map{|c| [c.name, c.id]}, :selected => current_user.communities.filtered_by("City").first.id) %>
<% end %>
Updated to complete tsherif's (better than my original) answer.
view.rb
<%= form_for current_user do |f| %>
<%= f.collection_select(:community_gender_ids, Community.filtered_by("Gender"), :id, :name, {}, id: 'community-gender-options') %>
<%= f.collection_select(:community_city_ids, Community.filtered_by("City"), :id, :name, {}, id: 'community-city-options') %>
<% end %>
model.rb
def community_gender_ids=(cg_ids)
self.community_ids ||= []
self.community_ids += cg_ids
end
def community_city_ids=(cc_ids)
self.community_ids ||= []
self.community_ids += cc_ids
end
def community_gender_ids
self.communities.select(:id).where(:community_type => 'gender').map(&:id)
end
def community_city_ids
self.communities.select(:id).where(:community_type => 'city').map(&:id)
end
Alternatively, you could write some CoffeeScript/Javascript to bind to the select tags and add the IDs to a hidden value which is then submitted to the server with the form.

How to edit a Rails serialized field in a form?

I have a data model in my Rails project that has a serialized field:
class Widget < ActiveRecord::Base
serialize :options
end
The options field can have variable data info. For example, here is the options field for one record from the fixtures file:
options:
query_id: 2
axis_y: 'percent'
axis_x: 'text'
units: '%'
css_class: 'occupancy'
dom_hook: '#average-occupancy-by-day'
table_scale: 1
My question is what is the proper way to let a user edit this info in a standard form view?
If you just use a simple text area field for the options field, you would just get a yaml dump representation and that data would just be sent back as a string.
What is the best/proper way to edit a serialized hash field like this in Rails?
If you know what the option keys are going to be in advance, you can declare special getters and setters for them like so:
class Widget < ActiveRecord::Base
serialize :options
def self.serialized_attr_accessor(*args)
args.each do |method_name|
eval "
def #{method_name}
(self.options || {})[:#{method_name}]
end
def #{method_name}=(value)
self.options ||= {}
self.options[:#{method_name}] = value
end
attr_accessible :#{method_name}
"
end
end
serialized_attr_accessor :query_id, :axis_y, :axis_x, :units
end
The nice thing about this is that it exposes the components of the options array as attributes, which allows you to use the Rails form helpers like so:
#haml
- form_for #widget do |f|
= f.text_field :axis_y
= f.text_field :axis_x
= f.text_field :unit
Well, I had the same problem, and tried not to over-engineer it. The problem is, that although you can pass the serialized hash to fields_for, the fields for function will think, it is an option hash (and not your object), and set the form object to nil. This means, that although you can edit the values, they will not appear after editing. It might be a bug or unexpected behavior of rails and maybe fixed in the future.
However, for now, it is quite easy to get it working (though it took me the whole morning to figure out).
You can leave you model as is and in the view you need to give fields for the object as an open struct. That will properly set the record object (so f2.object will return your options) and secondly it lets the text_field builder access the value from your object/params.
Since I included " || {}", it will work with new/create forms, too.
= form_for #widget do |f|
= f.fields_for :options, OpenStruct.new(f.object.options || {}) do |f2|
= f2.text_field :axis_y
= f2.text_field :axis_x
= f2.text_field :unit
Have a great day
emh is almost there. I would think that Rails would return the values to the form fields but it does not. So you can just put it in there manually in the ":value =>" parameter for each field. It doesn't look slick, but it works.
Here it is from top to bottom:
class Widget < ActiveRecord::Base
serialize :options, Hash
end
<%= form_for :widget, #widget, :url => {:action => "update"}, :html => {:method => :put} do |f| %>
<%= f.error_messages %>
<%= f.fields_for :options do |o| %>
<%= o.text_field :axis_x, :size => 10, :value => #widget.options["axis_x"] %>
<%= o.text_field :axis_y, :size => 10, :value => #widget.options["axis_y"] %>
<% end %>
<% end %>
Any field you add in the "fields_for" will show up in the serialized hash. You can add or remove fields at will. They will be passed as attributes to the "options" hash and stored as YAML.
I've been struggling with a very similar problem. The solutions I found here were very helpful to me. Thank you #austinfromboston, #Christian-Butske, #sbzoom, and everyone else. However, I think these answers might be slightly out-of-date. Here's what worked for me with Rails 5 and ruby 2.3:
In the form:
<%= f.label :options %>
<%= f.fields_for :options do |o| %>
<%= o.label :axis_y %>
<%= o.text_field :axis_y %>
<%= o.label :axis_x %>
<%= o.text_field :axis_x %>
...
<% end %>
and then in the controller I had to update the strong parameters like so:
def widget_params
params.require(:widget).permit(:any, :regular, :parameters, :options => [:axis_y, :axis_x, ...])
end
It seems to be important that the serialized hash parameter comes at the end of the list of parameters. Otherwise, Rails will expect the next parameter to also be a serialized hash.
In the view I used some simple if/then logic to only display the hash if it is not empty and then to only display key/value pairs where the value was not nil.
I was facing the same issue, after some research i found a solution using Rails' store_accessor to make keys of a serialized column accessible as attributes.
With this we can access "nested" attributes of a serialized column …
# post.rb
class Post < ApplicationRecord
serialize :options
store_accessor :options, :value1, :value2, :value3
end
# set / get values
post = Post.new
post.value1 = "foo"
post.value1
#=> "foo"
post.options['value1']
#=> "foo"
# strong parameters in posts_controller.rb
params.require(:post).permit(:value1, :value2, :value3)
# form.html.erb
<%= form_with model: #post, local: true do |f| %>
<%= f.label :value1 %>
<%= f.text_field :value1 %>
# …
<% end %>
No need setter/getters, I just defined in the model:
serialize :content_hash, Hash
Then in the view, I do (with simple_form, but similar with vanilla Rails):
= f.simple_fields_for :content_hash do |chf|
- #model_instance.content_hash.each_pair do |k,v|
=chf.input k.to_sym, :as => :string, :input_html => {:value => v}
My last issue is how to let the user add a new key/value pair.
I will suggest something simple, because all the time, when user will save form You will get string. So You can use for example before filter and parse those data like that:
before_save do
widget.options = YAML.parse(widget.options).to_ruby
end
of course You should add validation if this is correct YAML.
But it should works.
I'm trying to do something similar and I found this sort of works:
<%= form_for #search do |f| %>
<%= f.fields_for :params, #search.params do |p| %>
<%= p.select "property_id", [[ "All", 0 ]] + PropertyType.all.collect { |pt| [ pt.value, pt.id ] } %>
<%= p.text_field :min_square_footage, :size => 10, :placeholder => "Min" %>
<%= p.text_field :max_square_footage, :size => 10, :placeholder => "Max" %>
<% end %>
<% end %>
except that the form fields aren't populated when the form is rendered. when the form is submitted the values come through just fine and i can do:
#search = Search.new(params[:search])
so its "half" working...

Resources