Why would one use reject_if if you can use validation? - ruby-on-rails

Is there any reason you would use reject_if and do something like this?
class User < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: proc do |attributes|
attributes['title'].blank?
end
end
Instead of using validation on the Post model?
class Post < ActiveRecord::Base
belongs_to :user
validates :title, presence: true
end

If you use validation, the creation of User will fail if there exists a Post which does not have a title.
If you use reject_if, the creation of User will succeed even if some, or all, of the Posts don't have titles. Only those posts without titles won't be saved.

Related

Trouble with validation check for has_many :through association; how to create?

Rails newbie here. I am having trouble with ensuring the presence of at least one "has_many :through" relationship in my users model.
I have:
class User < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
#validates :company_users, presence: true
end
class Company < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
end
class CompanyUser < ApplicationRecord
belongs_to :user
belongs_to :company
end
I got the commented validates line from the excellent answers at:
Validate that an object has one or more associated objects
Rails 3: validate presence of at least one has many through association item
However when I implement this line, then doing the following in rails console:
c = Company.create
c.users.create!(email: "test#example.com")
gives
`raise_validation_error': Validation failed: Company users can't be blank (ActiveRecord::RecordInvalid)
From my newbie perspective, it seems like the HMT entry isn't created until after the user is created, which creates a validation error preventing the user in the first place! It's probably a very simple error, but how do I get the above behaviour to work with that validation in place?
I have tried setting inverse_of in a couple place, without any success.
And before it's mentioned, I'm purposefully using HMT instead of HABTM because I have additional attributes on the company_users model that will be set.
One option would be to change the validation to
validates :companies, presence: true
and then create the user with
User.create(companies: [Company.create])
Edit:
Adding inverse_of to the CompanyUser model should also work. The validation would be left as validate :company_users, presence: true:
class CompanyUser
belongs_to :user, inverse_of: :company_users
belongs_to :company
end

nested form associated model getting autosaved

My associated model gets autosaved. I would like this to happen only when those fields contains any values. Obviously reject_if is the way to go for this but I cannot get it to work. I've tried :new_record? together with passing in the Proc. But I suspect something else is wrong here as the reject_posts method below does not get called.
has_one :reservation, as: :reservable, autosave: false,
class_name: Booking::Reservation
accepts_nested_attributes_for :reservation, reject_if: :reject_posts
def reject_posts(attributed)
binding.pry
end
How would I go about to try to debug this?
How would I go about to try to debug this?
The first step is to make sure your flow is invoking the accepts_nested_attributes_for method.
The simple check is to determine if your request console log sends the parameters as...
parameters: {
"model" => {
"reservation_attributes" => {...}
}
}
If your params don't feature _attributes, your fields_for or flow are incorrect.
--
You should also make sure your class_name option passes a string:
has_one :reservation, as: :reservable, class_name: "Booking::Reservation"
I would personally do the following:
#app/models/model.rb
class Model < ActiveRecord::Base
has_one :reservation, as: :reservable, class_name: "Booking::Reservation", inverse_of: :model
accepts_nested_attributes_for :reservation, reject_if: :all_blank?
end
The "rejection" is not validation. It just prevents the associated object from being passed to the nested model...
You may also set a :reject_if proc to silently ignore any new record hashes if they fail to pass your criteria
I'd use validations on the other model (with inverse_of):
#app/models/reservation.rb
class Reservation < ActiveRecord::Base
belongs_to :model, inverse_of: :reservation
validates :x, :y, :z, presence: true
end
--
If you wanted to reject based on specific attributes being blank, you should use:
#app/models/model.rb
class Model < ActiveRecord::Base
has_one :reservation, as: :reservable, class_name: "Booking::Reservation"
accepts_nested_attributes_for :reservation, reject_if: proc { |a| a['name'].blank? || a['title'].blank? }
end

How to validate presence of associated fields in Ruby on Rails

I have tables applications and business.
Applications
has_many :business
Business
belongs_to :applications
If I will create an Application, I should have at least one Business. I used link_to_add in the same form where I create applications.
I used validate_association :applciations but it didn't work.
You may be better to use validates_associated:
#app/models/business.rb
class Business < ActiveRecord::Base
belongs_to :application
validates :name, :other, param, presence: true
end
#app/models/application.rb
class Application < ActiveRecord::Base
has_many :businesses
validates_associated :businesses
end
This gives you the ability to check the validity of any associated data you pass through the model. However, this will not determine if there is at least one associated business passed through your model.
--
Numerical Validation
You'll want to look at the following
The reject_if method in Rails will not give you the ability to check the number of associated items which have been sent. This will have to be custom coded, which the author of the above post has addressed (in 2012) by setting a custom constant:
#app/models/company.rb
class Company < ActiveRecord::Base
OFFICES_COUNT_MIN = 1
validates :name, presence: true
validate do
check_offices_number
end
has_many :offices
accepts_nested_attributes_for :offices, allow_destroy: true
private
def offices_count_valid?
offices.count >= OFFICES_COUNT_MIN
end
def check_offices_number
unless offices_count_valid?
errors.add(:base, :offices_too_short, :count => OFFICES_COUNT_MIN)
end
end
end
I have not tested this myself, but to explain how it works, you'll basically have a custom validator, which allows you to check whether the number of associated objects is less than or equal to the CONSTANT you assign in the class.
You can, of course, achieve this without a constant, but the above example should demonstrate how you're able to create the functionality where at least 1 associated item should be sent
Not sure if this is what you meant but you could do the following in your models e.g.
has_many :businesses, dependent: :destroy
validates :title, presence: true
and in the other model:
belongs_to :application
validates :name, presence: true
You can use validates_presence_of
Validates that the specified attributes are not blank (as defined by Object#blank?), and, if the attribute is an association, that the associated object is not marked for destruction. Happens by default on save.
Example:
Applications
has_many :businesses
validates_presence_of :business
Business
belongs_to :applications
Update:
I think you will be better to using accepts_nested_attributes_for
Nested attributes allow you to save attributes on associated records through the parent. By default nested attribute updating is turned off and you can enable it using the accepts_nested_attributes_for class method. When you enable nested attributes an attribute writer is defined on the model.
app/models/application.rb
Class Application < ActiveRecord::Base
has_many :businesses
accepts_nested_attributes_for :businesses, reject_if: :all_blank
end
#app/models/business.rb
Class Business < ActiveRecord::Base
belongs_to :application
end
This will give you the ability to call the reject_if: :all_blank method -
:reject_if
Allows you to specify a Proc or a Symbol pointing to a method that checks whether a record should be built for a certain attribute hash. The hash is passed to the supplied Proc or the method and it should return either true or false. When no :reject_if is specified, a record will be built for all attribute hashes that do not have a _destroy value that evaluates to true. Passing :all_blank instead of a Proc will create a proc that will reject a record where all the attributes are blank excluding any value for _destroy.

Rails STI validation should have one

How do I do a (I think a custom) validation to determine that my model should have one of either of my STI models?
My models are like this:
class Account < ActiveRecord::Base
has_many :users
has_one :admin, class_name: Admin, dependent: :destroy
has_many :members, class_name: Member, dependent: :destroy
accepts_nested_attributes_for :admin, reject_if: proc { |attributes| attributes['name'].blank? }
accepts_nested_attributes_for :members, reject_if: proc { |attributes| attributes['name'].blank? }
# Validate should have one of either a member or a user
# validates :users, ...
end
class User < ActiveRecord::Base
end
class Admin < User
end
class Member < User
end
I want to validate, when an account is created it should have one admin or at least one member.
I can provide more information if needed.
Thanks!
You might want to add an error if both of them are not present by using a custom validation.
class Account < ActiveRecord::Base
validate :require_at_least_one_user
def require_at_least_one_user
errors.add(:user, "At least one user is required.") if self.admin.blank? && self.members.blank?
end
end
I would validate the presence of at least one user, regardless of its type.
Maybe this can help.
Good Luck.

Validate presence of foreign key on associated objects and using collection.build

I'm having the following models:
class Price < ActiveRecord::Base
belongs_to :article, inverse_of: :prices
validates :article_id, presence: true
end
class Article < ActiveRecord::Base
has_many :prices, dependent: :destroy, inverse_of: :article
end
The code when creating them raises validation error when saving (Prices is invalid):
article = Article.new
article.prices.build( { amount: 55.0 } )
article.save! #=> Validation failed: Prices is invalid
So Rails isn't smart enough to save the parent object (Article) before the child objects (Prices) so article_id can be assigned to the price before it is saved.
How do you use validations on the foreign key when using the build function?
It seems like a pretty standard scenario that should work?
(I know you can use database level constraints, but I'm asking about application level validations here)
In Rails way you can do like this
class Article < ActiveRecord::Base
has_many :prices, dependent: :destroy, inverse_of: :article
validates_associated :prices
end
but this is not 100% solution to this.
You can try this gem https://github.com/perfectline/validates_existence

Resources