Getting list of states/events from a model that AASM - ruby-on-rails

I successfully integrated the most recent AASM gem into an application, using it for the creation of a wizard. In my case I have a model order
class Order < ActiveRecord::Base
belongs_to :user
has_one :billing_plan, :dependent => :destroy
named_scope :with_user, ..... <snip>
include AASM
aasm_column :aasm_state
aasm_initial_state :unauthenticated_user
aasm_state :unauthenticated_user, :after_exit => [:set_state_completed]
aasm_state : <snip>
<and following the event definitions>
end
Now I would like to give an administrator the possibility to create his own graphs through the AASM states. Therefore I created two additional models called OrderFlow and Transition where there order_flow has many transitions and order belongs_to order_flow.
No problem so far. Now I would like to give my admin the possibility to dynamically add existing transitions / events to an order_flow graph.
The problem now is, that I do not find any possibility to get a list of all events / transitions out of my order model. aasm_states_for_select seems to be the correct candidate, but I cannot call it on my order model.
Can anyone help?
Thx in advance.
J.

With version 3.0.18, you can should use ClassName.aasm.states

I don't understand how OrderFlow works with Order and Transitions, but I assumed you just included that to explain your scenario better.
ClassName.aasm_states_for_select gives you a list of states that is declared in the model.

For events in 3.1.1 I used Model.aasm.events.keys to get an array of event name symbols. In recent versions .map(&:name) won't do it for you.

also, not 100% sure what you're asking for, but if you want all the states and events declared for your model you could get those by calling Order.aasm_states and Order.aasm_events respectively.

A more elegant Ruby syntax can be used, as in this example in IRB below. You get all the allowable states in an array of symbols.
1.9.3-p0 :011 > ApprovalRequest.aasm_states.map(&:name)
=> [:created, :submitted, :rejected, :approved]

In version 5.4.0 this seems to now be an array, so we are back to
Model.aasm.events.map(&:name)

Related

What is the correct Rails association for this design?

Environment: Ruby 2.0.0, Rails 4.1, Windows 8.1, Devise
I am confused as to how to handle this issue. I have items that are stored at a location until they are checked out by a user. I want to be able to query the item and determine which location or which user currently holds it. So, the item will belong to either a location or a user. Each location and each user can have many items.
This is something like a large library system where the book could be in one of several buildings and a patron could check out books from each of the buildings. At any point, I'd like to locate a book as either in the building or with the patron.
My confusion stems mainly from the fact that the item would be associated with either of two different models at different points in time. It could be at a location or it could be held by a user, and should never be both.
What association or design would work for this? Thanks....
I would use a polymorphic association with this, try something like this:
Item.rb
belongs_to :check_outer, :polymorphic => true
User.rb
has_many :items, :as => :check_outer
Location.rb
has_many :items, :as => :check_outer
You will need the proper database columns to support this.
On the items table, add check_outer_type:string and check_outer_id:int
For more information on polymorphic associations, check this out: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Referring to instance in has_many (Rails)

I have a Game model which has_many :texts. The problem is that I have to order the texts differently depending on which game they belong to (yes, ugly, but it's legacy data). I created a Text.in_game_order_query(game) method, which returns the appropriate ordering.
My favourite solution would have been to place a default scope in the Text model, but that would require knowing which game they're part of. I also don't want to create separate classes for the texts for each game - there are many games, with more coming up, and all the newer ones will use the same ordering. So I had another idea: ordering texts in the has_many, when I do know which game they're part of:
has_many :texts, :order => Text.in_game_order_query(self)
However, self is the class here, so that doesn't work.
Is there really no other solution except calling #game.texts.in_game_order(#game) every single time??
I had a very similar problem recently and I was convinced that it wasn't possible in Rails but that I learned something very interesting.
You can declare a parameter for a scope and then not pass it in and it will pass in the parent object by default!
So, you can just do:
class Game < ActiveRecord
has_many :texts, -> (game) { Text.in_game_order_query(game) }
Believe or not, you don't have to pass in the game. Rails will do it magically for you. You can simply do:
game.texts
There is one caveat, though. This will not work presently in Rails if you have preloading enabled. If you do, you may get this warning:
DEPRECATION WARNING: The association scope 'texts' is instance dependent (the scope block takes an argument). Preloading happens before the individual instances are created. This means that there is no instance being passed to the association scope. This will most likely result in broken or incorrect behavior. Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
Following up using PradeepKumar's idea, I found the following solution to work
Assuming a class Block which has an attribute block_type, and a container class (say Page), you could have something like this:
class Page
...
has_many :blocks do
def ordered_by_type
# self is the array of blocks
self.sort_by(&:block_type)
end
end
...
end
Then when you call
page.blocks.ordered_by_type
you get what you want - defined by a Proc.
Obviously, the Proc could be much more complex and is not working in the SQL call but after there result set has been compiled.
UPDATE:
I re-read this post and my answer after a bunch of time, and I wonder if you could do something as simple as another method which you basically suggested yourself in the post.
What if you added a method to Game called ordered_texts
def ordered_texts
texts.in_game_order(self)
end
Does that solve the issue? Or does this method need to be chainable with other Game relation methods?
Would an Association extension be a possibility?
It seems that you could make this work:
module Legacy
def legacy_game_order
order(proxy_association.owner.custom_texts_order)
end
end
class Game << ActiveRecord::Base
includes Legacy
has_many :texts, :extend => Legacy
def custom_texts_order
# your custom query logic goes here
end
end
That way, given a game instance, you should be able to access instance's custom query without having to pass in self:
g = Game.find(123)
g.texts.legacy_game_order
Here is a way where you can do it,
has_many :texts, :order => lambda { Text.in_game_order_query(self) }
This is another way which I usually wont recommend(but will work),
has_many :texts do
def game_order(game)
find(:all, :order => Text.in_game_order_query(game))
end
end
and you can call them by,
game.texts.game_order(game)
Im not sure what your order/query looks like in the in_game_order_query class method but i believe you can do this
has_many :texts, :finder_sql => proc{Text.in_game_order_query(self)}
Just letting you know that I have never used this before but I would appreciate it if you let me know if this works for you or not.
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many for more documentation on :finder_sql
I think if you want runtime information processed you should get this done with:
has_many :texts, :order => proc{ {Text.in_game_order_query(self)} }

How do you scope ActiveRecord associations in Rails 3?

I have a Rails 3 project. With Rails 3 came Arel and the ability to reuse one scope to build another. I am wondering if there is a way to use scopes when defining a relationship (e.g. a "has_many").
I have records which have permission columns. I would like to build a default_scope that takes my permission columns into consideration so that records (even those accessed through a relationship) are filtered.
Presently, in Rails 3, default_scope (including patches I've found) don't provide a workable means of passing a proc (which I need for late variable binding). Is it possible to define a has_many into which a named scope can be passed?
The idea of reusing a named scope would look like:
Orders.scope :my_orders, lambda{where(:user_id => User.current_user.id)}
has_many :orders, :scope => Orders.my_orders
Or implicitly coding that named scope in the relationship would look like:
has_many :orders, :scope => lambda{where(:user_id => User.current_user.id)}
I'm simply trying to apply default_scope with late binding. I would prefer to use an Arel approach (if there is one), but would use any workable option.
Since I am referring to the current user, I cannot rely on conditions that aren't evaluated at the last possible moment, such as:
has_many :orders, :conditions => ["user_id = ?", User.current_user.id]
I suggest you take a look at "Named scopes are dead"
The author explains there how powerful Arel is :)
I hope it'll help.
EDIT #1 March 2014
As some comments state, the difference is now a matter of personal taste.
However, I still personally recommend to avoid exposing Arel's scope to an upper layer (being a controller or anything else that access the models directly), and doing so would require:
Create a scope, and expose it thru a method in your model. That method would be the one you expose to the controller;
If you never expose your models to your controllers (so you have some kind of service layer on top of them), then you're fine. The anti-corruption layer is your service and it can access your model's scope without worrying too much about how scopes are implemented.
How about association extensions?
class Item < ActiveRecord::Base
has_many :orders do
def for_user(user_id)
where(user_id: user_id)
end
end
end
Item.first.orders.for_user(current_user)
UPDATE: I'd like to point out the advantage to association extensions as opposed to class methods or scopes is that you have access to the internals of the association proxy:
proxy_association.owner returns the object that the association is a part of.
proxy_association.reflection returns the reflection object that describes the association.
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for has_many or has_and_belongs_to_many.
More details here: http://guides.rubyonrails.org/association_basics.html#association-extensions
Instead of scopes I've just been defining class-methods, which has been working great
def self.age0 do
where("blah")
end
I use something like:
class Invoice < ActiveRecord::Base
scope :aged_0, lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end
You can use merge method in order to merge scopes from different models.
For more details search for merge in this railscast
If you're just trying to get the user's orders, why don't you just use the relationship?
Presuming that the current user is accessible from the current_user method in your controller:
#my_orders = current_user.orders
This ensures only a user's specific orders will be shown. You can also do arbitrarily nested joins to get deeper resources by using joins
current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)

undefined_method 'tag_count' using acts_as_taggable_on_steroids

I am using acts_as_taggable to create a tag cloud for my application. I have a model 'Question' which is using the acts_as_taggable plug-in. I basically want to filter the tags for the question model.
I also have a 'Subject' model. So the relation between subjects and questions is that a subject has many questions, and a question belongs to a subject.
So when i call #subject.questions.tag_counts, it works fine. But say I call
#subject.free_questions.tag_counts, where free_questions is a method I've defined, it gives a me an "undefined_method tag_counts for #<Array>. I basically want to find all the tags for a subset of the questions.
Can anybody suggest a workaround?
It may help to implement free_questions as a named_scope so you can call association methods on it.
Something like:
class Question < ActiveRecord::Base
named_scope :free, :conditions => {:free => true} # conditions that make a question 'free'
end
Then you can:
#subject.questions.free
and I suspect this may work as well. (don't have a lot of experience with acts_as_taggable)
#subject.questions.free.tag_counts
When you use a named_scope (instead of a model method you've defined) you get back a proxy object that looks and acts like an Array, but allows you to chain ActiveRecord association methods onto it. Any methods that work on #subject.questions you should be able to call on #subject.questions.free.

Overwriting/Adding an ActiveRecord association dynamically using a singleton class

The business logic is this: Users are in a Boat through a join table, I guess let's call that model a Ticket. But when a User instance wants to check who else is on the boat, there's a condition that asks if that user has permission see everyone on the Boat, or just certain people on the Boat. If a User can see everyone, the normal deal is fine: some_user.boats.first.users returns all users with a ticket for that boat. But for some users, the only people that are on the boat (as far as they're concerned) are people in, let's say the dining room. So if User's ticket is "tagged" (using an acts_as_taggable style system) with "Dining Room", the only Users returned from some_user.boats.first.users should be Users with tickets tagged "Dining Room".
Just for the record, I'm not trying to design something to be insane from the getgo - I'm trying to wedge this arbitrary grouping into a (mostly) existent system.
So we've got:
class User
has_many :tickets
has_many :boats, :through => :tickets
end
class Ticket
belongs_to :user
belongs_to :boat
end
class Boat
has_many :tickets
has_many :users, :through => :tickets
end
Initially, I thought that I could conditionally modify the virtual class like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
has_many :tickets, :include => :tags, :conditions => ['tags.id in (?)', [#{tag_ids.to_s(:db)}]]
code
)
That gets all the way down to generating the SQL, but when generated, it generates SQL ending in:
LEFT OUTER JOIN "tags" ON ("tags"."id" = "taggings"."tag_id") WHERE ("tickets"._id = 1069416589 AND (tags.id in (5001,4502)))
I've tried digging around the ActiveRecord code, but I can't find anywhere that would prefix that 'id' in the SQL above with an underscore. I know that associations are loaded when an ActiveRecord class is loaded, and I'd assume the same with a singleton class. shrug.
I also used an alias_method_chain like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
def tickets_with_tag_filtering
tags = Tag.find(etc, etc)
tickets_without_tag_filtering.scoped(:include => :tags, :conditions => {:'tags.id' => tags})
end
alias_method_chain :tickets, :tag_filtering
code
)
But while that approach produces the desired Tickets, any joins on those tickets use the conditions in the class, not the virtual class. some_user.boats.first.users returns all users.
Any type of comment will be appreciated, especially if I'm barking up the wrong tree with this approach. Thanks!
So a wild guess about your underscore issue is that Rails is generating the assocation code based on the context at the time of evaluation. Being in a singleton class could mess this up, like so:
"#{owner.table_name}.#{association.class.name}_id = #{association.id}"
You could get in there and define a class name property on your singleton class and see if that fixes the issue.
On the whole I don't recommend this. It creates behavior that is agonizing to track down and impossible to extend effectively. It creates a landmine in the codebase that will wound you or someone you love at a later time.
Instead, consider using a named_scope declaration:
class User
has_many :taggings, :through => :tickets
named_scope :visible_to, lambda { |looking_user|
{ :include => [ :tickets, :taggings ],
:conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ]
}
}
end
While you may have to go back and edit some code, this is much more flexible in the ways it can be used:
Boat.last.users.visible_to( current_user )
It's clear that a restriction is being placed on the find, and what the purpose of that restriction is. Because the conditions are dynamically calculated at runtime, you can deal with the next weird modification your client hits you with. Say some of their users have xray vision and clairvoyance:
class User
named_scope :visible_to, lambda { |looking_user|
if looking_user.superhuman?
{}
else
{ :include => [ :tickets, :taggings ],
:conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ]
}
end
}
end
By returning an empty hash, you can effectively nullify the effect of the scope.
Why not just grab all users on the boat and include their tags.
Then run a quick filter to include & return only the users with the same tag as the inquiring user.
What version of Rails are you using? Have you tried upgrading to see if the underscore issue is fixed? It's like it can't find the foreign key to put in as "tag_id" or somethin'.
My ruby-fu is limited, so I'm not sure how to dynamically include the correct method options at run-time.
Just to help you clarify, you have to worry about this two places. You want to filter a user's viewable users so they only see users with the same tags. Your structure is:
user <--> tickets <--> boats <--> tickets <--> users
... right?
So, you need to filter both sets of tickets down to the ones with the current_user's tags.
Maybe you just need a current_user.viewable_users() method and then filter everything through that? I'm not sure what existing functionality you've got to preserve.
Blech, I don't feel like I'm helping you at all. Sorry.
Your approach is the problem. I know it seems expedient at the moment to hack something in where you don't have to refactor the existing call sites, but I believe given time this will come back to haunt you as the source of bugs and complexity.
Sleeping dogs that lie come back to bite you hard, in my experience. Typically in the form of a future developer who doesn't know your association is "magic" and uses it assuming it's just pail ole rails. He/she likely won't even have a reason to write a test case that would expose the behavior either, which raises the odds you'll only find out about the bug when it's live in production and the client is unhappy. Is it really worth the time you're saving now?
Austinfrombostin is pointing the way. Different semantics? Different names. Rule number one is always to write code that says what it does as clearly as possible. Anything else is the path of madness.

Resources