I would like to create one lambda and use it in multiple models.
For example:
scope :get_belongs_to_patient, lambda { |patient_id| where(patient_id: patient_id).order(created_at: :desc) }
I should use this lambda in ten models (the same code). So, have any way to do this?
Thanks!
I would use a concern. Here's a good article on them.
Name it "BelongsToPatient" or whatever makes sense to you. You want to be describing all of the "things" that are belonging to Patient.
Example:
# app/models/concerns/belongs_to_patient.rb
require 'active_support/concern'
module BelongsToPatient
extend ActiveSupport::Concern
included do
scope :get_belongs_to_patient, lambda { |patient_id| where(patient_id: patient_id).order(created_at: :desc) }
end
end
Then, include the module in all of the models where you want to have the scope:
class MyModel < ActiveRecord::Base
include BelongsToPatient
end
MyModel will now have access to your :get_belongs_to_patient scope.
Related
I'd like to create a gem that modifies ActiveRecord::Base select methods. For instance, when I include my gem and type:
Somemodel.all
it should return an array ordered by id as normally but in descending order instead of ascending. I have no clue how it should look. I would not like to create additional methods like:
Somemodel.where(name: "John").revert_it
but simply do:
Somemodel.where(name: "John")
I was wondering about modifying ActiveRecord::Base methods, but it does not make any sense. IMO the best way is to callback after every ActiveRecord::Base method that will do it.
You can use the ActiveRecord method default_scope to achieve this:
class MyModel < ActiveRecord::Base
default_scope { order("id DESC") }
end
MyModel.all
# => SELECT * FROM my_models ORDER BY id DESC
It's not advisable to modify any core ActiveRecord methods (unless you have a really good reason), because that will make lot of confusion in future.
Even if you are thinking of modifying the select , you have make sure that you always return an ActiveRecord relation , so that it can be chained with as the standard way,
For your example, AR already has a method
User.all.reverse
I got fully valid answer:
def self.included(base)
base.class_eval do
default_scope { order(id: 'desc') }
end
end
Above method should be inside some module and then module included within some model class
I have model user.rb and concern query_filter.rb.
module QueryFilter
extend ActiveSupport::Concern
def apply(attr)
end
end
class User < ActiveRecord::Base
extend QueryFilter
end
I would like to apply filters for whole model or for query.
For example:
> User.apply(attributes)
=> #query
> User.where(sex: 'male').apply(attributes)
=> #query
I have two problems.
First of all I don't know how can I access to query on which I have called my method in module method?
Secondly User.apply(attributes) won't work, I can use User.all.apply(attributes) but that's not the case. Is there any possibility to call method right after class name nor query ?
You have to include QueryFilter instead of extend
see http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
But I think for the desired effect you should look into scopes more than concerns.
Some of my models has a column named "company_id".
I need that all querys in these models has a condition based in this column, so I can easily separate the companies rows.
Something like this:
Customer.where(state: x).`where(company_id: current_company)`...
How can I intercept this method to enforce this extra condition?
I would recommend using a concern to add this requirement as a default scope to all of your models.
module HasCompany
extend ActiveSupport::Concern
included do
default_scope { where(company_id: current_company) }
end
end
class Customer < ActiveRecord::Base
include HasCompany
...
end
Note: this approach will only work if you have access to current_company as a class method on your models.
Where does this code live? It looks like controller logic? If it's in a controller, then you can just set the current_company in a before_action in the application controller—probably like you're doing already. Presuming you have a has_many relationship between company and customers, you should just do current_company.customers.where(state: x).
If this code lives in a model, that's when things get tricky. You shouldn't have access to current_company in a model, since that deals with the current request.
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.
I have several objects that all have an approved field.
What would be the best way to implement a scope to use across all models?
For example, I have a sighting object and a comment object. They both have to be approved by an admin before being availalbe to the public.
So how can I create a scope that returns comment.approved as well as sighting.approved respectively without repeating it on each model? Is this where concerns comes into play?
While just declaring a scope in each model is fine if you just want the scoping functionality. Using an ActiveSupport::Concern will give you the ability to add additional methods as well if that's something you think is going to happen. Here's an example:
# /app/models/concerns/approved.rb
module Approved
extend ActiveSupport::Concern
included do
default_scope { where(approved: false) }
scope :approved, -> { where(approved: true) }
end
def unapprove
update_attribute :approved, false
end
end
class Sighting < ActiveRecord::Base
include Approved
end
class Comment < ActiveRecord::Base
include Approved
end
Then you can make calls like Sighting.approved and Comment.approved to get the appropriate list of approved records. You also get the unapprove method and can do something like Comment.approved.first.unapprove.
In this example, I've also included default_scope which will mean that calls like Sighting.all or Comment.all will return only unapproved items. I included this just as an example, it may not be applicable to your implementation.
Although I've noticed the scope pulled from the concerns needs to be the last scope when concatenating scopes. I'm not quite sure why.
Comment.existing.approved
When I tried it as:
Comment.approved.existing
It failed silently.
And I take that back. I was migrating older code and using scopes with conditions instead of lambdas. When I replaced :conditions the scope order no longer mattered.
scope :existing, -> { where("real = 1") }
replaced
scope :existing, :conditions => "real = 1"