What's the actual class of a has_many relation? - ruby-on-rails

Basic primer:
class User
has_many :programs, :through => :memberships
has_many :memberships
end
class Program
end
class Membership
belongs_to :user
belongs_to :program
end
Console:
User.new.save
Program.new.save
User.programs << Program.first
User.first.programs.class
# => Array
User.first.programs.methods.grep /where/
# => []
User.first.programs.where :id => 1
# => [#<Program id: 1>]
User.first.programs.where(:id => 1).class
# => ActiveRecord::Relation
So the question is that User.first.programs, the has_many method, seems to return a result that barks like an ActiveRecord::Relation and accepts methods like a Relation, but self-identifies as an Array and shares its methods with an instance of class Array.
So what gives?

It's indeed surprising and don't know if it was the best solution, but at least it's documented (in the AssociationProxy class):
the association proxy in blog.posts
has the object in blog as #owner, the
collection of its posts as #target,
and the #reflection object represents
a :has_many macro.
This class has most of the basic
instance methods removed, and
delegates unknown methods to #target
via method_missing. As a corner case,
it even removes the class method and
that’s why you get
blog.posts.class # => Array though
the object behind blog.posts is not an
Array, but an
ActiveRecord::Associations::HasManyAssociation.

Related

Using ActiveRelation with :joins on polymorphic has_one to implicitly create relation

We can use ActiveRelation like this:
MyModel.where(:field => "test").create => #<Message ... field:"test">
But it doesnt work for joins with polymorphic has_one associations:
class RelatedModel < AR::Base
# has :some_field
belongs_to :subject, :polymorphic => true
end
class MyModel < AR::Base
# need some dirty magic here
# to build default related_model with params from active_relation
has_one :related_model, :as => :subject, :dependent => :destroy
end
describe MyModel do
it "should auto-create has_one association with joins" do
test = MyModel.joins(:related_model).where("related_models.subject_type" => "MyModel", "related_models.some_field" => "chachacha").create
test.related_model.should_not be_nil
test.related_model.some_field.should == "chachacha"
test.related_model.subject_type.should == "MyModel"
test.related_model.subject_id.should == test.id
# fails =)
end
end
Is it possible to extract active_relation params, pass them to MyModel for use in before_create and build RelatedModel with them?
Diving into ActiveRecord sources i found that
ActiveRecord::Relation covers 'create' with 'scoping' method.
ActiveRecord::Persistance 'create' calls 'initialize' from ActiveRecord::Core.
ActiveRecord::Core 'initialize' calls 'populate_with_current_scope_attributes'
This method declared in ActiveRecord::Scoping uses 'scope_attributes' declared in ActiveRecord::Scoping::Named.
scope_attributes creating relation 'all' and calls 'scope_for_create' on it.
'ActiveRecord::Relation's 'scope_for_create' uses only 'where_values_hash' from current_scope that does not contain rules like 'related_models.subject_type' (this values are contained in where_clauses). So we need to have simple key-value wheres to be used with 'create' on ActiveRecord::Relation. But ActiveRecord not clever enough to know that 'some_field' in where clause should be used with join table.
I found it can be implemented only by accessing where options with self.class.current_scope.where_clauses in 'before_create' on MyModel, parsing them and setting up attributes.
class MyModel < AR::Base
before_create :create_default_node
def create_default_node
clause = self.class.current_scope.where_clauses.detect{|clause| clause =~ /\`related_models\`.\`some_field\`/}
value = clause.scan(/\=.+\`([[:word:]]+)\`/).flatten.first
self.create_node(:some_field => value)
end
end
But it is so dirty, then i decided to find simpler solution and inverted dependency as described in Railscast Pro #394, moved RelatedModel functionality to MyModel with STI. Actually i needed such complicated relation creation because RelatedModel had some functionality common for all models (acts as tree). I decided to delegate 'ancestors' and 'children' to RelatedModel. Inverting dependency solved this problem.
class MyModel < AR::Base
acts_as_tree
belongs_to :subject, :polymorphic => true
end
class MyModel2 < MyModel
end
class RelatedModel < AR::Base
# has :some_field
has_one :my_model, :as => :subject, :dependent => :destroy
end
MyModel.create{|m| m.subject = RelatedModel.create(:some_field => "chachacha")}
MyModel.ancestors # no need to proxy relations

Get model class from symbol

I'm implementing a method, that will be used in many places of a project.
def do association
end
"association" is a symbol, like :articles, :tags, :users etc.
When the association is :articles, I need to work with the Article model.
When the association is :users, I need to work with the User model.
Etc.
I know, that I can write a helper method, that returns model class, depending on the provided symbol. But is there a ready to use method for that?
Rails provides a method called classify on the String class for such purpose.
:users.to_s.classify.constantize
#User
:line_items.to_s.classify.constantize
#LineItem
Edit:
If you are trying to retrieve the class associated with an association, use this approach:
Author.reflect_on_association(:books).klass
# => Book
This will address the scenario where the association name doesn't match the class name.
E.g:
class Order
has_many :line_items
has_many :active_line_items, :class_name => "LineItem",
:conditions => {:deleted => false}
end
In the example above, :active_line_items will result in ActiveLineItem and our original code will throw error.
Read more about this here.
This will work
(:users.to_s.singularize.capitalize.constantize).find :all, :conditions => ["name = ?", "john"]
And with your example
association.to_s.singularize.capitalize.constantize

(Rails Question) Merging multiple polymorphic has_many relationships

(This is not the actual code I'm using, although this sums up the idea of what I want to do)
class Connection < ActiveRecord::Base
belongs_to :connection1, :polymorphic => true
belongs_to :connection2, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :followers, :class_name => 'Connection', :as => :connection1
has_many :followings, :class_name => 'Connection', :as => :connection2
end
My question is that I want to know how I will be able to create a method called "network" such that what is returned isn't an array. Like so,
u = User.first
u.network # this will return a merged version of :followings and :followers
So that I'll still be able to do this:
u.network.find_by_last_name("James")
ETA:
Or hmm, I think my question really boils down to if it is possible to create a method that will merge 2 has_many associations in such a way that I can still call on its find_by methods.
Are you sure that you want a collection of Connections, rather than a collection of Users?
If it's a collection of Connections that you need, it seems like you'll be well served by a class method on Connection (or scope, if you like such things).
connection.rb
class Connection < ActiveRecord::Base
class << self
def associated_with_model_id(model, model_id)
include([:connection1, :connection2]).
where("(connection1_type IS #{model} AND connection1_id IS #{model_id})
OR (connection2_type IS #{model} AND connection2_id IS #{model_id})")
end
end
end
user.rb
class User < ActiveRecord::Base
def network
Connection.associated_with_model_id(self.class.to_s, id)
end
end
Probably not as useful as you'd like, but maybe it'll give you some ideas.

Rails: Overriding ActiveRecord association method

Is there a way to override one of the methods provided by an ActiveRecord association?
Say for example I have the following typical polymorphic has_many :through association:
class Story < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings, :order => :name
end
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :stories, :through => :taggings, :source => :taggable, :source_type => "Story"
end
As you probably know this adds a whole slew of associated methods to the Story model like tags, tags<<, tags=, tags.empty?, etc.
How do I go about overriding one of these methods? Specifically the tags<< method. It's pretty easy to override a normal class methods but I can't seem to find any information on how to override association methods. Doing something like
def tags<< *new_tags
#do stuff
end
produces a syntax error when it's called so it's obviously not that simple.
You can use block with has_many to extend your association with methods. See comment "Use a block to extend your associations" here.
Overriding existing methods also works, don't know whether it is a good idea however.
has_many :tags, :through => :taggings, :order => :name do
def << (value)
"overriden" #your code here
super value
end
end
If you want to access the model itself in Rails 3.2 you should use proxy_association.owner
Example:
class Author < ActiveRecord::Base
has_many :books do
def << (book)
proxy_association.owner.add_book(book)
end
end
def add_book (book)
# do your thing here.
end
end
See documentation
I think you wanted def tags.<<(*new_tags) for the signature, which should work, or the following which is equivalent and a bit cleaner if you need to override multiple methods.
class << tags
def <<(*new_tags)
# rawr!
end
end
You would have to define the tags method to return an object which has a << method.
You could do it like this, but I really wouldn't recommend it. You'd be much better off just adding a method to your model that does what you want than trying to replace something ActiveRecord uses.
This essentially runs the default tags method adds a << method to the resulting object and returns that object. This may be a bit resource intensive because it creates a new method every time you run it
def tags_with_append
collection = tags_without_append
def collection.<< (*arguments)
...
end
collection
end
# defines the method 'tags' by aliasing 'tags_with_append'
alias_method_chain :tags, :append
The method I use is to extend the association. You can see the way I handle 'quantity' attributes here: https://gist.github.com/1399762
It basically allows you to just do
has_many : tags, :through => : taggings, extend => QuantityAssociation
Without knowing exactly what your hoping to achieve by overriding the methods its difficult to know if you could do the same.
This may not be helpful in your case but could be useful for others looking into this.
Association Callbacks:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Example from the docs:
class Project
has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
def evaluate_velocity(developer)
...
end
end
Also see Association Extensions:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
Rails guides documents about overriding the added methods directly.
OP's issue with overriding << probably is the only exception to this, for which follow the top answer. But it wouldn't work for has_one's = assignment method or getter methods.

Tell me how to use ActiveRecord#afeter_add

Now I hava a problem,how can I make the callback#after_add receive a reference to the join model in a has_many :through association?
my code like this:
class Emergency
has_many :departments, :through => :eme_references, :after_add => Proc.new { |eme_reference| eme_reference.eme_flag = 1}
end
the attribute eme_flag is the model EmeReference's attribute! but in the block ,i get the eme_reference.class is Emergency.
I want to set the attribute eme_flag of the model EmeReference.
That is my question!
cheers!
Presumably Emergency also has_many :eme_references in order for the :through association to work?
In that case, you should be able to attach the callback there:
has_many :eme_references,
:after_add => Proc.new { |emergency, eme_ref| # code here }
The block accepts 2 parameters, the first will be the Emergency, the 2nd will be the EmeReference being added.
Perhaps a before_save callback on EmeReference can also do what you want in this instance?
I think what you want to do can't be done there.
You could create an after_create hook on departments (I'm assuming Emergency has_many eme_references has_many departments):
class Emergency
has_many :departments, :through => :eme_references
def flag!
eme_flag=1
save
end
end
class Department
after_create :check_emergency
# this allows you to call department.emergency. Will return nil if anything is nil
delegate :emergency, :to =>:eme_reference, :allow_nil => true
def check_emergency
self.emergency.flag! if self.emergency.present?
end
end

Resources