Wisper: Subscribers not hearing broadcasts - ruby-on-rails

I was recommend in an earlier question to use a gem called Wisper. I am very happy to learn about it, as it is exactly the solution I'm looking for. What I can't understand from the documentation on Wisper is how listeners register themselves.
Here is my code:
app/models/subscription.rb
class Subscription < ActiveRecord::Base
include Wisper::Publisher
def some_method
# some code here
broadcast(:subscription_paused)
end
end
app/models/offer.rb
class Offer < ActiveRecord::Base
def subscription_paused
binding.pry # or whatever
end
end
So I'm not entirely sure about this part here. I've tried a variety of subscribing techniques, but I think it just comes down to me not really understanding this aspect of it:
config/initializers/wisper.rb
Wisper.subscribe(Offer.new)
I also tried, similar to the example in the Wiki:
subscription = Subscription.new
subscription.subscribe(Offer.new)
What am I missing here? (I'm not really sure if the above code should even go in an initializer.)

If the tables exists for Offer and Subscription model then the code should work.
Try this in the rails console:
# class Subscription < ActiveRecord::Base
class Subscription
include Wisper::Publisher
def some_method
# some code here
broadcast(:subscription_paused)
end
end
# class Offer < ActiveRecord::Base
class Offer
def subscription_paused
puts "jeijjj"
end
end
Wisper.subscribe(Offer.new)
Subscription.new.some_method
It should generate an output:
"jeijjj"

Related

Safest way to override the update method of a model

I have the following model:
class TwitterEngagement < ApplicationRecord
end
And I would like to override create (and create!), update (and
update!) methods of it so no one can manually entry fake data. I would like the help of someone more experienced with active record and rails so I don't mess anything up. Right now what I have is:
class TwitterEngagement < ApplicationRecord
belongs_to :page
def create
super(metrics)
end
def update
super(metrics)
end
private
def metrics
client.get_engagements(page.url)
def client
TwitterClient.new
end
end
Thank you.
TL;DR:
class FacebookEngagement < ApplicationRecord
def create_or_update(*args, &block)
super(metrics)
end
Probably depends on your Rails version, but I traced the ActiveRecord::Persistence sometime before in Rails 5, and found out that both create and update eventually calls create_or_update.
Suggestion:
If ever possible, I'll just do a validation, because it kinda makes more sense because you are validating the inputs, and then probably set an optional readonly?, to prevent saving of records. This will also prevent "silent failing" code / behaviour as doing TL;DR above would not throw an exception / populate the validation errors, if say an unsuspecting developer does: facebook_engagement.update(someattr: 'somevalue') as the arguments are gonna basically be ignored because it's instead calling super(metrics), and would then break the principle of least surprise.
So, I'll probably do something like below:
class FacebookEngagement < ApplicationRecord
belongs_to :page
validate :attributes_should_not_be_set_manually
before_save :set_attributes_from_facebook_engagement
# optional
def readonly?
# allows `create`, prevents `update`
persisted?
end
private
def attributes_should_not_be_set_manually
changes.keys.except('page_id').each do |attribute|
errors.add(attribute, 'should not be set manually!')
end
end
def set_attributes_from_facebook_engagement
assign_attributes(metrics)
end
def metrics
# simple memoization to prevent wasteful duplicate requests (or remove if not needed)
#metrics ||= graph.get_object("#{page.url}?fields=engagement")
end
def graph
Koala::Facebook::API.new
end
end

Where to instantiate class in rails

I need to use the aws-sdk-comprehend gem in my model Metric, but I'm not sure if it's ok (accordingly to rails convention) to instantiate outside of the class, e.g:
comprehend = Aws::Comprehend::Client.new
class Metric < ApplicationRecord
def key_phrases
# Use comprehend object here.
end
end
Is there a rule of thumb to in regard to this situation? I don't want to instantiate in the key_phrases because it will instantiate every time I call it.
The answer was already posted in the comments. Here a full example of how it might look in idiomatic Ruby:
class Metric < ApplicationRecord
def key_phrases
# Use comprehend object here.
comprehend_client
end
private
def comprehend_client
#comprehend_client ||= Aws::Comprehend::Client.new
end
end

ActiveRecord Global Callbacks for all Models

I have around 40 models in my RoR application. I want to setup a after_save callback for all models. One way is to add it to all models. Since this callback has the same code to run, is there a way to define it globally once so that it gets invoked for all models.
I tried this with no luck:
class ActiveRecord::Base
after_save :do_something
def do_something
# ....
end
end
Same code works if I do it in individual models.
Thanks,
Imran
You should use observers for this:
class AuditObserver < ActiveRecord::Observer
observe ActiveRecord::Base.send(:subclasses)
def after_save(record)
AuditTrail.new(record, "UPDATED")
end
end
In order to activate an observer, list it in the config.active_record.observers configuration setting in your config/application.rb file.
config.active_record.observers = :audit_observer
Note
In Rails 4, the observer feature is removed from core. Use the https://github.com/rails/rails-observers gem.
I'm pretty late on this one, but in case someone else is using Rails 3 and finds this, then this response might help.
Some models might not be loaded when the observer is loaded. The documentation says that you can override observed_classes, and that way you can get the subclasses of active record dynamically.
class AuditObserver < ActiveRecord::Observer
def self.observed_classes
ActiveRecord::Base.send(:subclasses)
end
end
This seemed to work for me:
ActiveRecord::Base.after_save do
...
end
Is there a problem I'm not seeing?
Based on #harish's answer and in this answer (https://stackoverflow.com/a/10712838/2226338):
class AuditObserver < ActiveRecord::Observer
Rails.application.eager_load!
observe ActiveRecord::Base.descendants
def after_save(record)
...
end
end
This actually works pretty well for me in 2.3.8:
class AudiObserver < ActiveRecord::Observer
observe :'ActiveRecord::Base'
#
# observe methods...
#
end

Adding callbacks for model classes in separate file (RoR)

I have a Message model class (which inherits from ActiveRecord::Base). For a particular deployment, I would like to have a separate file which modifies Message by adding a callback. So, instead of doing:
# app/models/message.rb
class Message < ActiveRecord::Base
before_save :foo
def foo
puts 'foo!'
end
end
I would like to be able to do:
# app/models/message.rb
class Message < ActiveRecord::Base
end
# config/initializers/fixes.rb
Message
class Message
before_save :foo
def foo
puts 'foo!'
end
end
Problem is, it works when I start the script/console, but when I start it using script/server it usually doesn't. That is the worst part, it isn't that it never works. Sometimes I start the server and it works, sometimes it doesn't, and that is without making any changes to the source.
I am restarting the server itself as (as far as I know) the initializers are run only once and don't get reloaded if modified.
I know the 'sometimes' works is very vague, but I have spent hours here without any luck. Perhaps someone has had a similar issue, or can come up with a different idea to add the callback.
Why not put those into a module and import it?
class Message < ActiveRecord::Base
include Message::Callbacks
end
In another file you can define whatever you like, such as message/callbacks.rb:
module Message::Callbacks
def self.included(base)
base.class_eval do
before_save :foo
end
end
def foo
# ...
end
end
The downside to this is it's more work to make the methods protected.
Why not use observers? (http://api.rubyonrails.org/classes/ActiveRecord/Observer.html)
For example, you'd do something like this:
class MessageObserver < ActiveRecord::Observer
def before_save(message)
puts 'you win at ruby!'
end
end

Call a method in model after find in Ruby on Rails

I would like to know if it is possible to call a method from a model after using find.
Something like after_save, but after_find.
Thank you,
Gabriel.
Nowadays ((26.04.2012) this is proper way (and working!) to do that:
class SomeClass < ActiveRecord::Base
after_find :do_something
def do_something
# code
end
end
Edit: For Rails >= 3, see the answer from #nothing-special-here
There is. Along with after_initialize, after_find is a special case, though. You have to define the method, after_find :some_method isn't enough. This should work, though:
class Post < ActiveRecord::Base
def after_find
# do something here
end
end
You can read more about it in the API.
Interestingly enough, this will call the method twice... learned that one the hard way.
class Post < ActiveRecord::Base
after_find :after_find
def after_find
# do something here
end
end
If you need the found object in your method:
class SomeClass < ActiveRecord::Base
after_find{ |o| do_something(o) }
def do_something(o)
# ...
end
end
More details here: http://guides.rubyonrails.org/active_record_callbacks.html#after-initialize-and-after-find

Resources