I'm trying to always run some code after .find or .where or whatever is used to retrieve objects.
For example, the following describes what I want, but does not work
Class Person < ActiveRecord::BA
#mortality=true
end
I want #mortality=true to run whenever a Person object is created
And based on my current understanding of ORM/ActiveRecord, a new object is created whenever retrieval is done. Hopefully that is correct.
You want to do this in the after_initialize method:
class Person < ActiveRecord::Base
def after_initialize
#mortality = true
end
end
Note that this is something you should avoid doing if possible because it happens on every object, even when you retrieve enormous result sets.
In this (albeit simple) case, you can do the assignment lazily by overriding the getter:
class Person < ActiveRecord::Base
def mortality
#mortality.nil? ? true : #mortality
end
end
(you can't use the nil gate ||= here because it filters false values as well)
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Look for after_find and after_initialize callbacks.
Related
I have a gamification app that has four types of points, and the sum of all these kinds is the total points for a user, I want to be able to do sum and scopes on that column, so I think I should have it as a column in the DB.
scope :points_rank, -> { order(points: :desc) }
I was using a before_save for adding all four point types and storing it in points, but now I'm using a gem that does increment to these types of points, so when it updates those values, the before_save is not called, hence not updating the points value as expected.
What is the correct ActiveRecord callback to be using instead of before_save, or what else could I be doing to keep the column updated.
Try using the after_touch callback instead.
after_touch callback is triggered whenever an object is touched.
So, whenever point type changes, it should update the points.
First of all, counter_culture seems to be a way to enhance the counter_cache functionality of rails...
Used to cache the number of belonging objects on associations. For example, a comments_count column in a Post class that has many instances of Comment will cache the number of existent comments for each post.
It might not be exactly what you want, judging from your question.
Okay I get it. You're using points in your User model to create a "cached" column which can be used for wider application functionality. Okay that's cool...
--
Your setup, then, will look something like this (you were manually setting the counter_cache column, and now the gem handles it):
#app/models/user.rb
class User < ActiveRecord::Base
counter_cache :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, counter_cache: true
end
The question is then that when you update the points model, you need to be able to update the "cached" column in the users model, now without any callbacks.
What is the correct ActiveRecord callback to be using instead of before_save
I'm presuming you're calling before_save on your User model (IE adding the associated data and putting the points column?
If so, you should try using a callback on the Point model, perhaps something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, inverse_of: :points
after_commit :update_user
private
def update_user
if user?
user.update(points: x + y + z)
end
end
end
--
Oberservers
If you have real problems, you could look at ActiveRecord observers.
Here's an answer I wrote about it: Ruby On Rails Updating Heroku Dynamic Routes
Whether this will trigger without any callbacks is another matter, but what I can say is that it will work to give you functionality you may not have had access to otherwise:
#config/application.rb (can be placed into dev or prod files if required)
config.active_record.observers = :point_observer
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def before_save(point)
#logic here
end
end
A good way to test this would be to use it (you'll have to use the rails-observers gem) with different methods. IE:
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def initialize(point)
#if this fires, happy days
end
end
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
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!
This is very similar to Rails partial updates problem with hashes , but the question has not really been answered IMHO.
The problem is this: I have a model with a serialized column:
class Import < AR::Base
serialize :data
In my case, this data will, and should, not change after the first save/creation of the model. So I want to disable the feature of AR that always saves serialized columns (which is normally a good idea, as it can't detect those changes). I want to disable the saving because the data can be quite large, and the model will be updated frequently.
I've already tried monkeypatching into ActiceRecord::AttributeMethods::Dirty like this:
class Import
def update(*)
if partial_updates?
super(changed | (attributes.keys & (self.class.serialized_attributes.keys - ["data"])))
else
super
end
end
but this seems to have no effect. Anybody got a better idea ?
This is under Rails 3.0.12
What I ended up doing, even though it's not really an answer to the original question, is the following:
class Import < AR::Base
belongs_to :storage
class Storage < AR::Base
serialize :data
...i.e. moving the data column into a model of its own, and associate that with the original model. Which is actually conceptually somewhat cleaner.
Here is an ugly monkey patch solution:
module ActiveRecord
module AttributeMethods
module Dirty
def update(*)
if partial_updates?
# Serialized attributes should always be written in case they've been
# changed in place. Unless it is 'spam', which is expensive to calculate.
super(changed | (attributes.keys & self.class.serialized_attributes.keys - ['spam']))
else
super
end
end
private :update
end
end
end
class Foo < ActiveRecord::Base
serialize :bar
serialize :spam
def calculate_spam
# really expensive code
end
def cache_spam!
calculated_spam = calculate_spam
#changed_attributes['spam'] = [spam, calculated_spam]
self.update_attribute(:spam, calculated_spam)
end
end
You will have to remember to call cache_spam!, or your serialized attribute will never be saved.
I am writing a Ruby on Rails app and I want a method to be called every time the database is modified. Is it possible to do this without inserting a method call in every location where the database is modified?
I like KandadaBooggu's answer but if you did not want to monkey with AR you might be able to do this with an Observer.
class AllObserver < ActiveRecord::Observer
observe :model_a, :model_b
def after_save(record)
logger.info("CREATED #{record.class}")
end
def after_update(record)
logger.info("UPDATED #{record.class}")
end
end
Just add the models that you want to observer. In this example it will log updates to ModelA and ModelB
Depends on the database. Many databases have very powerful stored procedure languages that can, among other things, invoke web services.
You could have a trigger on the important database tables call a ruby web service that calls your method.
Or you can have triggers that update an event table, and then have a process that watches for changes on that table and then fires the method.
There's likely some meta-programming magic that you might be able to use to tweak your ruby code to invoke the change as well.
All sorts of options.
If you want to log all models:
Monkey patch the ActiveRecord::Base class.
class ActiveRecord::Base
after_save :log_something
after_destroy :log_something
private
def log_something
end
end
For a specific model:
class User < ActiveRecord::Base
after_save :log_something
after_destroy :log_something
private
def log_something
end
end
Have you considered using: after_update or before_update in ActiveRecord:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html