Alternative to default_scope in ActiveRecord - ruby-on-rails

I'm facing a problem with changing the functionality of an app and having to re-write about 700 method calls that now need to be scoped.
I've been looking into how default_scope works, and like most people I'm finding that it's close but not quite helpful for me, because I can't override it very easily.
What rails currently offers to override a default_scope is the unscoped method. The problem with unscoped is that it completely removes all scope, not just the default scope.
I'd really like some input from a Rails / ActiveRecord guru on some alternatives.
Given this basic desired functionality...
class Model < ActiveRecord::Base
...
belongs_to :user
...
default_scope where(:foo => true)
scope :baz, where(:baz => '123')
scope :sans_foo, without_default.where(:foo=>true)
...
end
Could you / how could you create a method that could remove the default scope while leaving the other scoping intact? IE, currently if you use...
user.models.unscoped.where(something)
... it's the same as calling
Model.where(something)
Is it possible to define a method that would allow you to instead do something like this...
user.models.without_default.where(something)
...where the result would still be scoped to user but not include the default scope?
I'd very, very much appreciate any help or suggestions on how such a functionality might be implemented.

You have to use with_exclusive_scope.
http://apidock.com/rails/ActiveRecord/Base/with_exclusive_scope/class
User.with_exclusive_scope do
user.models.baz.where(something)
end

Related

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.

Override mongomapper has_many association

I use Rails 3 with MongoMapper.
I want to add some records to the result of has many association.
For example, user has_many posts
class User
include MongoMapper::Document
many :posts
end
By default it will show only posts which belongs to the user, but if he/she specify special option in query (or in the user's settings menu, say show-commented=true), then I also need to add posts where user left any comments. So I think to override posts method
def posts
super + (show_commented_posts ? commented_posts : [])
end
But of course it doesn't work. How can I correctly override this method using mongo_mapper? Or is there any better approach for that problem?
Overriding methods on mongomapper is a very bad idea, you should try to refrain from doing it as it creates a lot of problems that are hard to trace back (I've been burned before by this).
Instead, you should consider using a scope such as
class Post
scope :related_to_user, lambda {|user| where('$or' => [ {user_id: user.id}, {'comments.user_id' => user.id}]) }
end
Then you can call
Post.related_to_user(current_user)

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.

Resources