Ruby on Rails: Defining a method with options - ruby-on-rails

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

Related

ActiveAdmin, polymorphic associations, and custom filters

Rails 3.1, ActiveAdmin 0.3.4.
My question is somewhat similar to this one but different enough in terms of data modeling that I think it warrants its own response. Models:
class CheckoutRequest < ActiveRecord::Base
has_one :request_common_data, :as => :requestable, :dependent => :destroy
end
class RequestCommonData < ActiveRecord::Base
belongs_to :requestable, :polymorphic => true
end
The RequestCommonData model has a completed field (boolean) that I'd like to be able to filter in ActiveAdmin's CheckoutRequest index page. I've tried a few different approaches to no avail, including the following:
filter :completed, :collection => proc { CheckoutRequest.all.map { |cr| cr.request_common_data.completed }.uniq }
which results in no filter being displayed. Adding :as => :select to the line, as follows:
filter :completed, :as => :select, :collection => proc { CheckoutRequest.all.map { |cr| cr.request_common_data.completed }.uniq }
results in the following MetaSearch error message:
undefined method `completed_eq' for #<MetaSearch::Searches::CheckoutRequest:0x007fa4d8faa558>
That same proc returns [true, false] in the console.
Any suggestions would be quite welcome. Thanks!
From the meta_search gem page you can see that for boolean values the 'Wheres' are:
is_true - Is true. Useful for a checkbox like “only show admin users”.
is_false - The complement of is_true.
so what you need is to change the generate input name from 'completed_eq' to be 'completed_is_true' or 'completed_is_false'.
The only way I have found this possible to do is with Javascript, since by looking at the Active Admin code, the 'Wheres' are hardcoded for each data type.
I would usually have a line like this in my activeadmin.js file (using jQuery)
$('#q_completed_eq').attr('name', 'q[completed_is_true]');
or
$('#q_completed_eq').attr('name', 'q[completed_is_false]');
Terrible and ugly hack but have found no other solution myself.
Be careful to enable this only in the pages you want.
--- NEW FOR VERSION 0.4.2 and newer ---
Now Active Admin uses separate modules for each :as => ... option in the filters.
So for example you can place the code below inside an initializer file
module ActiveAdmin
module Inputs
class FilterCustomBooleanInput < ::Formtastic::Inputs::SelectInput
include FilterBase
def input_name
"#{#method}_is_true"
end
def input_options
super.merge(:include_blank => I18n.t('active_admin.any'))
end
def method
super.to_s.sub(/_id$/,'').to_sym
end
def extra_input_html_options
{}
end
end
end
end
and the use
:as => :custom_boolean
where you specify your filter.

DRYing up query on a Rails polymorphic assocation

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)

Named scopes for states in state_machine

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

How to override activerecord's default attribute column associations?

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.

Custom getters in Ruby on Rails

I have a MailingList model that has_may :people
For most of my application, I only want to get people that are active
So #mailing_list.people should only return people that are active
In my model, I can't do
def people
self.people.find_all{ |p| !p.activated_at.nil? }
end
because that keeps calling itself. What is the ruby/rails way to automatically filter the people. Another possible issue is that I think self.people returns an array of active record objects where self.people.find_all... will return an array. This will cause some of my code to break. It's easy fixes but is there a way to return active record objects? It would be nice to have the option.
Thanks!
This is a perfect example for a named scope:
class Person < ActiveRecord::Base
named_scope :active, :conditions => 'activated_at is not null'
end
Then just call it:
# equivalent to Person.find(:all, :conditions => 'activated_at is not null')
#active_people = Person.active
You can also filter at the association level.
has_many :people, :conditions => {:activated => true}
You can used the standard find method or a dynamic finder. Your find might read as follows:
people.find(:all, :conditions => "activated_at = nil")
OR
people.find_all(:conditions => "activated_at = nil")
A dynamic version of this might read as:
people.find_by_activated_at(nil)

Resources