How could I define methods on an association? - ruby-on-rails

UPDATE:
I've put this in my person class
has_many :things, :dependent => :destroy do
def [](kind)
where("kind = ?", kind.to_s)
end
end
but when I invoke <Person Instance>.things[:table], I get this error:
undefined method `where' for #<Class:0x111dc3ba8>
ORIGINAL QUESTION:
I have a person, who has_many things.
I'd like to be able to do something like:
<Person Instance>.things[:table]
that would be defined similar to
def things[](arg)
self.things.find(:first, :conditions => ["kind = ?", arg.to_s])
end
currently, that method gives me this error:
syntax error, unexpected '[', expecting '\n' or ';'
So, how do I properly define things[]?

What you're looking for are called association extensions in rails.
Read about it here
Your implementation might look something like:
has_many :things do
def [](kind)
where(:kind => kind)
end
end

I think [] are not allowed in method names...
You could just skip them in your method name, but then your association method would be overwritten/replaced, if you've set up your assocations properly.
I'd do something like this:
# in your Person model
def things_of_kind(kind)
self.things.find(:first, :conditions => ["kind = ?", arg.to_s]
end
# then you could call
<PersonInstance>.things_of_kind(:table)
Alternatively there are association extensions which use this technique but in the proper place.
And there are also scopes, which can be helpful.

Related

Accessing attributes via Rails relations

I have a Position model for which I have a scope defined:
scope :default, where('is_default = ?', 1)
Idea being that I want to know which is the default position. I can do something like: #profile.positions.default and this returns an activerecord relation and the default position record. The issue is that now that I have the default record, I need to access other attributes of Positions such as title..
#profile.positions.default.title
but the above returns an error: NoMethodError: undefined method `title' for #
Any clues? Thanks.
A scope turns a collection of objects, not a single object, so you're trying to call title on an array of ActiveRecord results.
You probably want something like this:
#profile.positions.default.first.title
Or if you always want just one record, you may switch from a scope to a class method:
def self.default
where('is_default = ?', 1).first
end
class Profile < ActiveRecord::Base
has_many :positions
has_one :default_position, :class_name => 'Position',
:conditions => ['is_default = ?', true]
end
Then
#profile.default_position.title

how to index associated models using thinkingtank and indextank

We are using thinkingtank gem and having trouble indexing model associations, even simple ones. For example, a profile belongs to an institution, which has a name – we would like to do something like:
class Profile < ActiveRecord::Base
#model associations
define_index do
indexes institution(:name), :as => :institution_name
end
end
but that doesn't work. This must be very simple – what am I doing wrong?
a possible solution to this issue would be adding a method returning the element to index. For the profile.institution.name case:
# profile.rb
# ...
belongs_to :institution
# ...
define_index do
indexes institution_name
end
def institution_name
self.institution.name
end
# ...
Also the ", :as => ..." syntax is not supported on thinkingtank.
I would also recommend giving a try to Tanker: https://github.com/kidpollo/tanker
Regards.
Adrian

How do I create custom "association methods" in Rails 3?

I've read this article, but it's for Rails 1.x.
I'd really like to create my own association methods:
user = User.find(1)
# Example of a normal association method
user.replies.create(:body => 'very informative. plz check out my site.')
# My association method
user.replies.find_by_spamminess(:likelihood => :very)
In Rails 3, what's the proper way of doing this?
The Rails 3 way of doing things is often to not use find methods, but rather scopes, which delays the actual database call until you start iterating over the collection.
Guessing at your first example, I would do:
in class Reply ...
scope :spaminess, lambda {|s| where(:likelyhood => s) }
and then using it:
spammy_messages = user.replies.spaminess(:very)
or to use it in a view
spammy_messages.each do |reply|
....
end
I think I found it!
If you search for "association extensions" the Rails API page for ActiveRecord::Assications, you'll see that this is the syntax (copied from that link):
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

Method ignoring parameter value in Ruby on Rails, using default value instead

I'm having some issues in RoR with some model methods I am setting. I'm trying to build a method on one model, with an argument that gets supplied a default value (nil). The ideal is that if a value is passed to the method, it will do something other than the default behavior. Here is the setup:
I currently have four models: Market, Deal, Merchant, and BusinessType
Associations look like this:
class Deal
belongs_to :market
belongs_to :merchant
end
class Market
has_many :deals
has_many :merchants
end
class Merchant
has_many :deals
belongs_to :market
belongs_to :business_type
end
class BusinessType
has_many :merchants
has_many :deals, :through => :merchants
end
I am trying to pull some data based on Business Type (I have greatly simplified the return, for the sake of brevity):
class BusinessType
def revenue(market=nil)
if market.nil?
return self.deals.sum('price')
else
return self.deals(:conditions => ['market_id = ?',market]).sum('price')
end
end
end
So, if I do something like:
puts BusinessType.first.revenue
I get the expected result, that is the sum of the price of all deals associated with that business type. However, when I do this:
puts BusinessType.first.revenue(1)
It still returns the sum price of all deals, NOT the sum price of all deals from market 1. I've also tried:
puts BusinessType.first.revenue(market=1)
Also with no luck.
What am I missing?
Thanks!
Try this:
class BusinessType
def revenue(market=nil)
if market.nil?
return self.deals.all.sum(&:price)
else
return self.deals.find(:all, :conditions => ['market_id = ?',market]).sum(&:price)
end
end
end
That should work for you, or at least it did for some basic testing I did first.
As I have gathered, this is because the sum method being called is on enumerable, not the sum method from ActiveRecord as you might have expected.
Note:
I just looked a bit further, and noticed you can still use your old code with a smaller tweak than the one I noted:
class BusinessType
def revenue(market=nil)
if market.nil?
return self.deals.sum('price')
else
return self.deals.sum('price', :conditions => ['market_id = ?', market])
end
end
end
Try this!
class BusinessType
def revenue(market=nil)
if market.nil?
return self.deals.sum(:price)
else
return self.deals.sum(:price,:conditions => ['market_id = ?',market])
end
end
end
You can refer this link for other functions. http://en.wikibooks.org/wiki/Ruby_on_Rails/ActiveRecord/Calculations

Ruby on Rails: Defining a method with options

I'm looking to define a method that lets me pass options; something like:
#user.tasks(:completed => true)
I thought something like this would work in my user model (but it's not):
User.rb model
def tasks(options)
tasks.find(:all, options)
end
How would I define the method correctly to let me use #user.tasks(:completed => true)?
This is basically how I'd do it:
def tasks(options={})
unless options[:something].blank?
# do stuff
end
end
There are some different ways to pass options, but you definitively want to pass a hash with a default value (so that you can call the method without options).
In your case the following should address what you want to do:
def tasks(options={})
Task.find(:all, options[:conditions])
end
Edit: and then call it #thing.tasks( {:conditions => "blah"} )
I haven't tested but it should be ok
Edit 2: But like EmFi said it's not optimal to do this. Consider using an association instead. You'll be able to go #thing.tasks.find(:all, :conditions => {blah})
Does User have a has_many :tasks association? That seems to be what you're after here. In that case Rails provides finders for you, which you can access like this:
#user.tasks.find :all, :conditions => { :completed => true }
Or even shorter:
#user.tasks.all :conditions => { :completed => true }
If that's not terse enough and you always want to use a particular condition, try a named scope:
# In your Task model:
named_scope :completed, :conditions => { :completed => true }
# Then you can just call...
#some_user.tasks.completed # => Only completed Tasks for #some_user
Why would you associate a find all on another model with an instance method? I could understand if it was a relation and the find required find options based on the calling record. But there's ActiveRecord Associations for that.
Then there's ActiveRecord::Base#all(options) which is an alias for Task.find(:all, options)
Together make things simpler:
class User < ActiveRecord::Base
has_many :tasks
end
#user.tasks.all(:conditions => {:completed => true})
what you need is:
options[:conditions] in your method
Activerecord provides a method called with_scope, so to pass any additional conditions
#user.tasks(:completed => true)
you can define the task method as
def tasks(options={})
with_scope :find => options
User.all :order => 'id desc'
end
end
and this will merge any hash passed as options parameter with the actual find
The only caveat is you need to modify your method call slightly
#user.tasks(:conditions => {:completed => true})
or to something like
#user.tasks(:select => 'username')
But if there is an association between user and tasks model then I would do what Jordan has in his post

Resources