If you want to go straight to the question, just go to the last paragraph.
A Pack has many items included, Item is polymorphic and one of the linked tables is Access (so Access is an Item that can be added to the Pack)
Here you are the models and controller.
class Pack < ActiveRecord::Base
has_many :pack_items, dependent: :destroy
has_many :items_included, through: :pack_items, source: :item
accepts_nested_attributes_for :pack_items, allow_destroy: true
validate :valid_max_value, if: :infinite_item?
end
class Item < ActiveRecord::Base
has_many :pack_items, dependent: :restrict_with_error
has_many :packs, through: :pack_items
end
class Access < ActiveRecord::Base
has_one :item, as: :itemable, dependent: :destroy
has_one :entitlement, as: :entitlementable, dependent: :destroy
accepts_nested_attributes_for :item, allow_destroy: true
accepts_nested_attributes_for :entitlement, allow_destroy: true
validate :valid_max_value, if: :infinite?
private
def infinite?
entitlement.infinite
end
end
class PacksController < BaseController
def update
#pack = Pack.find(params[:id])
if #pack.update(permitted_params)
...
end
end
private
def permitted_params
params.require(:pack).permit(item_attributes: [:id, :name, :max_purchasable],
pack_items_attributes: [:id, :item_id, :amount, :_destroy])
end
end
There is an importan validation in pack "valid_max_value. If a pack has an infinite Access inside, the max_value of the Pack should never be higher than 1.
It works perfectly when I create a pack and I add some Accesses, but the problem is this:
I have a Pack with two Items. An Access that's infinite and a common Access(not infinite). So the Pack's max_value should be 1 because it has an infinite Access inside.
Now I edit that Pack and I delete the infinite Access, so now I can select a higher max_value, 5 in example, because the pack doesn't have an Access with restriction inside.
When I click update there is a rollback because the valid_max_value validation runs before the deletion of the infinite Access, so it says the max_value is invalid because the validation depends on a child field.
In short, my question is: How can I delete the nested items before run the parent validation?
You do not have to actually delete the items before validation, check marked_for_destruction? instead in your validations, so that items that are to be deleted will be ignored
Related
I have a Resume Model that has_many :skills, and my skills model that belongs_to :resume.
I am nesting the skills form inside the resume form and it's creating the record and relationship perfectly. But when I try to destroy the resume, the associated Skill is not being destroyed along with it.
Here are my models:
# Resume.rb
class Resume < ActiveRecord::Base
has_many :skills
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
# Skill.rb
class Skill < ActiveRecord::Base
belongs_to :resume
end
Here's the strong params in the resume_controller.rb
def resume_params
params.require(:resume).permit(:user_id, :title, :summary, :job_title, skills_attributes [:skill_name, :_destroy])
end
As far as I can tell I am passing the _destroy key properly. I noticed some people had _destroy checkboxes in the form. I want the Skills to be deleted when I Destroy the entire resume. Thanks!
All you have specified is that you can destroy skills on a resume like with the checkboxes you mention seeing in some examples. If you want it to destroy all skills associated when a resume is destroyed you have adjust your has_many declaration.
has_many :skills, dependent: :destroy
Add :dependent => :destroy in your Resume model like below:
class Resume < ActiveRecord::Base
has_many :skills, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
:dependent Controls what happens to the associated objects when their owner is destroyed:
:destroy causes all the associated objects to also be destroyed
:delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
:nullify causes the foreign keys to be set to NULL. Callbacks are not executed.
:restrict_with_exception causes an exception to be raised if there are any associated records
:restrict_with_error causes an error to be added to the owner if there are any associated objects
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.
I have a model and a has_many association.
Say,
class Invoice < ActiveRecord::Base
has_many :lines, class_name: 'InvoiceLine', inverse_of: :invoice, dependent: :destroy
accepts_nested_attributes_for :lines, reject_if: :invalid_line, allow_destroy: true
end
Is it possible for me to iterate through the lines before it is saved (especially during an update), either in InvoicesController or in Invoice model?
When I iterate on the existing record during an update, I get old lines, not the updated lines from the view.
Currently I am doing the following in the controller. I am not quite happy with this.
total = params["invoice"]["lines_attributes"].
map{|k,v| [v["amount"], v["_destroy"]]}.
select{|x| x[1] == "false"}.
map{|x| x[0].to_f}.
inject {|total, amount| total + amount}
You can use assign_attributes and marked_for_destruction? as:
#invoice.assign_attributes(params[:invoice])
total = #invoice.lines.select(&:marked_for_destruction?).map(&:amount).sum
#invoice.save #save all on db
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
How can I delete nested objects in a form? I found out that I need to add :allow_destroy in the parent model at the accepts_nested_attributes_for directive.
Further, I want to restrict the deletion. A nested object only should be deleted, if the parent object is the only one that retains the association.
Example:
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships
end
Explanation: A company can host many internships. Therefore, I do not want to delete the company record as long as there is at least one other internship associated with it.
You could use dependent => :destroy
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships, :dependent => :destroy
end
If you return false in a before_destroy filter, then the destroy action will be blocked. So we can check to see if there are any internships associated to the company, and block it if so. This is done in the company model.
class Company < ActiveRecord::Base
has_many :internships
before_destroy :ensure_no_internships
private
def ensure_no_internships
return false if self.internships.count > 0
end
end