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
Related
class Message
has_many :threads, :class_name=>"Message", :conditions => "`#{Message.table_name}`.conversation_id = #{self.send(:conversation_id)}"
end
m = Message.first
NoMethodError: undefined method `conversation_id' for #<Class:0xc5021dc>
I even tried with single quote:
class Message
has_many :threads, :class_name=>"Message", :conditions => '`#{Message.table_name}`.conversation_id = #{self.send(:conversation_id)}'
end
m = Message.first
m.threads
This gave me Mysql::Error: You have an error in your SQL syntax
It seems it's not considering the #{...} thing while generating the condition sql
i could do it with scopes
scope :threads, lambda {|conv_id| where(:conversation_id => conv_id) }
and access it Message.where("some condition").threads()
but am looking for a neat association like
m = Message.find(1000)
m.threads should give all the conversation threads which it belongs to
You cannot use dynamic conditions in has_many. However, in your particular case it seems you need primary_key and foreign_key instead:
class Message
has_many :threads, :class_name=>"Message", :primary_key => 'conversation_id', :foreign_key => 'conversation_id'
end
You may also be interested by one of the gems that adds tree structure to ActiveRecord.
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.
I am trying to retrieve an activerecord object from my db. My models are
class User < ActiveRecord::Base
belongs_to :account
has_many :domains, :through => :account
end
And
class Account < ActiveRecord::Base
has_many :domains
has_many :users
end
And
class Domain < ActiveRecord::Base
belongs_to :account
end
Now I would like to retrieve a user based on the username and a domain name (lets assume that these are attributes of the User and the Domain classes respectively). i.e. something along the lines of
User.find(:first, :conditions =>{:username => "Paul", :domains => { :name => "pauls-domain"}})
I know that the above piece of code will not work since I do have to mention something about the domains table. Also, the association between users and domains is a one-to-many (which probably further complicates things).
Any ideas on how should this query be formed?
If you're using Rails 3.x, the following code would get the query result:
User.where(:username => "Paul").includes(:domains).where("domains.name" => "paul-domain").limit(1)
To inspect what happen, you can append .to_sql to above code.
If you're using Rails 2.x, you'd better write the raw sql query.
The following piece of code did the trick:
User.joins(:account).joins('INNER JOIN "domains" ON "accounts"."id" = \
"domains"."account_id"').where(:users => {"username" => "Paul"}).
where(:domains => {"name" => "paul-domain"})
Sorry about the formatting of this long line of code
In Rails 3, I am having a problem accessing a helper method from within a model
In my ApplicationController I have a helper method called current_account which returns the account associated with the currently logged in user. I have a project model which contains projects that are associated with that account. I have specified 'belongs_to :account' in my project model and 'has_many' in my account model. All of this seems to be working correctly and my projects are being associated with the right accounts.
However at the moment when I use 'Project.all', all of the project records are being returned as you would expect. I would like it to automatically filter the projects so that only those associated with the specified account are returned and I would like this to be the default behaviour
So I tried using 'default_scope'. The line in my model looks something like this
default_scope :order => :name, :conditions => ['account_id = ?', current_account.id]
This throws an error saying
Undefined local variable or method current_account for #
If I swap the call to current_account.id for an integer id - eg
default_scope :order => :name, :conditions => ['account_id = ?', 1]
Everything works correctly. How do I make my current_account method accessible to my model
Many Thanks
You can't access the session from models. Instead, pass the account as a parameter to a named scope.
#controller
Model.my_custom_find current_account
#model.rb
named_scope :my_custom_find, lambda { |account| { :order => :name, :conditions => ['account_id = ?', account.id]}}
I haven't used rails 3 yet so maybe named_scopes have changed.
The association setup is enough to deal with scoping on controller and view levels. I think the problem is to scope, for instance, finds in models.
In controller:
#products = current_account.products.all
Fine for view scoping, but...
In model:
class Inventory < ActiveRecord::Base
belongs_to :account # has fk account_id
has_many :inventory_items, :dependent => :destroy
has_many :products, :through => :inventory_items
end
class Product < ActiveRecord::Base
has_many :inventory_items
def level_in(inventory_id)
inventory_items.where(:inventory_id => inventory_id).size
end
def total_level
# Inventory.where(:account_id => ?????) <<<<<< ISSUE HERE!!!!
Inventory.sum { |inventory| level_in(inventory.id) }
end
end
How can this be scoped?
+1 for mark's answer. This should still work in Rails 3.
Just showing you the rails 3 way with scope and new query api:
scope :my_custom_find, lambda { |account| where(:account_id=>account.id).order(:name) }
You've got the association set up, so couldn't you just use:
#projects = current_account.projects.all
...in your controller?
I've not adjusted the syntax to be Rails 3 style as I'm still learning it myself.
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.