mongoid before_destroy callback on has_and_belongs_to_many association - ruby-on-rails

I have a simple has_and_belongs_to_many relation set up in Mongoid like so:
class Post
...
has_and_belongs_to_many :authors
scope :live, lambda{ published.where(:published_at.lt => Time.now) }
end
class Author
has_and_belongs_to_many :posts
before_save :count_posts
def count_posts
self.post_count = posts.live.length
end
end
When I update the Post model and destroy an Author / Post relationship, how can I do an before_destroy or some other callback on the author to update the post count?

I don't believe there are any features built into Mongoid relationships that would help with this, but what you can do is add a before_destroy callback on the post that tells each author it belongs to that it's being deleted by simply triggering a save on that author so that your count_posts hook is called.
class Post
has_and_belongs_to_many :authors
after_remove :update_author_counts
scope :live, lambda{ published.where(:published_at.lt => Time.now) }
....
protected
def update_author_counts
# Assuming you keep the count_posts callback in
# your author model, all you have to do is trigger a save
authors.each { |a| a.save }
end
end
Reference: http://mongoid.org/en/mongoid/docs/callbacks.html

Related

How to identify newly added has_many association in rails after_commit

Have below association in author class
has_many :books,
class_name :"Learning::Books",
through: :elearning,
dependent: :destroy
with after_commit as,
after_commit :any_book_added?, on: :update
def any_book_added?
book = books.select { |book| book.previous_changes.key?('id') }
# book's previous_changes is always empty hash even when newly added
end
Unable to find the newly added association with this method. Is this due to class_name?
Rails has a couple methods that might help you, before_add and after_add
Using this, you can define a method to set an instance variable to true
class Author < ApplicationRecord
has_many :books, through: :elearning, after_add: :new_book_added
def any_book_added?
#new_book_added
end
private
def new_book_added
#new_book_added = true
end
end
Then when you add a book to an author, the new_book_added method will be called and you can at any future time ask your Author class if any_book_added?
i.e.
author = Author.last
author.any_book_added?
=> false
author.books = [Book.new]
author.any_book_added?
=> true
As you can see, the callback method new_book_added can accept the book that has been added as well so you can save that information.

how to skip_callback while building has_many relationship objects in rails 5

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.

How can I create all has_one relationships automatically?

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

Detect changes on existing ActiveRecord association

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

Rails: How can I eager load associations with sorting through instance an method?

class Newsroom < ActiveRecord::Base
has_many :blog_posts
has_many :quote_posts
end
class BlogPost < ActiveRecord::Base
belongs_to :newsroom
end
class QuotePost < ActiveRecord::Base
belongs_to :newsroom
end
I would like to have an instance method, such that I could do #newsroom.posts to get a collection of blog_posts and quote_posts sorted by created_at.
def posts
#posts ||= #load and sort blog_posts, quote_posts, etc
end
What is the best and most efficient way to accomplish this? I have looked into using default_scope, something like:
default_scope :include => [:blog_posts, :quote_posts]
def posts
#posts ||= [blog_posts + quote_posts].flatten.sort{|x,y| x.created_at <=> y.created_at}
end
But I would rather keep the sorting at the database level, if possible. Any suggestions on how to accomplish this? Thanks.
Try something like this:
#app/models/newsroom.rb
scope :ordered_posts, lambda {
includes(:blog_posts,:quote_posts) & BlogPost.order("created_at asc") & QuotePost.order("created_at asc")
}
ARel should be able to handle the ordering of included Quote and Blog Posts. You could clean that up slightly by having scopes in both the BlogPost and QuotePost model that order by created_at and then use those scopes in the Newsroom#ordered_posts method.
I ended up using a polymorphic post model. This seems to give me what I want with the insignificant downside of having an extra model/table. I used delegate to hand off specific attribute getter methods to the correct model.
class Newsroom < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belong_to :blog_post, :polymorphic => true
delegate :title, :author, :etc, :to => :postable
end
class BlogPost < ActiveRecord::Base
has_one :post, :as => :postable
end

Resources