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
Related
UPDATE:
How do I add a virtual attribute to a model and preserve the active record relation.
I tried the below, but .each returns an array, not active record. What other method can I use?
My set_listable_for method is converting an activerecord relation into an array. I want to preserve the ActiveRecord Relation.
At runtime, I added an attr_access to an active record model.
def add_listable_attribute_to(*relation)
relation.each do |rel|
rel[1].first.class.class_eval do
attr_accessor :listable
end
end
end
Then I used this method to set the value of the attribute to the same value for all records....
def set_listable_for(relation, object)
relation.each do |record|
record.listable = object
end
end
However, my ActiveRecord relation gets converted to an Array afterwords.
How to I preserve the Active Record relation, as I don't want an array. Since I continue to use it here and continue to scope and query on it...
def union_scope(*relation)
add_listable_attribute_to(*relation)
listable = relation.first[0]
combined = set_listable_for(relation.first[1], listable)
relation.drop(1).each do |relation_set|
listable = relation[0]
set_listable_for(relation_set[1], listable)
combined = combined.or(relation_set[1])
end
combined
end
Thank you
The .each call executes the query and iterates over the result. It wouldn't be a problem if this happens in the controller after scoping and pagination, but if it gets called before scoping and pagination, the whole model dataset will be loaded which isn't good.
To avoid that, you'll need to set up the listable as late as possible after data is retrieved from the database. I can see three approaches to handle this:
Use a decorator to wrap instances of your relation after it is loaded into the controller or view. This is simpler to understand but pulls the functionality out of the model layer.
Set listable in an after_initialize callback. This keeps the functionality in the model layer, but adds a lot of complexity.
Ensure that you only call set_listable_for in the controller after scoping. Variant of #1.
By adding an 'AS' Statement in Select, I was able to return an ActiveRecord model. Only issue is that when I call .count, I need to use .count(:all), or .count(:id), to prevent errors.
def union_scope(*relation)
listable = relation.first[0]
scope = relation.first[1]
combined = scope.select("#{scope.table_name}.*, \'#{listable.class.name}\' as listable")
relation.drop(1).each do |relation_set|
listable = relation_set[0]
scope = relation_set[1].select("#{scope.table_name}.*, \'#{listable.class.name}\' as listable")
combined = combined.or(scope)
end
combined
end
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 wondering if it is posible to use a model instance method as a where clause query. I mean. I have a model School with a method defined
class School < ActiveRecord::Base
def my_method
users.where(blablabla).present?
end
end
Is it posible to get something like:
School.all.where(my_method: true)
I know that I can do something like:
School.all.map{|x| x if x.my_method}
But this way has a huge penalization in performance compared to where query. Furthermore, the return of what I'm searching is an ActiveRecord Relation and map returns an array.
UPDATE:
Also there is another way to do it like:
School.all.joins(:users).where("users.attribute = something")
But this do not fit exactly what I want for several reasons.
Thanks in advance
I don't really know the relations between your models.
but where clause gets a hash of key - value.
So you can for example return the ID's of the users in a some kind of a hash and then use it.
def my_method
{user_id: users.where(blablabla).ids}
end
and use it:
School.all.where(my_method)
The question below had a good answer to grab associated values of an activerecord collection in one hit using Comment.includes(:user). What about when you have multiple associations that you want to grab in one go?
Rails have activerecord grab all needed associations in one go?
Is the best way to just chain these together like below Customer.includes(:user).includes(:sales).includes(:prices) or is there a cleaner way.
Furthermore, when I am doing this on a loop on an index table. Can I add a method on the customer.rb model so that I can call #customers.table_includes etc and have
def table_includes
self.includes(:user).includes(:sales).includes(:prices)
end
For the record I tested the above and it didn't work because its a method on a collection (yet to figure out how to do this).
In answering this, I'm assuming that user, sales, and prices are all associations off of Customer.
Instead of chaining, you can do something like this:
Customer.includes(:user, :sales, :prices)
In terms of creating an abstraction for this, you do have a couple options.
First, you could create a scope:
class Customer < ActiveRecord::Base
scope :table_includes, -> { includes(:user, :sales, :prices) }
end
Or if you want for it to be a method, you should consider making it a class-level method instead of an instance-level one:
def self.table_includes
self.includes(:user, :sales, :prices)
end
I would consider the purpose of creating this abstraction though. A very generic name like table_includes will likely not be very friendly over the long term.
consider
def Foo
has_one :user
end
let's say i only want a Foo's User's name, and not any of the other columns. so i want
SELECT name FROM "users" WHERE "prices"."id" = 123
but doing foo.user.name will give me
SELECT * FROM "users" WHERE "prices"."id" = 123
is there any slick way to use the association to get only one column? if not, then i have to do:
User.where(id: foo.user_id).pluck(:name).first
In general you can specify what columns you want to select using the .select method, like:
User.select(:name).where(...)
This will return just the values from the name column. You can chain this onto an association, but not onto an instance. So, as meagar very agressively pointed out by downvoting the other answers (including Mori's deleted answer), in a has_one relationship you can't chain this on the association (because it's not an association in that case). However, you could build a custom scope, like this:
class Foo < ActiveRecord::Base
has_one :bar
scope :bar_name, lambda {Bar.select(:name).where(:foo_id=> id)}
end
The above is untested so you may have to tweak it, but generally speaking that approach would allow you to do something like:
foo.bar_name
...without loading all the columns from Bar.
No, in the case of your has_one, but yes in the case of has_many.
The object returned for a has_one association isn't a scope onto which you can chain additional methods like select, like with a has_many. It's an actual instance of the model, and instantiating it will necessarily involve a select *.
If you want to select just the name, you'll have to access the User model directly and use select.
Conversely, if your Foo has many users, you could use foo.users.select("name") or any of the other chainable methods, as foo.users would be an actual ActiveRecord association, not an instance of a model.