Rails scope check serialize column is present - ruby-on-rails

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

Related

How to know if an "includes" call was made while working with a single record?

Motivation
The motivation was that I want to embed the serialization of any model that have been included in a Relation chain. What I've done works at the relation level but if I get one record, the serialization can't take advantage of what I've done.
What I've achieved so far
Basically what I'm doing is using the method includes_values of the class ActiveRecord::Relation, which simply tells me what things have been included so far. I.e
> Appointment.includes(:patient).includes(:slot).includes_values
=> [:patient, :slot]
To take advantage of this, I'm overwriting the as_json method at the ActiveRecord::Relation level, with this initializer:
# config/initializers/active_record_patches.rb
module ActiveRecord
class Relation
def as_json(**options)
super(options.merge(include: includes_values)) # I could precondition this behaviour with a config
end
end
end
What it does is to add for me the option include in the as_json method of the relation.
So, the old chain:
Appointment.includes(:patient).includes(:slot).as_json(include: [:patient, :slot])
can be wrote now without the last include:
Appointment.includes(:patient).includes(:slot).as_json
obtaining the same results (the Patient and Slot models are embedded in the generated hash).
THE PROBLEM
The problem is that because the method includes_values is of the class ActiveRecord::Relation, I can't use it at the record level to know if a call to includes have been done.
So currently, when I get a record from such queries, and call as_json on it, I don't get the embedded models.
And the actual problem is to answer:
how to know the included models in the query chain that retrieved the
current record, given that it happened?
If I could answer this question, then I could overwrite the as_json method in my own Models with:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
extend Associations
def as_json(**options)
super(options.merge(include: included_models_in_the_query_that_retrieved_me_as_a_record))
end
end
One Idea
One Idea I have is to overwrite the includes somewhere (could be in my initializer overwriting directly the ActiveRecord::Relation class, or my ApplicationRecord class). But once I'm there, I don't find an easy way to "stamp" arbitrary information in the Records produced by the relation.
This solution feels quite clumsy and there might be better options out there.
class ApplicationRecord < ActiveRecord::Base
def as_json(**options)
loaded_associations = _reflections.each_value
.select { |reflection| association(reflection.name).loaded? }
.map(&:name)
super(options.merge(include: loaded_associations))
end
end
Note that this only loads 1st level associations. If Appointment.includes(patient: :person) then only :patient will be returned since :person is nested. If you plan on making the thing recursive beware of circular loaded associations.
Worth pointing out is that you currently merge include: ... over the provided options. Giving a user no choice to use other include options. I recommend using reverse_merge instead. Or swap the placements around {includes: ...}.merge(options).

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

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.

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.

Extending ALL ActiveRecord SQL calls with additional conditions

I have an ActiveRecord model and the way it is setup, I never delete anything. Instead, I have a boolean column which is true if the record is deleted and false otherwise. I perform several .where queries on this model. I want a nice way to automatically append the condition, ":deleted => 0", to all my .where calls I make so that I never retrieve deleted records.
My initial instinct was to extend the 'where' method for ActiveRecord, but it seems that there is no where method.
What would be the best way to achieve the above functionality in the most object-oriented way?
Thanks.
Sounds like you want to set the default_scope:
Use this macro in your model to set a default scope for all operations on the model.
So something like this:
class M < ActiveRecord::Base
default_scope where(:deleted => 0)
#...
end

Resources