How to create your own methods like model.association.build in Rails? - ruby-on-rails

I was wondering if there is some standard way to create methods like the build method, which is generated on a has_many association. For illustration, assuming the following setup
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Rails automatically generates a post.comments.build method. Is there a standard way or The Rails Way to create my own method like this? I tried it by defining the method on the singleton class of the comments object, like so:
class Post < ActiveRecord::Base
after_initialize do
class << comments
def go
#do something where I can access the the 'owning' post object
end
end
end
end
But this code seemed to break after an ActiveRecord update. So, I was wondering if there is a better way.

You can add methods to the association by passing a block to the has_many method call and defining the method. An example taken from the docs:
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end

Related

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

Get unsaved associations from inside the model

How can I access unsaved associations from inside a model method? For example:
class Post < ActiveRecord::Base
has_many :comments
def comment_dates
comments.pluck(:created_at).sort
end
end
The comments method called inside a model method returns just already saved associations. When I call the method from the object, e.g. post.comments it returns both saved and unsaved associations.
How can I access saved and unsaved associations from inside a model method? I need this to do some complex validation including associations.
How about something like this?
class Post < ActiveRecord::Base
has_many :comments
def comment_dates
comments.pluck(:created_at).sort
end
def comments_dates_no_query
comments.map(&:created_at).sort
end
def unsaved_comments
comments.reject(&:persisted?)
end
def saved_comments
comments.select(&:persisted?)
end
end
You can us it like
post.saved_comments
or
post.unsaved_comments
Hope that helps!

How to call instance method of comment class through Article Model ?

class Comment < ActiveRecord::Base
belongs_to :article
end
class Article < ActiveRecord::Base
has_many :comments do
def posted_comments
#user_comment is just an attribute of comment.
collect(&:user_comment)
end
end
end
to fetch the posted comments :
Article.first.comments.posted_comments
=> ["Nice article posted", "comment 2 added", "Good article"]
Above one is fetching correct results, but I want to have a more compact version.
Something like this:
Article.first.posted_comments
#this should list the collection of comments on the article.
Can we do something like this with Rails ActiveRecord ?
For simply solution, you can define method called posted_comments that calls nested association as the following:
def posted_commments
self.comments.posted_comments
end
Or, Try the following code:
has_many :posted_comments, -> { select("user_comment") }, class_name: "Comment"

Can I call a class method from another model in Rails?

I'm trying to call a class method (currently a scope) that uses an attribute from its parent (or belongs_to) model, but can't seem to get it working right.
My models:
class Venue < ActiveRecord::Base
attr_accessible :address
has_many :events, :dependent => :destroy
end
class Event < ActiveRecord::Base
belongs_to :venue
scope :is_near, lambda {|city| self(Venue.address).near(city, 20, :units => :km)}
end
I know the syntax is wrong, but I think that illustrates what I'm intending to do. I want to get the address of the venue and call another method on it. I need the scope in the Event class so I can chain other scopes together.
Appreciate any ideas.
Since #address is not a class method but an instance method, you won't be able to do what you want by using a scope.
If you want to get all the events within a 20km range of a venue, create these class methods in Venue instead:
class Venue < ActiveRecord::Base
def self.events_near_city(city)
venues_near_city(city).map(&:events).flatten
end
private
def self.venues_near_city(city)
near(city, 20, :units => :km)
end
end
Then call it by using Venue.events_near_city(session[:city]) since, as you told me in chat, you're storing the city in the session.
As you've defined it above, address is not a class method - it's an instance method. You would have to have an instance of venue (like you do in your view) to call it.
Searching a bit more I found this page that answered the question in another way. This works better for me because it's simpler to call, and I can use it on various relations. In rails how can I delegate to a class method
class Venue < ActiveRecord::Base
attr_accessible :address
def self.is_near(city)
venues_near_city(city).map(&:events).flatten
end
private
def self.venues_near_city(city)
self.near(city, 20, :units => :km)
end
end
class Event < ActiveRecord::Base
belongs_to :venue
class << self
def is_near(*args, &block)
Venue.is_near(*args, &block)
end
end
end
And I call it with event.is_near(session[:city])

accessing associations within before_add callback in Rails 3

In Rails 3.2 I have been looking for a way to traverse the associations of an object within the before_add callback.
So basically my use case is:
class User < ActiveRecord::Base
has_and_belongs_to_many :meetings
end
class Meeting < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :comments, :before_add => :set_owner_id
end
class Comment < ActiveRecord::Base
belongs_to :meeting
end
def set_owner_id(child)
child.owner_id = <<<THE USER ID for #user >>>
end
and I am creating a comment within the context of a user:
#user.meetings.first.comments.create
How do I traverse the associations from within the before_add callback to discover the id of #user? I want to set this at model level. I have been looking at proxy_association, but I may be missing something. Any ideas?
You should probably create the comment in the context of the meeting, no? Either way, you should handle this in the controller since you'll have no access to #user in your model.
#comment = Meeting.find(id).comments.create(owner_id: #user, ... )
But if you insist on your way, do this:
#comment = #user.meetings.first.comments.create(owner_id: #user.id)

Resources