How to invoke a method every time database is modified - ruby-on-rails

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

Related

Model column that depends on other columns

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

Rails methods called during a database change

Basically i want to update a set of caches when data is created/removed/amended from a table
what would the most pragmatic way of doing this?
ActiveRecord has a set of callbacks (more info).
I would use the after_commit callback to invalidate your caches.
class MyClass < ActiveRecord::Base
after_commit :invalidate_cache
private
def invalidate_cache
#some logic
end
end

how to run a one-time database change on a single user

I have Customer and each customer has_many Properties. Customers belong to a Company.
I'm trying to add a certain Property to each one of a single Company's Customers. I only want this change to happen once.
I'm thinking about using a migration but it doesn't seem right to create a migration for a change that I only ever want to happen once, and only on one of my users.
Is there a right way to do this?
You can just use rails console.
In rails c:
Company.where(conditions).last.customers.each do |customer|
customer.properties << Property.where(condition)
customer.save!
end
Validation
Depending on how you're changing the Customer model, I'd include a simple vaidation on the before_update callback to see if the attribute is populated or not:
#app/models/Customer.rb
class Customer < ActiveRecord::Base
before_update :is_valid?
private
def is_valid?
return if self.attribute.present?
end
end
This will basically check if the model has the attribute populated. If it does, it means you'll then be able to update it, else it will break
--
Strong_Params
An alternative will be to set the strong_params so that the attribute you want to remain constant will not be changed when you update / create the element:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
...
private
def strong_params
params.require(:model).permit(:only, :attributes, :to, :update)
end
end
It would be much more helpful if you explained the context as to why you need this type of functionality - that will give people the ability to create a real solution, instead of proposing ideas

how do I use after_save for a model as well as for all its associated models?

I have a category model and it has association with say category_supply and category_experience model.If there is any changes in either category model or its associated model (category_supply and category_experience) It should call after_save.So that I dont need to write after_save in each model
You can use an ActiveRecord::Observer to watch for changes on the association models and then trigger a callback on the parent model. Here is an example:
class CategoryAssociationsObserver < ActiveRecord::Observer
observe Supply, Experience
def after_save(record)
record.category.run_callbacks(:save) if record.category
# I don't know if you can trigger just the `after_save` callback here
end
end
See the ActiveRecord::Observer documentation for more information on its use: http://apidock.com/rails/ActiveRecord/Observer.

Can a rails model observe a subject?

I'm trying to have a rails model observe another object (which acts as a subject). I saw that there is an update method (which has different semantics) in ActiveRecord::Base, but in order to use the Observable module, I need to implement an update method in my model so that I can be notified of changes to the subject.
Any thoughts on how this could be achieved?
You probably want to use a regular Observer which will receive event callbacks when something happens to the observed model.
Why do you need to encapsulate your observer functionality into another model?
You're better off putting the events/callbacks in your observer and calling any needed functionality as a helper method on the other model instead of making your model an observer.
EDIT: Adding example code
class User < ActiveRecord::Base
end
class UserObserver < ActiveRecord::Observer
observe :user
def after_save(user)
MyHelperClass.do_some_stuff_for_user(user)
end
end
class MyHelperClass
def self.do_some_stuff_for_user(user)
puts "OMG I just found out #{user.name} was saved so I can do stuff"
end
end
It appears that you can override the default update that comes with ActiveRecord, so that it can receive notifications from subjects (assuming you have mixed in Observable). The procedure for doing something like this is in the book "Pro ActiveRecord" published by APress (Chap. 5, "Bonus Features").
It involves the use of alias_method / alias_method_chain with some metaprogramming involved...
I haven't tried this out personally yet, but just leaving a note here in case anyone else is intested.

Resources