How do you scope ActiveRecord associations in Rails 3? - ruby-on-rails

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)

Related

Rails 5 - using a scope in an edit action to find relevant children of a specific instance

I am trying to learn how to use scopes in my Rails 5 app.
I have asked a background question here.
have models in my Rails 5 app for User, Proposal and Potential.
Users create Proposals which they themselves and others can then create comments on.
The associations between models are:
User
has_many :proposals, dependent: :destroy
has_many :potentials
Proposal
belongs_to :user
has_many :potentials, inverse_of: :proposal
accepts_nested_attributes_for :potentials, reject_if: :all_blank, allow_destroy: true
Potential
belongs_to :proposal, inverse_of: :potentials
belongs_to :user
In my routes file, I have two resources for potentials. I'm not sure if I've gone off piste with this bit- I cant find an example of how to do this otherwise. I have both:
resources :potentials
as well as:
resources :proposals do
resources :potentials
Objective:
When the user who made the proposal tries to edit it, I only want that user to be able to edit the potentials that they created themselves.
The reason I have two routes set up for potentials is that the nested resource has a nested form fields inside the proposal form, so that the proposal creator can make a potential in that way. Any other user that sees the proposal and makes a potential does it via a separate form.
Any user (including the proposal creator, can edit the potential via that separate form), and the proposal creator can also edit any of its own proposals by the nested form in the proposal form.
At the moment, whenever I edit the proposal form (even when I don't edit the potential nested fields), all of the potentials are updated to insert the proposal creator's user id overriding the actual potential creator's user id.
Solution
I am trying to limit the edit action in the proposals controller, so that it only allows the proposal /potentials to be edited if they have the user_id == the proposal.user_id.
For this purpose, I have written scopes in my proposal.rb
scope :owner_potentials, ->{ where(user_id: potential.user_id ) }
scope :third_party_potentials, ->{ where(user_id: != potential.user_id) }
The solution in the post i liked above was to try using a scope. Since scopes are meant to work on the class, rather than an instance, I'm stuck in trying to figure out how to adapt them so that I can use the scope to search for all the compliant potentials (i.e. potentials where potential.user_id == proposal.user_id). That means Im not searching the Proposal class, Im searching the specific proposal.
This post suggested defining Event.all inside the relevant controller action, but then how would I limit that so it only applied to the specific potentials edit line? I have other lines in my edit action which should not be tested on the Proposal table, but just the instance. If this were able to work, I imagine I would then need to rewrite my scope to try to exclude all the other proposals.
Is there a way to use an edit action in a controller with a scope, on a specific instance?
I would suggest scopes like this:
scope :owner_potentials, -> (user_id) { where(user_id: user_id) }
scope :third_party_potentials, -> (user_id) { where.not(user_id: user_id) }
When calling these scopes you just need to pass current user's id.
Scopes define queries for the AR class they are defined in. You say you have written owner_potentials and third_party_potentials scopes in proposal.rb. But if these scopes are meant to return a collection of potentials, then these should be defined in the Potential class. If you need to access these scopes from a proposal record, you can chain scopes to associations, e.g.
class Potential
scope :owner_potentials, -> (user) { where(user: user) }
scope :third_party_potentials, -> (user) { where.not(user: user) }
end
...
class ProposalsController # Proposals::PotentialsController..? imo Proposals::PotentialsController#edit sounds like an endpoint for editing exactly one potential record and nothing else, which doesn't sound like what you want. Your call on how to structure the controller/routes though.
def edit
#proposal = ... # Whatever your logic is to find the proposal
#proposal.potentials.owner_potentials(current_user) # do something with the user's potentials
#proposal.potentials.third_party_potentials(current_user) # do something with the potentials the user doesn't own
end
end
You can see here how you chain an association (.potentials) to a scope (.owner_potentials).
Also, if you have an association, you can treat that association as a field in a where method, a la where(user: user) instead of where(user_id: user.id).
Last thing to note is that you probably want to change the name of the scopes with this refactor.
potentials.owner_potentials(user) is a bit redundant. Maybe something like potentials.owned_by(user) ?

How to make default_scope in Rails model dynamic?

In one of my Rails models I have this:
class Project < ActiveRecord::Base
belongs_to :user
default_scope order("number ASC")
end
Now the problem is that I want each user to be able to set his or her default_scope individually. For example, a user A might want default_scope order("date ASC"), another one might want default_scope order("number DESC").
In my User table I even have columns to store these values: order_column and order_direction.
But how can I make the default_scope in the model dynamic?
Thanks for any help.
As #screenmutt said, default scopes are not meant to be data-driven, they are meant to be model driven. Since this scope is going to change according to each user's data I'd use a regular scope for this.
#fmendez answer is pretty good but it uses default scope which I just explained why it is not recommended using this method.
This is what I'd do in your case:
class Post < ActiveRecord::Base
scope :user_order, lambda { order("#{current_user.order_column} #{current_user.order_direction}")}
end
Also a very important thing to notice here is SQL injection: Since you are embedding current_user.order_column and current_user.order_direction inside your query, you MUST ensure that the user can only feed these columns into the database with valid data. Otherwise, users will be able to craft unwanted SQL queries.
You won't want to use default_scope. What you do what is regular scope.
class Post < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
Scope | Ruby on Rails
You could do something like this:
def self.default_scope
order("#{current_user.order_column} #{current_user.order_direction}")
end
This should dynamically pick the values stored in the current_user's order_column and order_direction columns.
You can define a class method with whatever logic you require and set your default scope to that. A class method is identical to a named scope when it returns a relation,eg by returning the result of a method like order.
For example:
def self.user_ordering
# user ording logic here
end
default_scope :user_ordering
You may want to add a current_user and current_user= class methods to your User model which maintains the request user in a thread local variable. You would typically set the current user on your User model from your application controller. This makes current_user available to all your models for logic such as your sorting order and does it in a thread safe manner.

Conditional associations or scope?

Let's say I have two models
class Client < ActiveRecord::Base
has_many :accounts
end
class Account < ActiveRecord::Base
belongs_to :client
end
Now, I want to have convenient method to access approved accounts(:approved => true).
What is better to use, and why?
has_many: approved_accounts, -> { where :approved => true } for Client model
or
scope: approved, -> { where :approved => true } for Account model
Short answer, It Depends. For Long answer, kindly read on...
Conditional associations allows us to Customize the query that ActiveRecord will use to fetch the association. Use when you are sure that this condition is permanent & you will never-ever need access to data that doesn't fit the conditional(atleast not in this model) as Conditional associations are APPLIED to every query to ActiveRecord does to the association from that particular model.
Scope It is basically, a class method for retrieving and querying objects. so what you are actually doing is defining the following method in your model.
class Client < ActiveRecord::Base
self.approved
where(:approved => true)
end
end
so scope is generally use to define short names for one or more regularly used query customization. But important difference is that scope is not auto-applied unless you use default_scope but conditional associations are auto-applied.
In your case, you want to show unapproved accounts in this model? If not, then use conditional association. Else use scope
According to me, the scope solution seems the better:
As you probably know, to get the approved accounts of a client, you can do: approved_accounts = client.accounts.approved which is not less legible than: approved_accounts = client.approved_accounts
So not many difference here. But if in the future you want the list of all approved accounts (for statistics or whatever), with the scope solution, a single approved_accounts = Account.approved will be enough. But if you choose the client side, it will be trickier to get (understand: you will have to use the Client model).
Just consider than being approved is a property of an Account more than a property of a Client and it should be clearer that the scope is the best solution.
Hope this clarifies things.

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)} }

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