validate destruction of has_many association? - ruby-on-rails

Models:
class Factory < ActiveRecord::Base
has_many :factory_workers
has_many :workers, through: :factory_workers
end
class FactoryWorkers < ActiveRecord::Base
belongs_to :factory
belongs_to :worker
before_destroy :union_approves?
private
def union_approves?
errors.add(:proletariat, "is never destroyed!")
false
end
end
class Worker < ActiveRecord::Base
has_many :factory_workers
has_many :factorys, through: :factory_workers
end
If I attempt to update a Factory's list of Workers via Factory, and that leads to the destruction of some FactoryWorker associations, I hope that the before_destroy hook is called, but this does not seem to be the case.
Example
Factory.create(name: 'communist paradise', worker_ids: [1, 2])
Factory.find_by(name: 'commnist paradise').update(worker_ids: [1])
# before_destroy hook is not called, proletariat must riot!
How can I ensure the before_destory hook is called when updating a record's associations?

Found what I needed: ActiveRecord provides a before_remove method on associations, so I just need to rejigger as follows:
class Factory < ActiveRecord::Base
has_and_belongs_to_many :workers, before_remove: :union_approves?
private
def union_approves?
...
end
end

Related

Rails 5 model custom validation with relation has many

In my Rails 5.2 app, I have the following Model relations.
A Job has many Contracts and has many Expertises. Resulting in the following Ruby on Rails models.
job.rb
class Job < ApplicationRecord
has_many :attached_expertises, as: :expertisable, dependent: :destroy
has_many :expertises, through: :attached_expertises
has_many :attached_contracts, as: :contractable, dependent: :destroy
has_many :contracts, through: :attached_contracts
end
contract.rb
class Contract < ApplicationRecord
has_many :attached_contracts, dependent: :destroy
end
attached_contract.rb
class AttachedContract < ApplicationRecord
belongs_to :contract
belongs_to :contractable, polymorphic: true
end
expertise.rb
class Expertise < ApplicationRecord
has_many :attached_expertises
end
attached_expertise.rb
class AttachedExpertise < ApplicationRecord
belongs_to :expertise
belongs_to :expertisable, polymorphic: true
end
I need to perform a validation in Job depending in the values of the associated models.
I have 3 types of Contracts and 3 types of Expertises as well.
Lets say contracts can be P, C or E, and Expertises J, L, S.
When creating or updating a Job if Contract of type E is selected, Expertise must be of type J, otherwise if should raise an error.
I have tried this creating custom validation ActiveModel::EachValidator. But was not able to get it to work. Neither with a custom method in the model it self.
Whats is the best way to achieve this?
And where should I place the validator file? In app/models/concerns/?
You can use validates_with and write a custom method to do the checking
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end

How cause association callback in rails models

How to track in the models this command
=> order = Order.create
=> order.items << Item.first // this command
if i have such models:
class Order < ApplicationRecord
has_many :order_items
has_many :items, through: :order_items
end
class Item < ApplicationRecord
has_many :order_items
has_many :orders, through: :order_items
end
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :item
end
I try use after_add for example, but I did not succeed.
For example my task:
In controller`s (OrderController) method create:
def create
#order = Order.create(order_params)
#order.items << Item.find(params[:id])
end
And i have that models Order or Item track this (when i add item to order) and print me message in console (for example)
Have a look at the Rails Guides about Association Callbacks. There is, for example, an after_add callback.
# in your Order model
has_many :items, after_add: :track_item_added
private
def track_item_added(item)
# your tracking code, for example
Rails.logger.debug("Item ##{item.id} added to order ##{id}")
end

Cart validations for maximum items

I have 2 models, cart and line_item:
cart.rb & line_item.rb
class Cart < ActiveRecord::Base
has_many :line_items, dependent: :destroy
belongs_to :user
class LineItem < ActiveRecord::Base
belongs_to :cart
belongs_to :user
application_controller.rb
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = current_user.cart.create
session[:cart_id] = cart.id
cart
end
How can I add validations to my cart so that user can only add 5 items maximum into their cart? At the moment I have this code but it is not working?
def maximum_items_not_more_than_5
if line_items.count > 5
errors.add(:line_items, "must be less than 5")
end
end
Here is a way, I would try :
class LineItem < ActiveRecord::Base
belongs_to :cart, validate: true # enables validation
Then inside the Cart model, write your own custom validation like :
class Cart < ActiveRecord::Base
has_many :line_items, dependent: :destroy
validate :maximum_items_not_more_than_5 # using custom validation
private
def maximum_items_not_more_than_5
if line_items.count > 5
errors.add(:base, "must be less than 5")
end
end
Why is line_item belonging to user?? Surely it would be item:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :carts
end
#app/models/cart.rb
class Cart < ActiveRecord::Base
belongs_to :user
has_many :line_items, inverse_of: :cart
has_many :items, through: :line_items
validate :max_line_items
private
def max_line_items
errors.add(:tags, "Too many items in your cart!!!!!!") if line_items.size > 5
end
end
#app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :cart, inverse_of: :line_items
belongs_to :item #-> surely you want to specify which item is in the cart?
end
#app/models/item.rb
class Item < ActiveRecord::Base
has_many :line_items
has_many :carts, through: :line_items
end
Validation
This is certainly in the realms of validation, specifically a custom method:
#app/models/model.rb
class Model < ActiveRecord::Base
validate :method
private
def method
## has access to all the instance attributes
end
end
I also put inverse_of into the mix.
You can see how this works here: https://stackoverflow.com/a/20283759/1143732
Specifically, it allows you to call parent / child objects from a particular model, thus allowing you to call validations & methods residing in those files.
In your case, it may be prudent to add validations to the line_item model -- specifying individual quantities or something. You can call the validations in this model directly from your cart model by setting the correct inverse_of

Queue of callbacks Rails

class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
before_destroy :post_method1
after_destroy :post_method2
end
class Comment < ActiveRecord::Base
belongs_to :post
after_destroy :comment_method
end
How will the callbacks and destroy methods be ordered in the two models after the post.destroy like (if post.comments.any? = true)???

Initialize objects of Associated models

I have three models which have been defined as follows:
Answer Sheet
class AnswerSheet < ActiveRecord::Base
has_many :answer_sections
accepts_nested_attributes for :answer_sections
end
Answer Section
class AnswerSection < ActiveRecord::Base
belongs_to :answer_sheet
has_many :answers
accepts_nested_attributes_for :answers
end
Answers
class Answers < ActiveRecord::Base
belongs_to: answer_section
end
I also have the following method defined in the AnswerSheet model
def self.build_with_answer_sections
answer_sheet = new # new should be called on the class e.g. AnswerSheet.new
4.times do |n|
answer_sheet.answer_sections.build
end
answer_sheet
end
How would I go about making it so that when I make a new instance of the the AnswerSheet, I can also generate all it's dependent models as well?
You can use the after_initialize callback
class AnswerSheet < ActiveRecord::Base
has_many :answer_sections
accepts_nested_attributes for :answer_sections
after_initialize :add_answer_section
def add_answer_section
4.times {self.answer_sections.build }
end
end
class AnswerSection < ActiveRecord::Base
belongs_to :answer_sheet
has_many :answers
accepts_nested_attributes_for :answers
after_initialize :add_answer
def add_answer
2.times {self.answers.build}
end
end

Resources