I have polymorphic likes model, for storing productions, comments, whatever that people like. Is there a way to dry up this query?
user.likes.where(:likeable_id => thing.id, :likeable_type => thing.class)
This doesn't work:
user.likes.where(:likeable => thing)
How about this:
class Like
...
# scope
def self.for(object)
scoped.where(:likeable_id => object.id, :likeable_type => object.class.to_s)
end
end
You would then use it like this:
user.likes.for(thing)
Related
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.
I have an ActiveRecord model that has two database attributes, total and processing_fees.
It has various scopes defined, such as (just as examples):
class Item < ActiveRecord::Base
scope :completed, joins(:order).where(:orders => {:status => Order::Status::COMPLETED})
scope :for_client, lambda {|client| where("client_id=?", client.id)}
...etc...
end
I can easily do something like:
Item.completed.for_client(client).sum(:total)
but what I would like to do is something like:
Item.completed.for_client(client).calculate(:total - :processing_fees) # obv not valid
Is there any way to do such a thing?
Something like this should work:
Item.completed.for_client(client).calculate(:sum, "item.total - item.processing_fees")
I use state_machine with ActiveRecord on one of my Rails 3.1 application. I found the syntax to access records with different states to be cumbersome. Is it possible to define each state to be the scope at the same time without writing scope definitions by hand?
Consider following example:
class User < ActiveRecord:Base
state_machine :status, :initial => :foo do
state :foo
state :bar
# ...
end
end
# state_machine syntax:
User.with_status :foo
User.with_status :bar
# desired syntax:
User.foo
User.bar
I'm adding the following to my models:
state_machine.states.map do |state|
scope state.name, :conditions => { :state => state.name.to_s }
end
Not sure if you count this as "writing scope definitions by hand?"
Just in case, if somebody is still looking for this, there are following methods added while defining state_machine:
class Vehicle < ActiveRecord::Base
named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}}
# with_states also aliased to with_state
named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
# without_states also aliased to without_state
end
# to use this:
Vehicle.with_state(:parked)
I like to use this because there will never be conflict with state name. You can find more information on state_machine's ActiveRecord integration page.
Bonus is that it allows to pass array so I often do something like:
scope :cancelled, lambda { with_state([:cancelled_by_user, :cancelled_by_staff]) }
I also needed this functionality, but state_machine has nothing similar. Although I've found this gist, but aasm seems like a better state machine alternative in this case.
I will show you a way which can be used if the model has multiple state_machines too.
It works even in the case when your states are integers.
def Yourmodel.generate_scopes_for_state_machines state_machines.each_pair do |machine_name, that_machine|
that_machine.states.map do |state|
# puts "will create these scopes: #{machine_name}_#{state.name} state: #{state.value} "
# puts "will create these scopes: #{machine_name}_#{state.name} state: #{state.name.to_s} "
# Price.scope "#{machine_name}_#{state.name}", :conditions => { machine_name => state.name.to_s }
Price.scope "#{machine_name}_#{state.name}", :conditions => { machine_name => state.value }
end end end
Yourmodel.generate_scopes_for_state_machines
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
I'm working on a legacy database that is complete non-sense. I have a table called movie that contains columns with names like c00, c01, c02 and so on. The table also uses non-standard primary_keys. So I've created a class called movie like this:
class Movie < ActiveRecord::Base
set_table_name "movie"
set_primary_key "idMovie"
belongs_to :media_file, :foreign_key => "idFile"
def title
self.c00
end
def plot
self.c01
end
end
I'd like to be able to do something like Movie.find_by_title("Die Hard") and have it return the right result. Also I'd like to be able to say Movie.create(:title => "Die Hard"). How do I do this?
I think you want alias_attribute. Check out Brian Hogan's excellent presentation from RailsConf this year.
You really just need a combination of Sarah's answer and Ben's answer:
class Movie < ActiveRecord::Base
# gives you Movie.find_by_title
# and lets you chain with other named scopes
named_scope :find_by_title, lambda { |title| { :conditions => { :c00 => title } } }
# gives you
# movie.title
# movie.title=(title)
# and
# Movie.new(:title => title)
alias_attribute :title, :c00
end
The find_by_* methods use reflection, so it just isn't going to happen with Rails out-of-the-box. You can, of course, define your own methods:
def self.find_by_title(title)
first(:conditions => { :c00 => title })
end
The next step would be to iterate over a hash of column_aliases => real_columns to use as fodder for calls to alias_attribute and define_method.