Is there a way to add callbacks for when an item is added to a habtm relationship?
For example, I have the following two models, User and Role:
# user.rb
class User; has_and_belongs_to_many :roles; end
# role.rb
class Role; has_and_belongs_to_many :users; end
I want to add a callback to the << method (#user << #role), but I can't seem to find an ActiveRecord callback because there is no model for the join table (because its a true habtm).
I'm aware that I could write a method like add_to_role(role), and define everything in there, but I'd prefer to use a callback. Is this possible?
Yes there is:
class User < AR::Base
has_and_belongs_to_many :roles,
:after_add => :tweet_promotion,
:after_remove => :drink_self_stupid
private
def tweet_promotion
# ...
end
def drink_self_stupid
# ...
end
end
Look for 'Association callbacks' on this page for more:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Related
how to skip_callback while building has_many relationship objects in rails 5
Consider below case
class Customer
has_many :resources
end
class Resource
attr_accessor: :skip_callback
belongs_to :customer
after_commit :data_calculation, unless: :skip_callback
def data_calculation
# logic goes here
end
end
customer = Customer.new
customer.resources.build({name: 'abc'})
customer.save
I want skip callback of associated object.
Can we do this while building object?
found solution,
customer = Customer.new
customer.resources.build({name: 'abc', skip_callback: true})
customer.save
passing attr_accessor as params will set callbacks condition value also.
I have the following models:
class Post < ApplicationRecord
has_one: :basic_metric
has_one: :complex_metric
end
class BasicMetric < ApplicationRecord
belongs_to :post
end
class ComplexMetric < ApplicationRecord
belongs_to :post
end
Once a post is created, both basic_metric and complex_metric are nil:
p = Post.first
p.basic_metric # => nil
p.complex_metric # => nil
And because of how my app is going to work, the BasicMetricsController and ComplexMetricsController only have the update method. So I would like to know if there is a way to create them as soon as a post is created.
One very common way of accomplishing this is using ActiveRecord callbacks
class Post
after_create :create_metrics
private
def create_metrics
# methods created by has_one, suggested in the comments
create_basic_metric(additional_attrs)
create_complex_metric(additional_attrs)
end
end
Another option you have is to overwrite the method created by has_one, i.e.:
class Post
has_one: :basic_metric
has_one: :complex_metric
def basic_metric
super || create_basic_metric(additional_attrs)
end
def complex_metric
super || create_complex_metric(additional_attrs)
end
end
This way they won't be created automatically with any new post, but created on demand when the method is called.
Can you try this one,
post = Post.first
post.build_basic_metric
post.build_complex_metric
This will help you to build/create the has_one association object if record not saved by default use post.save at the end.
If you need this in modal you can use like this,
class Post
after_create :build_association_object
private
def create_metrics
self.build_basic_metric
self.build_complex_metric
# save # (optional)
end
end
An application I'm working on, is trying to use the concept of polymorphism without using polymorphism.
class User
has_many :notes
end
class Customer
has_many :notes
end
class Note
belongs_to :user
belongs_to :customer
end
Inherently we have two columns on notes: user_id and customer_id, now the bad thing here is it's possible for a note to now have a customer_id and a user_id at the same time, which I don't want.
I know a simple/better approach out of this is to make the notes table polymorphic, but there are some restrictions, preventing me from doing that right now.
I'd like to know if there are some custom ways of overriding these associations to ensure that when one is assigned, the other is unassigned.
Here are the ones I've tried:
def user_id=(id)
super
write_attribute('customer_id', nil)
end
def customer_id=(id)
super
write_attribute('user_id', nil)
end
This doesn't work when using:
note.customer=customer or
note.update(customer: customer)
but works when using:
note.update(customer_id: 12)
I basically need one that would work for both cases, without having to write 4 methods:
def user_id=(id)
end
def customer_id=(id)
end
def customer=(id)
end
def user=(id)
end
I would rather use ActiveRecord callbacks to achieve such results.
class Note
belongs_to :user
belongs_to :customer
before_save :correct_assignment
# ... your code ...
private
def correct_assignment
if user_changed?
self.customer = nil
elsif customer_changed?
self.user = nil
end
end
end
I have a rails 4 app with STI models:
# models/person.rb
def Person < ActiveRecord::Base
end
# models/director.rb
def Director < Person
end
# models/actor.rb
def Director < Person
end
But because one person can be an actor and an director simultaneously, I want STI with many types like:
person = Person.first
person.type = "Director, Actor"
person.save
Actor.first.id => 1
Director.first.id => 1
Is there mechanism in rails or gem for realize this?
Rails does not support this and I'm not aware of any gems that support this as described (i.e. multiple subclass names in the type column).
There is gem at https://github.com/mhuggins/multiple_table_inheritance which uses separate tables for the subclasses and you can always use mixins as an alternative to inheritance.
I believe the more Rails idiomatic way to do something similar would be via scopes, which would allow you to do:
person = Person.first
person.position = 'Director, Actor'
person.save
person.directors.first.id => 1
person.actors.first.id => 1
And you would just have to define a pair of scopes in your Person class:
scope :actors, -> { where('position like ?', '%Actor%') }
scope :directors, -> { where('position like ?', '%Director%') }
You would lose the ability to do person.is_a? with this, but Ruby doesn't really do multiple inheritance in such a way as to allow #is_a? to return true when passed sibling classes anyway. You can also get effectively similar functionality with a simple test method:
def is_actor?
self.position =~ /Actor/
end
def is_director?
self.position =~ /Director/
end
EDIT: I haven't done a lot of Rails 4, so my scope syntax MAY not be right, I just glanced at the docs. The principle should be sound, though.
Thank to all answerers above!
I found solution that most appropriate for me:
I've created hmt association Person-ProfessionsPerson-Profession and leave descendants for Person class (Director and Actor).
# models/profession.rb
Profession < ActiveRecord::Base
has_many :professions_people, dependent: :destroy
has_many :people, through: :professions_people
end
# models/person.rb
def Person < ActiveRecord::Base
has_many :professions_people, dependent: :destroy
has_many :professions, through: :professions_people
end
# models/director.rb
def Director < Person
include PeopleFromProfession
end
# models/actor.rb
def Actor < Person
include PeopleFromProfession
end
I've seed 2 professions with column "class_type" (which should not change in app's work) "Actor" and "Director"
I've also add concern PeopleFromProfession for share some code:
# models/concerns/actor.rb
module PeopleFromProfession
extend ActiveSupport::Concern
included do
default_scope { includes(:professions).where(professions: {class_type: self.name}) }
after_create :create_join_table_record
end
module ClassMethods
def model_name
Person.model_name
end
end
private
def create_join_table_record
self.professions << Profession.where(class_type: self.class.name).first
end
end
default_scope is for scoping only people with specific profession, create_join_table_record callback is monkey-patch for create missed join table record.
Class method model_name was overwriting for purposes, that covered here Best practices to handle routes for STI subclasses in rails
If you will find some problems in that approach, please tell me.
I am writing an ActiveRecord extension that needs to know when an association is modified. I know that generally I can use the :after_add and :after_remove callbacks but what if the association was already declared?
You could simply overwrite the setter for the association. That would also give you more freedom to find out about the changes, e.g. have the assoc object before and after the change E.g.
class User < ActiveRecord::Base
has_many :articles
def articles= new_array
old_array = self.articles
super new_array
# here you also could compare both arrays to find out about what changed
# e.g. old_array - new_array would yield articles which have been removed
# or new_array - old_array would give you the articles added
end
end
This also works with mass-assignment.
As you say, you can use after_add and after_remove callbacks. Additionally set after_commit filter for association models and notify "parent" about change.
class User < ActiveRecord::Base
has_many :articles, :after_add => :read, :after_remove => :read
def read(article)
# ;-)
end
end
class Article < ActiveRecord::Base
belongs_to :user
after_commit { user.read(self) }
end