Check if scope is called on class or instance (scoped) - ruby-on-rails

Say I have a scope:
class Post
belongs_to: :user
scope(:visible_for, ->(user = default_user) { where("<some SQL>") })
end
Is there a way to check inside the scope whether it has been called 1. scoped or 2. unscoped?
some_user.posts.visible_for
Post.visible_for
Reason I want this is scoped automatically includes WHERE posts.user_id = <user_id> which optimizes the query, and I want only optimized query to be legal.

I just found you can call scope_attributes inside the scope, problem solved.

Related

Disable default scope for associations in module / Concern

I have a concern where I want to have unscoped association, But could not able to get it. In one of my instance method , I have a logic like that:
Assume I want to use only one parent (belongs_to)
parent_class_name = self.class.reflect_on_all_associations(:belongs_to).map(&:name).last
parent = nil
if parent_class_name.present?
parent_class_const = parent_class_name.to_s.camelize.constantize
parent_class_const.send(:unscoped) do
parent = self.send(parent_class_name)
end
end
The parent association has always the default scope, which should not be case. Similarly I have logic for has_many association, but that also does not work.
Looking forward for help.
P.S I don't want to use any gem
First, you can get the class constant directly:
parent_class = self.class.reflect_on_all_associations(:belongs_to).last.klass
Then you can call unscoped on the class directly:
parent_class.unscoped do
# do the unscoped stuff here
end

Rails scope check serialize column is present

I am learning the scope of rails
if QuestionSet has a column called questions_list and its format is serialize.
Like this
class QuestionSet < ActiveRecord::Base
serialize :questions_list
end
Then I have a method called is_order, and it is simple.
The only use is to check whether questions_list is present or not
Like this
def is_order
self.questions_list.present?
end
Can I write it into a scope? Or in this case, it is not a suitable scope scenario
scope is a wrong choice here, becuase it's always returns an Active Record and doesn't bound to any instance object, scope it's about collection.
Scoping allows you to specify commonly-used queries which can be
referenced as method calls on the association objects or models.
If your goal is to have a scope that will return QuestionSet records that have a questions_list present, you should be able to define a scope like this:
scope :with_questions_list, -> { where.not(questions_list: nil) }
And then you can do:
QuestionSet.with_questions_list # This is the same as QuestionSet.all.with_questions_list
If the goal is instead to build a method that will return true or false for a single object, then you are doing it correctly, but I'll suggest two changes: (1) You don't need to reference self (as that is implied in the context) and (2) you should use the Ruby convention of putting a question mark at the end of your method.
def is_order?
questions_list.present?
end

Specify currently grabbed records within Model class method

I have a class method where I want to modify the records that are currently grabbed by an ActiveRecord::Relation object. But I don't know how to refer to the current scope in a class method. self does not do it.
Example:
class User < ActiveRecord::Base
...
def self.modify_those_records
#thought implicitly #to_a would be called on currently grabbed records but doesn't work
temp_users_to_a = to_a
...
end
end
I would use it like this:
User.some_scope.modify_those_records
So User.some_scope would return to me an ActiveRecord::Relation that contains a bunch of User records. I then want to modify those records within that class method and then return them.
Problem is: I don't know how to explicitly refer to "that group of records" within a class method.
You can use current_scope:
def self.modify_those_records
current_scope.each do |user|
user.do_something!
end
end
If you want to order Users based on their admin rights, you would be better to use ActiveRecord:
scope :order_admins_first, order('CASE WHEN is_admin = true THEN 0 ELSE 1 END, id')
User.some_scope.order_admins_first
This code implies that you have a boolean column is_admin on the users table.
I would argue that a combination of a scope with each and an instance method is easier to understand than a class method. And as a bonus it is easier to test, because you can test all steps in isolation:
Therefore instead of User.some_scope.modify_those_records I would do something like:
User.some_scope.each(&:modify)
and implement a instance method:
# in user.rb
def modify
# whatever needs to be done
end
If you only want to modify the order of the records - better way is to add a sort field (if you do not have it already) to the model and sort by that.
User.some_scope.order(name: :asc).order(is_admin: :desc)

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.

Callback before association is called

I have to set the table name of an associated model (limesurvey), because the table name is dynamic and depends on an attribute (survey_id) of the model (task).
My current implementation sets the table name, when the task is initialized:
class task < ActiveRecord::Base
after_initialize :setTablename
has_one :limesurvey
def setTablename
Limesurvey.table_name = "lime_survey_#{self.survey_id}"
end
end
This implementation works, but it has the disadvantage, that the setTablename-method is called for every task, although it isn't needed.
How can I execute setTablename only before the association limesurvey is loaded?
Caveat: I agree that you are taking on a sea of troubles as the commenters have mentioned. Further, this will likely be worse, since before at lease setTablename was getting called for every task.
class Task < ActiveRecord::Base
has_one :limesurvey
def lime_survey
#table_name || = (Limesurvey.table_name = "lime_survey_#{self.survey_id}")
limesurvey
end
end
This defines a version of limesurvey with an underscore, but checks first if the table name has been set. Call lime_survey instead of limesurvey, and you will have the effect you asked for.
Similar to the approach suggested by Andy. However, although the association is just a method, I'm not sure you can redefine it and call super, since it's not a method in the parent class (or module).
Associations are just methods defined in a module that is included in your model, so you can override them as you would other methods
class Task < ActiveRecord::Base
has_one :limesurvey
def limesurvey
# do something here...
super
end
end
However, as people have mentioned in the comments on the question, what you're doing is a really bad idea. What would happen if you have two tasks available at once, and attempted to access the limesurvey on both of them?
t1 = Task.first
t2 = Task.last
l1 = t1.limesurvey
l2 = t2.limesurvey
l1.update_attributes(foo: "bar")
# Oops... saves into the wrong table...
Even if you manage to avoid doing this explicitly anywhere in your whole app, if you have two concurrent requests it could potentially happen accidentally!

Resources