rails method to get the association name of a model - ruby-on-rails

Is there a way to find out what associations a model has? Take these 2 models:
class Comment < ActiveRecord::Base
belongs_to :commentable
end
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
end
I'm looking for something like:
Post.has_many #=> ['comments', ...]
Post.belongs_to # => ['user']
Comment.belongs_to # => ['commentable']

You're looking for reflect_on_all_associations.
So in short:
Post.reflect_on_all_associations(:has_many)
...will give an array (of object with attributes like name, etc) of all has_many associations.

The following will list all the associations for a particular instance of Post.
#app/models/post.rb
def list_associations
associations = []
User.reflect_on_all_associations.map(&:name).each do |assoc|
association = send assoc
associations << association if association.present?
end
associations
end

Related

Rails: collect through association?

For example, Topic has many comment, and each comment belongs to a user.
How can I get all the users have commented on the one topic, efficiently?
Now I doing this by
#commenters = #topic.comments.collect do |post|
user = post.user
user
end
And, how can I make #commenters uniq? Turn it into an array?
You could define through relation
Rails through association
Topic model
class Topic < ActiveRecord::Base
...
has_many :comments
has_many :users,
:through => :comments # add this line, it will enable association
...
end
Comment model
class Comment < ActiveRecord::Base
..
belongs_to :topic
belongs_to :user
..
end
User model
class User < ActiveRecord::Base
...
has_many :comments
...
end
then you can find users on topic.
#topic.users

Relation on scope on ActiveRecord

Here are my ActiveRecord models, with Rails 3.2 :
class User < ActiveRecord::Base
has_one :criterion
has_many :user_offer_choices
end
class Offer < ActiveRecord::Base
has_many :user_offer_choices
def seen
user_offer_choices.where(seen: true)
end
def accepted
user_offer_choices.where(accepted: true)
end
end
class Criterion < ActiveRecord::Base
belongs_to :user
end
class UserOfferChoice < ActiveRecord::Base
belongs_to :user
belongs_to :offer
end
I want to get all the criterions of the users who have seen an offer. Something like :
Offer.find(11).seen.users.criterions
but I do not know how to to it with ActiveRecord
I know I can do something like :
Criterion.joins(user: { user_offer_choices: :offer }).where(user: { user_offer_choices: {accepted: true, offer_id: 11} } )
But I want to be able to use my scopes on offers (seen & accepted). So how can I do it ?
Edit :
I have found what I was looking for, the merge method of Arel : http://benhoskin.gs/2012/07/04/arel-merge-a-hidden-gem
First, what you really want is define a scope on your choices.
class UserOfferChoice < ActiveRecord::Base
belongs_to :user
belongs_to :offer
scope :seen, where(seen: true)
scope :accepted, where(accepted: true)
end
Which allows you to do this
Offer.find(11).user_offer_choices.seen
and to get the criteria:
Offer.find(1).user_offer_choices.seen.map{|choice| choice.user}.map{|user| user.criterion}
Now, this could be cleaned up with a has many through in the Offer class.
class Offer < ActiveRecord::Base
has_many :user_offer_choices
has_many :users, :through => :user_offer_choices
end
but that gets us to the user, but skipping the scope.
Offer.find(1).users
Now, there's a trick you can do with Rails 3 scopes which you could not do with Rails 2.3.5 named_scopes. The named_scopes took a hash as arguments but returned a relation. The Rails 3 scopes take a relation, as from query methods like where. So you can define a scope in users, using the scope defined in your choices class!
class User < ActiveRecord::Base
has_one :criterion
has_many :user_offer_choices
has_many :offers, :through => :user_offer_choices
scope :seen, UserOfferChoice.seen
scope :accepted, UserOfferChoice.accepted
end
That allows us to do this:
Offer.find(1).users.seen
The map now looks like this:
Offer.find(1).users.seen.map{|user| user.criterion}
BTW, the plural of criterion is criteria. Hearing criterions in my head when I read it, hurts. You can do this to get Rails to know the plural:
config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.plural /^criterion$/i, 'criteria'
end

Retrieve the Active Record Association name between two classes\models

I am using Ruby on Rails 3.0.7 and I would like to know how to retrieve the Active Record Association name between two classes\models.
That is, I have two models
class User < ActiveRecord::Base
has_many :accounts
end
class Account < ActiveRecord::Base
belongs_to :users
end
and I would like to retrieve (on runtime) them association name, in this case accounts and users strings.
Is it possible? If so, how can I do that?
UPDATE
If I have more association statements in User and Account classes (see the below example), how can I retrieve exactly the User Account association name?
class User < ActiveRecord::Base
has_many :accounts
has_many :articles
has_many :comments
end
class Account < ActiveRecord::Base
belongs_to :users
has_many :articles
belongs_to :authorization
end
?
User.reflect_on_all_associations.each do |assoc|
puts "#{assoc.macro} #{assoc.name}"
end
#=> "has_many accounts"
UPD
User.reflect_on_all_associations.select{|a| a.class_name == "Account"}.each do |assoc|
puts "#{assoc.macro} #{assoc.name}"
end
#=> "has_many accounts"

Can't update rails has_many :through relationship

I try:
#item.associations.update_attributes(:tag_id=>params[:tag])
and
#item.associations.tag_id=params[:tag]
Both give me undefined method errors for update_attributes and tag_id=, respectively. Here's my setup:
class Item < ActiveRecord::Base
has_many :associations,:foreign_key=>"item_id",:dependent=>:destroy
has_many :reverse_associations,:foreign_key=>"tag_id",:class_name=>"Association"
has_many :tags,:through=>:associations
end
class Tag < ActiveRecord::Base
has_many :associations,:foreign_key=>"tag_id",:dependent=>:destroy
has_many :reverse_associations,:foreign_key=>"item_id",:class_name=>"Association"
has_many :items,:through=>:associations
attr_accessible :name
end
class Association < ActiveRecord::Base
belongs_to :item
belongs_to :tag
end
What am I doing wrong?
You're trying to update tag_id on the entire #item.associations collection instead of updating a single Assocation instance.
The proper way to solve this depends on what you're trying to accomplish. To update the tag_id for all associations in #item.association, try:
#item.associations.each do |association|
association.update_attributes(:tag_id => params[:tag])
end
If you want to update the tag id for a specific Association, then you somehow need to get that association first:
# Just picking the first association for the item as an example.
# You should make sure to retrieve the association that you actually
# want to update.
retagged_association = #item.associations.first
# Now, retag the association
retagged_association.update_attributes(:tag_id => params[:tag])

Relationships of model

How can I get all relationships for model. IE, I've got User model:
class User < AR::Base
has_many :messages, :foreign_key => 'author'
has_many :posts
belongs_to :role
end
So how can I know which relationships User model has got? And foreign_keys if they are presented.
User.reflect_on_all_associations.each do |assoc|
puts "#{assoc.macro} #{assoc.name}"
end
Outputs:
has_many messages
has_many posts
belongs_to role
The reflect_on_all_associations method return an array of MacroReflection objects. They support other methods as well, for querying the options hash of each association and other useful stuff.

Resources