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)
Related
In our Rails application, the Post resource can be made by either a User or an Admin.
Thus, we have an ActiveRecord model class called Post, with a belongs_to :author, polymorphic: true.
However, in certain conditions, the system itself is supposed to be able to create posts.
Therefore, I'm looking for a way to add e.g. System as author.
Obviously, there will only ever be one System, so it is not stored in the database.
Naïvely attempting to just add an instance (e.g. the singleton instance) of class System; end as author returns errors like NoMethodError: undefined method `primary_key' for System:Class.
What would be the cleanest way to solve this?
Is there a way to write a 'fake' ActiveRecord model that is not actually part of the database?
There's two ways that I see that make the most sense:
Option A: Add a 'system' Author record to the DB
This isn't a horrible idea, it just shifts the burden onto you making sure certain records are present in every environment. But you can always create these records in seed files if you want to ensure they're always created.
The benefit over option B is that you can just use standard ActiveRecord queries to find all of the system's Posts.
Option B: Leave the association nil and add a new flag for :created_by_system
This is what I would opt for. If a Post was made by the system, just leave the author reference blank and set a special flag to indicate this model was created internally.
You can still have a method to quickly get a list of all of them just by making a scope:
scope :from_system, -> { where(created_by_system: :true) }
Which one you choose I think depends on whether you want to be able to query Post.author and get information about the System. In that case you need to take option A. Otherwise, I would use option B. I'm sure there's some other ways to do it too but I think this makes the most sense.
Finally I ended up with creating the following 'fake' model class that does not require any changes to the database schema.
It which leverages a bit of meta-programming:
# For the cases in which the System itself needs to be given an identity.
# (such as when it does an action normally performed by a User or Admin, etc.)
class System
include ActiveModel::Model
class << self
# The most beautiful kind of meta-singleton
def class
self
end
def instance
self
end
# Calling`System.new` is a programmer mistake;
# they should use plain `System` instead.
private :new
def primary_key
:id
end
def id
1
end
def readonly?
true
end
def persisted?
true
end
def _read_attribute(attr)
return self.id if attr == :id
nil
end
def polymorphic_name
self.name
end
def destroyed?
false
end
def new_record?
false
end
end
end
Of main note here is that System is both its own class and its own instance.
This has the following advantages:
We can just pass Post.new(creator: System) rather than System.new or System.instance
There is at any point only one system.
We can define the class methods that ActiveRecord requires (polymorphic_name) on System itself rather than on Class.
Of course, whether you like this kind of metaprogramming or find it too convoluted is very subjective.
What is less subjective is that overriding ActiveRecord's _read_attribute is not nice; we are depending on an implementation detail of ActiveRecord. Unfortunately to my knowledge there is no public API exposed that could be used to do this more cleanly. (In our project, we have some specs in place to notify us immediately when ActiveRecord might change this.)
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
I have a user model.
I'm trying to set my user index view to show only those users who have completed an onboarding process.
My approach to doing that is:
index:
<% Users.onboarded.each do |user| %>
In my user.rb, I tried to define a scope, for onboarding, as:
scope :onboarded, -> { where (:user_is_matchable?) }
I have a method in my organisation service class which has:
class UserOrganisationMapperService
attr_accessor :user
private
def user_is_matchable?
profile.present? && matching_organisation.present?
end
When i try this, I get an error that says:
undefined method `onboarded' for Users:Module
Can anyone see where I've gone wrong?
Firstly: Users or User in your user.rb file.. what's the actual name of the class? because it should be User not Users
Secondly: scope :onboarded, -> { where (:user_is_matchable?) } this is just not going to work. a scope is an ActiveRecord query - it can only deal with details of the actual structure in the database. if you don't have a column in your users table called user_is_matchable? then this scope will complain and not work.
you need to make that scope into something that would work just on the database.
I'm only guessing (you haven't given us the full structure of your relations here) but would you be able to do something like:
scope :onboarded, -> { where ("profile_id IS NOT NULL AND matching_organisation_id IS NOT NULL") }
???
Alternatively, you'll need to make it run just in ruby - which will be slower (especially if your table gets very big, as user-tables are wont to do). But if you're ok with it being super slow, then you could do this:
class User < ActiveRecord::Base
def self.onboarded
all.select(&:user_is_matchable?)
end
or something similar...
Redefine each methods in ActiveRecord::Base for spec class User
This is what i know
class Rainbow
include Enumerable
def each
yield "red"
yield "orange"
yield "yellow"
yield "green"
yield "blue"
yield "indigo"
yield "violet"
end
end
r = Rainbow.new
r.select { |a| a.start_with?('r')} #=> ["red"]
Ok !!
Like this way what i want in User Model
class User < ActiveRecord::Base
include Enumerable
def user_ids
User.all.map(&:id) ## instead of this i want to write like User.map(&:id)
end
end
Actually There is lots of data in News model and in need only id from all the record To write the query like User.all.map(&:id) it taking lots of time.
1: For that i need to redefine each method but how ? but what line of codes i need to write in each method .
2: so that all the enumerable method can invoke on that classe`s object
Is there any other way.
Any help would be greatly appreciate.
This is not a good idea. The reason is because ActiveRecord classes (and therefore inner instance methods) can be both accessed as first-class object (when you call Model.foo) or via the ActiveRecord::Relation object and association proxy.
There is a very high chance that you will cause some hard-to-detect conflict at some point.
There is no real benefit of trying to do what you want to do. In fact, the method
class User < ActiveRecord::Base
include Enumerable
def user_ids
User.all.map(&:id)
end
end
can already be rewritten to
def user_ids
User.ids
end
that is a shorter version for
def user_ids
User.pluck(:id)
end
Note that both pluck and ids selects only the required field, hence they are way more efficient (both at Ruby level and at database level) than loading all the records and mapping a field.
Without mentioning that your code is probably wrong. In fact, you are defining an instance method that should be called
User.new.user_ids
whereas you probably expect to use it as
User.user_ids
hence you can define it as
class User < ActiveRecord::Base
def self.user_ids
# User it's implicit, its the current scope
ids
end
end
You can define each
class User < ActiveRecord::Base
include Enumerable
def each(&block)
# all returns a lazy-evaluated scope
# that responds to each
# Note that .each will trigger a query.
# In this case, that's effectively equivalent to to_a.each
all.each(&block)
end
end
but it will not bring you any advantage. Moreover, that will always trigger a query at the time you call the method, skipping the very handy lazy-load feature of active record.
In fact, ActiveRecord::Relation exists also as a performance improvement to take advantage of lazy-load.
Bottom line, if your goal is to not type User.all.map(&:id) then use a custom method, or use the Rails API effectively.
User.all.map(&:id)
can be written as
User.pluck(:id)
which is equivalent to
User.ids
that wrapped in a method becomes
class User
def self.user_ids
ids
end
end
User.user_ids
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.