Named scopes for states in state_machine - ruby-on-rails

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

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.

Scopes vs class methods in Rails 3

Are scopes just syntax sugar, or is there any real advantage in using them vs using class methods?
A simple example would be the following. They're interchangeable, as far as I can tell.
scope :without_parent, where( :parent_id => nil )
# OR
def self.without_parent
self.where( :parent_id => nil )
end
What is each of the techniques more appropriate for?
EDIT
named_scope.rb mentions the following (as pointed out below by goncalossilva):
Line 54:
Note that this is simply 'syntactic
sugar' for defining an actual class
method
Line 113:
Named scopes can also have extensions,
just as with has_many declarations:
class Shirt < ActiveRecord::Base
scope :red, where(:color => 'red') do
def dom_id
'red_shirts'
end
end
end
For simple use cases, one can see it as just being syntax sugar. There are, however, some differences which go beyond that.
One, for instance, is the ability to define extensions in scopes:
class Flower < ActiveRecord::Base
named_scope :red, :conditions => {:color => "red"} do
def turn_blue
each { |f| f.update_attribute(:color, "blue") }
end
end
end
In this case,turn_blueis only available to red flowers (because it's not defined in the Flower class but in the scope itself).

User status field, how to map to an enum

I have a User model, and I want to have a user_status attribute.
I want this attribute to be stored as an integer in the database.
I was thinking of then creating a enum, and then mapping that enum to the integer value so I could do things like:
if user.status == MyEnum::RequiresApproval
..
..
Using active_record, at the model level, is there something I can do with the enum?
What is normally done in this kind of situation?
Enums are not very Rails like. State Machines are.
Check out the 'transitions' gem (link) (which was almost part of Rails Core)
And then you can do the following ...
#GemFile
gem "transitions", :require => ["transitions", "active_record/transitions"]
#And in your Model, do something like the following:
include ActiveRecord::Transitions
field :state, type: String
scope :active, where(state: 'active')
state_machine do
state :active
state :inactive
event :inactivate do
transitions :from => :active, :to => :inactive
end
event :activate do
transitions :from => :inactive, :to => :active
end
end
It was a transition for me too not to use enums and type tables -- but I haven't missed them

Rails: Storing static data like ActiveRecords

I've been looking for a while for gems and/or plugins that implement static storage similar ActiveRecords but is not database-based. Let's call this class NonDBRecord. It should have the following property:
class Foo < NonDBRecord
add_item('a', :property1 => 'some value', :property2 => 'some more value')
add_item('b', :property1 => 'some value', :property2 => 'some more value')
end
class Bar < ActiveRecord::Base
belongs_to_nondbrecord :foo, :class_name => 'Foo'
end
# NonDBRecord declare constants automatically
[ Foo::A, Foo::B ]
# NonDBRecord is enumerable
Foo.all # returns [Foo::A,Foo::B]
# NonDBRecord is id-based
Bar.create(:foo_id => Foo::A.id)
# ...so you can search by it
x = Bar.find(:first, :conditions => { :foo_id => Foo::A.id })
# ...and is stored, retrieved, and instantiated by its id
x.foo # returns Foo::A
I've thought about simply using ActiveRecords (and database storage), but I don't feel good about it. Plus I've had to tip-toe around some eager loading problems with the ActiveRecord solution. Any help would be appreciated before I start writing my own solution.
edit
These records are meant to be enumerations. For example, let's say you're making a card game. I want to be able to do something like
class Card < NonDBRecord
attr_reader :suit, :index
end
class Game
belongs_to :wild_card, :class_name => 'Card'
end
I would say ActiveModel is what you are looking for. It comes with Rails 3 and encapsulates all kind of goodies from ActiveRecord, such as Validation, Serialization and sorts. There is a Ryan Bates railscast on that issue. Hope this helps!
As BigD says, ActiveModel is the Rails 3 way.
In Rails 2.3 I am using this as a kluge:
class TablelessModel < ActiveRecord::Base
def self.columns() #columns ||= []; end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
def save(validate = true)
validate ? valid? : true
end
end
I use that to e.g. validate contact forms that are not going to persist in any way. It's possible it could be extended for your specific purposes.

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