I am trying to use the nested_form gem to create a dropdown that will show a list of chains and their locations. When the user clicks the submit button, the nested_form should create a new entry in a joiner table called users_locations. However, the form spits out an error as follows:
User locations user can't be blank
In other words, its complaining that the user_id is NULL and therefore it cannot create an entry in the table (because user_id is a required field in the user_locations table).
_form.html.erb (In the User view folder)
<div class="field" id="location_toggle">
<%= f.label "Location:", :class => "form_labels", required: true %>
<%= f.fields_for :user_locations do |location| %>
<%= location.grouped_collection_select :location_id, Chain.all, :locations, :name, :id, :name, include_blank: false %>
<% end %>
<%= f.link_to_add "Add a Location", :user_locations, :class => "btn btn-primary btn-small" %>
<br/>
</div>
user.rb (model)
belongs_to :chain
has_many :user_locations
has_many :locations, :through => :user_locations
...
accepts_nested_attributes_for :user_locations, :reject_if => :all_blank, :allow_destroy => true
validates_associated :user_locations, :message => ": you have duplicate entries."
...
attr_accessible :first_name, :last_name, ... , :user_locations_attributes
users_controller.rb
def create
#user = User.new(params[:user])
#user.save ? (redirect_to users_path, notice: 'User was successfully created.') : (render action: "new")
end
Log shows:
SELECT 1 AS one FROM "user_locations" WHERE ("user_locations"."user_id" IS NULL AND "user_locations"."location_id" = 1)
I'm assuming you're user_location validates :user_id, presence: true or something, right?
I had a look at this recently in response to this question, it seems that, when creating nested models, validations for all objects to be created are run before any of them are saved, and thus even though your model will get have user_id set when it is saved, it cannot have it when validated.
To get around this you may want to disable the validation on creation, for example with:
# user_locations.rb
validates :user_id, presence: true, on: :update
It looks like you're blending simple_form with the default Rails form helpers and getting some odd behavior. I haven't worked with simple_form, but my guess is that you can fix your problem by changing f.fields_for to f.simple_fields_for.
Here's an example that may help.
Related
I have a site that keeps track of SAT tutoring sessions. The curriculum that the students learn is a collection of rules. I have a model for each tutoring session called "Sittings" and the rules model is called "Rules". I want the site admin to be able to enter a Sitting by date, and then use checkboxes to select which "rules" the student got wrong in that sitting. I'm a little confused as to how I can create the form to pull out specific rules without adding attributes to my Sitting model of rule1, rule2, etc. I'm using simple_form to create my forms.
My Sitting model:
class Sitting < ActiveRecord::Base
attr_accessible :date, :comment, :rule_id, :user_id
validates :date, presence: true
belongs_to :user
has_many :combos
has_many :rules, :through => :combos
end
My Rules model:
class Rule < ActiveRecord::Base
attr_accessible :name, :subject, :session_id, :hint_id, :question_id, :trigger_id
validates :name, presence: true
validates :subject, presence: true
has_many :questions
has_many :triggers
has_many :hints
has_many :combos
has_many :sittings, :through => :combos
end
My Combo model:
class Combo < ActiveRecord::Base
belongs_to :sitting
belongs_to :rule
end
Edit:
Here's what I have tried for the form. It does create the checkbox form, but my DB isn't updating the rule_id. (shows as nil when I create a Sitting)
form:
<%= simple_form_for(#sitting, html: { class: "form-horizontal"}) do |f| %>
<%= f.error_notification %>
<% Rule.all.each do |rule| %>
<%= check_box_tag "sitting[rule_ids][]", rule.id, #sitting.rule_ids.include?(rule.id) %> <%= rule.id %>
<% end %>
<div class="form-group">
<%= f.input :comment, as: :text, input_html: { rows: "2", :class => "form-control" }, label: "Comments:" %>
</div>
<div class="form-group">
<%= f.date_select :date, as: :date, label: "Taken Date:" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I updated my strong params to allow an array:
def create
#sitting = Sitting.new(sitting_params)
respond_to do |format|
if #sitting.save
format.html { redirect_to #sitting, notice: 'Sitting was successfully created.' }
format.json { render action: 'show', status: :created, location: #sitting }
else
format.html { render action: 'new' }
format.json { render json: #sitting.errors, status: :unprocessable_entity }
end
end
end
def sitting_params
params.require(:sitting).permit(:comment, :date, :user_id, :rule_id => [])
end
Am I missing something in order to update the Sitting.rule_id properly? I get the following error in my logs:
WARNING: Can't mass-assign protected attributes for Sitting: rule_ids
app/controllers/sittings_controller.rb:27:in `create'
Just to summarize what we got through over the chat.
Firstly, you do not need both attr_accessible and strong_params at the same time. I've posted another answer some time ago explaining how those two approaches differs from each other.
You're running rails 4, so you should take advantage of strong params and not use protected_attributes gem. In short, remove this gem from you Gemfile as well as all attr_accessible calls.
As Marian noticed, you have a typo in your strong params method, you need to permit rule_ids, not rule_id. rule_id column is obsolete, as sitting has_many :rules :through rather than sitting belongs_to :rule - most likely it is an artifact of old association code.
As soon as rule_ids are assigned within your model, it will create new join models in your join table, creating an association between given sitting and passed rules.
I have two models, User and House. They have one-to-one association.
class User < ActiveRecord::Base
attr_accessible :name, :house_attributes
has_one :house, :dependent => :destroy
validates :name, :presence => true
accepts_nested_attributes_for :house, allow_destroy: true, :reject_if => lambda { |a| a['desc'].blank? }
end
class House < ActiveRecord::Base
attr_accessible :desc, :price
belongs_to :user
validates :desc, :presence => true
end
Now I created a nested form inside of User new view like this:
<%= simple_form_for(#user) do |f| %>
<%= f.input :name %>
<%= f_builder.simple_fields_for :house, #house do |h| %>
<%= h.input :price %>
<%= h.input :desc %>
<% end %>
<%= f.button :submit %>
<% end %>
And the new controller is like this
def new
#user = User.new
#house = #user.build_house
respond_to do |format|
format.html # new.html.erb
end
end
I want to always create a house at the same time that user is created. So if house fails validation, it should not create user. (Now the house model has only one validation, which is :desc field needs to be present.)
The code in models, only guarantee house is not created, if :desc is blank. But it will still create the user.
I tried to add custom validation inside of User model, but can not find a way to access :desc attribute (failed to call self.desc) or :house_attributes (self.house_attributes) inside of User model. I really don't know where does rails store these house attributes before a house is created.
I hope you guys can help me figure out a nice and clear way to
1) Be able to validate and show error message for :desc attribute. Right now errors message will only show up for :name field of User model.
2) Do not save user if validation for either user or house is failed.
3) If any of validation failed, render :new
Thanks
You should add validates_associated :house to your User model. This will run the House validations and ensure they succeed before considering the user to be valid.
I have a Subject Model and a Lesson Model.
I implemented a nested model form.
After subject creation, I led it to a page where it supposedly shows the lessons associated. However, I fail to see the lessons.
I believe the data for lessons did not get saved properly as
When I did a for example lesson.find_by_subject_id('1'), I get 'nil' in return.
I am trying to figure out how polymorphism works on rails and would appreciate it if someone could either point out where I've gone wrong or give me some guidance on how those to pass the values for belong_to classes to be created.
Subject Model
attr_accessible :subjectCode, :lessons_attributes
has_many :lessons, :dependent => :destroy
accepts_nested_attributes_for :lessons, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
Lesson Model
attr_accessible :lessonName, :subject, :subject_id
belongs_to :subject
Subject Controller
def new
3.times {#subject.lessons.build}
end
def create
#subject = Subject.new(params[:subject])
if #subject.save
redirect_to #subject, :notice => "Successfully created subject."
else
render :action => 'new'
end
end
Form
<%= form_for #subject do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :subjectCode %><br />
<%= f.text_field :subjectCode %>
</p>
<%= f.fields_for :lessons do |builder| %>
<p>
<%= builder.label :lessonName %> <br/>
<%= builder.text_area :lessonName, :rows=>3 %>
</p>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
Routes
resources :subjects do resources :lessons end
Your reject_if lambda will always reject the lessons attributes because lessons don't have a content attribute, so you're essentially evaluating nil.blank? which will return true
Perhaps you want to check if the lesson name is blank? Ala :reject_if => lambda { |a| a[:lessonName].blank? }
You don't have a field for content of lesson on form, so content will be blank with every lesson. And you also use:
:reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
This will check if content is blank, the subject will not save the lesson. This is your problem, because you don't have a content field on your form, so content will blank every time you create subject, you used :reject_if so subject will not save its lesson. If you wan user can put content of lesson later, remove :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true and your lesson will be save with associated subject.
I have these models:
class Organisation < ActiveRecord::Base
has_many :people
has_one :address, :as => :addressable,
:dependent => :destroy
accepts_nested_attributes_for :address, :allow_destroy => true
end
class Person < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :organisation_id, :address_attributes
belongs_to :user
belongs_to :organisation
has_one :address, :as => :addressable,
:dependent => :destroy
accepts_nested_attributes_for :address, :allow_destroy => true
# These two methods seem to have no effect at all!
validates_presence_of :organisation, :unless => "address.present?"
validates_associated :address, :unless => "organisation.present?"
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
validates_presence_of :line1, :line2, :city, :zip
end
...and these views:
_fields.html.erb:
<%= render 'shared/error_messages', :object => f.object %>
<fieldset>
<div class="left">
<%= f.label :first_name %><br/>
<%= f.text_field :first_name %>
</div>
<div>
<%= f.label :last_name %><br/>
<%= f.text_field :last_name %>
</div>
<div>
<%= f.label :email %><br/>
<%= f.text_field :email %>
</div>
<div>
<%= f.label :organisation_id %><br/>
<%= f.select(:organisation_id, current_user.organisation_names, {:include_blank => "--- None ---"}, :id => 'organisation_select') %>
</div>
</fieldset>
<%= f.fields_for :address do |address| %>
<%= render 'shared/address', :f => address %>
<% end %>
_address.html.erb:
<fieldset id="address_fields">
<div>
<%= f.label :line1 %>
<%= f.text_field :line1 %>
</div>
<div>
<%= f.label :line2 %>
<%= f.text_field :line2 %>
</div>
<div>
<%= f.label :zip %>
<%= f.text_field :zip %>
</div>
<div>
<%= f.label :city %>
<%= f.text_field :city %>
</div>
</fieldset>
people_controller.rb:
def new
puts params.inspect
#person = Person.new(:organisation_id => params[:organisation_id])
#person.build_address
#title = "New person"
end
{"action"=>"new", "controller"=>"people"}
def edit
puts params.inspect
#title = #person.name
end
{"action"=>"edit", "id"=>"69", "controller"=>"people"}
def create
puts params.inspect
if params[:organisation_id]
#person = current_user.organisations.build_person(params[:person])
else
#person = current_user.people.build(params[:person])
end
if #person.save
flash[:success] = "Person created."
redirect_to people_path
else
render :action => "new"
end
end
{"commit"=>"Create", "action"=>"create", "person"=>{"last_name"=>"Doe", "organisation_id"=>"9", "email"=>"john.doe#email.com", "first_name"=>"John", "address_attributes"=>{"city"=>"Chicago", "zip"=>"12345", "line2"=>"Apt 1", "line1"=>"1 Main Street"}}, "authenticity_token"=>"Jp3XVLbA3X1SOigPezYFfEol0FGjcMHRTy6jQeM1OuI=", "controller"=>"people", "utf8"=>"✓"}
Inside my Person model I need to make sure that only if a person's organisation_id is blank, that person's address fields have to be present.
I tried something like this:
validates :address, :presence => true, :if => "organisation_id.blank?"
But it's not working.
How can this be done?
Thanks for any help.
First of all, I want to be sure that you mean blank? rather than present?. Typically, I see this:
validate :address, :presence_of => true, :if => 'organisation.present?'
Meaning, you only want to validate address if organisation is also present.
Regarding, :accepts_nested_attributes_for, are you using this feature by passing in nested form attributes, or some such thing? I just want to make sure you absolutely need to use this functionality. If you are not actually dealing with nested form attributes, you can implement cascading validation using:
validates_associated :address
If you do need to use :accepts_nested_attributes, be sure to check out the :reject_if parameter. Basically, you can reject adding an attribute (and it's descendants) altogether if certain conditions apply:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :no_organisation
def no_organisation(attributes)
attributes[:organisation_id].blank?
end
Now, if none of the above apply, let's take a look at your syntax:
It should work, :if/:unless take symbols, strings and procs. You don't need to point to the foreign_key, but can simplify by pointing to:
:if => "organisation.blank?"
You have other validations in the Address model, correct? Is Address being validated when you don't want it to? Or is Address not being validated? I can help you test it out in the console if you can give me some additional details.
To make things easier for myself re: mass-assignment, I changed the rails config: config.active_record.whitelist_attributes = false
I created a gist for you to follow along
I have a sample project as well. Let me know if you are interested.
Basic points:
Added the following to Person to ensure that either Org or Address are valid:
validates_presence_of :organisation, :unless => "address.present?"
validates_associated :address, :unless => "organisation.present?"
Added validation to Address to trigger errors when Org is not present:
validates_presence_of :line1, :line2, :city, :zip
I was able to produce the requirements you are seeking. Please look at the gist I created where I have a full console test plan.
I added a controller file to the previous gist.
Overview:
All you should need to create the person is:
#person = current_user.people.build(params[:person])
:organisation_id will always be found off of the :person param node, like so:
params[:person][:organisation_id]
So you're if will never be true.
I updated the gist with the necessary changes to the controller, the model and the form.
Overview:
You need to cleanup your controller. You are using accepts_nested_attribute, so in the :create, you only care about params[:person]. Additionally, in the render :new, you need to setup any instance variables that the partial will use. This does NOT go back through the :new action. The :new and :edit actions also need to be simplified.
Your Person model needs to use the :reject_if argument because the Address fields are coming back to the :create action as :address_attributes => {:line1 => '', :line2 => '', etc}. you only want to create the association if any have values. Then your validates_presence_of for :organisation will work just fine.
Your form needs to pass the organisation id to the controller, rather than the organisation names
It's all in the gist
Should be the final gist.
Overview:
Add the following to your edit action right after building the #person:
#person.build_address if #person.address.nil?
This ensure that you have the address inputs, even if the #person.address does not exist. It doesn't exist, because of the :reject_if condition on accepts_nested_attributes
I DRYed up the :reject_if as follows. It's a little hacky, but has some utility:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :attributes_blank?
def attributes_blank?(attrs)
attrs.except('id').values.all?(&:blank?)
end
a. attrs -> the result of params[:person][:address]
b. .except('id') -> return all key-values except for 'id'
c. .values -> return all values from a hash as an array
d. .all? -> do all elements in the array satisfy the following check
e. &:blank -> ruby shorthand for a block, like this: all?{ |v| v.blank? }
Are you sure you didn't mean:
validates :address, :presence => true, :if => organisation_id.nil?
A more simple approach might be to add a custom validator. It's super easy, and you don't have to stumble on syntax or try to figure out why Rails' magic isn't working.
Inside my Person model I need to make sure that only if a person's organisation_id is blank, that person's address fields have to be present.
class Person < ActiveRecord::Base
...
validate :address_if_organisation_id_is_present
private
def address_if_organisation_id_is_present
return true unless organisation_id
errors.add(:address, "cannot be blank") unless address
end
end
Adding to a model's errors will prevent it from saving. Note: you may wish to use address.blank? or address.empty? as discussed in other answers, but you can define this for the behavior you'd like.
I am kinda new to Rails and this is my first post to StackOverflow.
Say I have 3 models:
class Product < ActiveRecord::Base
default_scope :order => :title
has_many :line_items
has_many :promo_products
has_many :promotions, :through => :promo_products, :foreign_key => :promotion_id
before_destroy :ensure_not_referenced_by_any_line_item
before_destroy :ensure_not_referenced_by_any_promo_product
validates :title, :presence => true, :uniqueness => true
validates :description, :presence => true
validates :price, :numericality => {:greater_than_or_equal_to => 0.01}
private
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, 'Line Items present')
return false
end
end
def ensure_not_referenced_by_any_promo_product
if promo_products.empty?
return true
else
errors.add(:base, 'Some promotions are still in effect')
return false
end
end
end
class Promotion < ActiveRecord::Base
CART_OR_PRODUCT = ['Cart', 'Product']
PROMOTION_TYPE = ['Percentage based', 'Value based']
has_many :promo_products
accepts_nested_attributes_for :promo_products
has_many :products, :through => :promo_products, :foreign_key => :product_id
accepts_nested_attributes_for :products
#attr_accessible :promo_products_attributes, :title, :description, :cart_or_product, :promotion_type, :discount, :minimum_price, :minimum_quantity
validates :title, :description, :presence => true
validates :cart_or_product, :inclusion => {:in => CART_OR_PRODUCT, :message =>
"is invlaid. Please select a valid option"}
validates :promotion_type, :inclusion => {:in => PROMOTION_TYPE, :message =>
"is invalid. Please select a valid option"}
validates :discount, :minimum_price, :numericality => {:greater_than_or_equal_to => 0.00}
validates :minimum_quantity, :numericality => {:greater_than_or_equal_to => 0}
end
class PromoProduct < ActiveRecord::Base
belongs_to :promotion
belongs_to :product
accepts_nested_attributes_for :products
end
In the promotions new page, I would like to show list of products that could be part of a promotion. A user may select 0, 1 or more products, depending on the type of promotion.
In the action new of promotions_controller, I built like this:
#promotion.promo_products.build.build_product
In the _form of promotions, I needed to show the list of products for user to select. I made a nested form like:
<%= form_for(#promotion) do |f| %>
<!-- other promotion fields -->
<%= f.fields_for :promo_products do |pp| %>
<%= pp.fields_for :products do |p| %>
<div class="field">
<%= f.label "Products" %><br />
<%= collection_select :promo_product, :product_id, Product.all, :id, :title {:selected => #promotion.product_ids}, {:multiple => true} %>
</div>
<% end %>
<% end %>
<% end %>
I have 2 issues.
First my code throws an error:
ArgumentError in PromotionsController#new
No association found for name `products'. Has it been defined yet?
If I change the line in PromoProduct model:
accepts_nested_attributes_for :products
to
accepts_nested_attributes_for :product
Then there are no errors, and everything works fine.
The data doesn't get saved to promo_product table. I have the create action in promo_product controller as:
def create
#promotion = current_promotion
products = Product.select(:id => params[:product_id])
products.each do |p|
promo_product = #promotion.promo_products.build(p)
promo_product.save
end
##promo_product = PromoProduct.new(params[:promo_product])
redirect_to promotions_path
end
How can I go about it?
Thank you.
You shouldn't put the "accept_nested_attribute_for" in the association table PromoProducts. It should exist in the model that you want to use for creating association to another model. "accept_nested_attribute_for" IIRC simply inserts an "[association]_attributes=" method for your model. For instance, if you add this method to your Product class for Promotion, you will get "promotion_attributes=" method inserted in the Product class. Then a nested form can use this function to create new objects with a hash that represents the model and association.
Base on the above, the create action shouldn't be in PromoProduct controller, instead it should be in Promotion controller.
<%= form_for(#promotion) do |f| %>
<!-- other promotion fields -->
<%= f.fields_for :products do |pp| %>
<div class="field">
<%= f.label "Products" %><br />
<%= collection_select :promo_product, :product_id, Product.all, :id, :title {:selected => #promotion.product_ids}, {:multiple => true} %>
</div>
<% end %>
<% end %>
I don't know without trying if the above collection_select line is correct. But you can debug this by checking the parameter returned by the form to the controller in the server console log. Basically you should see a nested hash of
{:promotion => {:products => ...}}
Let me know if you need more help on this. In my solution I used a combination of select_tag and options_from_collection_for_select. (But I don't recall the behavior of all these offhand without looking at the API doc.)
Lastly, do you need the :through model? I think since you created the through model you need to handle saving that in your create action. But since you don't have other attributes on the PromoProducts table I wonder if you want to simply leave it as a HABTM association and let rails deal with the rest?