Rails validation for has_one relation - ruby-on-rails

I have two model like:
class Employee
field :name
field :login, type: Boolean
has_one :user
end
class User
field :username
field :email
belongs_to :employee
validates_presence_of :username
end
I want to create an user account when create employee if check box of login field is checked. For this my new action of employees controller is:
def index
#employee = Employee.new
#employee.build_user
end
For this my form code is:
<%= simple_form_for(#employee) %>
<%= f.input :login, :as => :boolean, :label => "Create User" %>
<div class="create-user" style="display: none">
<%= f.simple_fields_for :user do |u| %>
<%= render 'user_fields', {f: u} %>
<% end %>
</div>
<button class="btn btn-info">Save Change</button>
<% end %>
and the _user_fields.html.erb is:
<%= f.input :username %>
I want to validate the user model when check_box of :login field is checked. On unchecked condition the form should be submit. What is the better solution.

Maybe you can move your user validation into employer model ?
something like code below should solve your problems
validates_presence_of :username, :if => login?
What do you think?

maybe use validates_associated:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#validates_associated
or delegation + validates presence might also work
delegate :username, to: :user, allow_nil: true
validates :username, presence: true, if: :login

Finally I got its solution:
class Employee
field :name
field :login, type: Boolean
has_one :user
has_one :user, :class_name => "User", :dependent => :destroy
accepts_nested_attributes_for :user, :reject_if => :login_blank
def login_blank
return false if self.login == true
return true if self.login == false
end
end

Related

form validation with nested models in Rails

I have this problem. I need to validate the attributes of two models in the same form in Rails. One is the parent of the other.
The form is like this:
<%= semantic_form_for #professional do |pro| %>
<%= pro.inputs :id => "information" do %>
<%= pro.input :name, label: t("Artistic Name") %>
<%= pro.semantic_fields_for #user do |user| %>
<%= user.inputs :id => "register" do %>
<%= user.input :email, :placeholder=>"email#example.com" %>
<%= user.input :password, label: t('Password') %>
<%end%>
<% end %>
<% end %>
<% end %>
The models I am using are like this:
User:
class User < ActiveRecord::Base
belongs_to :role, polymorphic: true
validates :email, :password, presence: true
end
Professionals:
class Professional < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy
accepts_nested_attributes_for :user
validates :date_birthday, :gender, :height, :name, :description, :Weight, :address, :languages,:services, :category, :phonenumber, :fullname, :hair_color, :age, :orientation, presence: true
end
So, what is the problem?
When I clicked in the submit button the professional attributes are marked but not the users attributes.
Like this:
The fields marked in red belongs to the professional model but the fields email and password belongs to the user model aren't marked in red when it should be because they are empty.
What can i do? I need the warning message for the user is attributes too
Thanks in advances.
We've achieved what you need before.
We had to use inverse_of so that the object was a singular piece of data (rather than multiple pieces as is the case by default):
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role, polymorphic: true, inverse_of: :user
validates :email, :password, presence: true
end
#app/models/professional.rb
class Professional < ActiveRecord::Base
has_one :user, as: :role, dependent: :destroy, inverse_of: :role
accepts_nested_attributes_for :user
end
This will help.
You also need to make sure you're passing these objects correctly (I see so many people not doing this).
You need to tell Professional to validate the associated User:
class Professional < ActiveRecord::Base
...
validates_associated :user

ActiveAdmin form: multiple nested forms

I have a structure User-> Profile-> Image and want to use one form for editing and recording in ActiveAdmin.
I use accepts_nested_attributes_forin models:
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy;
accepts_nested_attributes_for :profile, allow_destroy: true
end
class Profile < ActiveRecord::Base
belongs_to :image, dependent: :destroy;
accepts_nested_attributes_for :image,:reject_if => proc { |attributes| !attributes['img'].present? }, :allow_destroy => true
end
class Image < ActiveRecord::Base
has_attached_file :img
validates_attachment_content_type :img, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
end
And such permit_params in ActiveAdmin.register User:
permit_params do
permitted=[:id,:login, :email, :admin, :password, :password_confirmation, :ip_address];
permitted.append(profile_attributes:[:name,:second_name,:middle_name,:img,:mobile_phone,:country, :city,:region, image_attributes:[:img]]);
permitted
end
Finally, the code itself forms
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "User Details" do
f.input :login
f.input :email
f.input :password
f.input :password_confirmation
end
f.inputs "Profile", for: [:profile, f.object.profile || f.object.build_profile] do |pf|
pf.input :name
pf.input :second_name
pf.input :middle_name
pf.input :mobile_phone, :as => :phone
pf.input :country, selected: "RU"
pf.input :city
pf.input :region
pf.inputs "Avatar", for:[:image, pf.object.image || pf.object.build_image] do |fpf|
fpf.input :img, :as => :file
end
end
f.inputs "User Perference" do
f.input :admin, type: :boolean
end
f.actions
end
Unfortunately, this code does not work: the form is correctly displayed with Profile and work, but the form is not visible to the Image. How can I fix this?
Unfortunately, I have not found the right solution for this problem, and it is still relevant (I'm not going to close this question, just in case someday it will answer).
But I modeled the behavior close to the desired one.
So, to the User model I added Avatar-interface for the respective object.
def avatar
if self.profile && self.profile.image
self.profile.image.img
else
nil
end
end
def avatar=(arg)
unless self.profile.image
self.profile.create_image(img:arg)
else
self.profile.image.update_attributes(img:arg)
end
self.profile.image
end
def avatar?
if self.profile && self.profile.image
!self.profile.image.img.nil?
else
nil
end
end
In ActiveAdmin added to the avatar permit_params for User.
permit_params ..., :avatar
Finally, in the form of added file fields:
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "User Details" do
#Fields for User
end
f.inputs "profile", for: [:profile, f.object.profile || f.object.build_profile] do |pf|
#Fields for profile
end
f.inputs "Avatar" do
f.input :avatar, as: :file
end
f.actions
end
It is worth noting that if you have observed abnormal behavior when the form is submitted, with empty values, then you should pay attention to the parameters for accepts_nested_attributes_for, in particular update_only and reject_if.
I'm still waiting for the normal solution for multiple nested forms in ActiveAdmin

How to update attributes in many to many relation in rails?

I am new in rails. So i am not comprehending much of it. I have a database with three table. students table, courses table and registrations table.
My Railde models are as follows :
class Student < ActiveRecord::Base
attr_accessible :name
has_many :registrations
has_many :courses, :through => :registrations
validates :name, :presence => true
end
class Course < ActiveRecord::Base
attr_accessible :name
has_many :registrations, :dependent => :destroy
has_many :students, :through => :registrations
validates :name , :presence => true
validates :name, :uniqueness => true
end
class Registration < ActiveRecord::Base
attr_accessible :course_id, :student_id
belongs_to :course
belongs_to :student
validates :course_id, :student_id, :presence => true
validates :course_id, :uniqueness => {:scope => :student_id}
validates :student_id, :uniqueness => {:scope => :course_id}
end
......................
Controller action for updating student :
def update
#student = Student.find(params[:id])
if #student.update_attributes(params[:student])
redirect_to students_path, :notice => "Registration completed"
else
render 'edit'
end
end
................
View :
<%=form_for #student do |f| %>
<p>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
</p>
<p>
<%= render('course_list') %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
...............
_course_list partial :
Select Courses :<br/>
<p>
<% Course.all.each do |course| %>
<%=check_box_tag "student[course_ids][]", course.id, `enter code here`#student.course_ids.include?(course.id) %>
<%= course.name %> <br/>
<% end %>
</p>
.............................
when i submit the update button, i got an error
Can't mass-assign protected attributes: course_ids
.......
parameters :
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"j/lDE5bv1gWkfadQ6Cag6hGjg5nD2Ikad9vHOJTE7Pc=",
"student"=>{"name"=>"Galib",
"course_ids"=>["2",
"3"]},
"commit"=>"Update Student",
"id"=>"6"}
.........................
What i want to do is, if update button is clicked, both students table and registrations table need to be updated. Please help.
In models define the associations
class Student < ActiveRecord::Base
attr_accessible :name
has_many :registrations
has_many :courses, :through => :registrations
validates :name, :presence => true
accepts_nested_attributes_for :courses
attr_accessible :course_ids
end
class Course < ActiveRecord::Base
attr_accessible :name
has_many :registrations, :dependent => :destroy
has_many :students, :through => :registrations
validates :name , :presence => true
validates :name, :uniqueness => true
accepts_nested_attributes_for :students
end
class Registration < ActiveRecord::Base
attr_accessible :course_id, :student_id
belongs_to :course
belongs_to :student
validates :course_id, :student_id, :presence => true
validates :course_id, :uniqueness => {:scope => :student_id}
validates :student_id, :uniqueness => {:scope => :course_id}
accepts_nested_attributes_for :course
end
In Controller
def update
#student = Student.find(params[:id])
if #student.update_attributes(params[:student])
#student.courses.build
redirect_to students_path, :notice => "Registration completed"
else
render 'edit'
end
end
It may help you
The error is telling you that you have a permission error - course_ids can't be posted to from a form. More specifically, you have attr_accessible :name in the Student model which means that is the only attribute that can be saved when you save a Student record using a form (attr_accessible dictates what can be mass assigned).
Try changing your Student model to:
attr_accessible :name, :registrations_attributes
accepts_nested_attributes_for :registrations
You are trying to created a nested model form, so accepts_nested_attributes_for is used for this.
If you want to have the Registrations table updated, as well, you're going to have to tell the partial so that it knows that the partial is updating a different model than Student.
<%= f.fields_for :registrations do |registration| %>
<%= render('course_list') %>
<% end %>
In case of MongoDB, For implementing many to many in rails model you have to use has_and_belongs_to_many instead of simple has_many

Ruby on Rails: How to validate nested attributes on certain condition?

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.

Rails nested form with has_many :through, not saving the data to joining table

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?

Resources