Ruby on Rails: "after_create" and validations - ruby-on-rails

I have a record that needs to be validated before doing some action. Am I required to use a "valid?" method if I'm doing it with after_create?
For example, I have in my User model:
def after_create
if valid?
...
end
end
I thought it wasn't necessary to put in the valid method, but my application is telling me otherwise. Any idea?

You do not need the if valid? declaration there because after_create gets called after the record has already been validated (and created).
What do you mean your application is telling you otherwise?
Also, for the callback methods, you should use something like:
after_create :call_my_method
private
def call_my_method
# Do cool stuff
end

Related

Rails ActiveRecord callbacks dilemma

I am trying to update a rails record if a duplicate exists as follow:
class Rating < ActiveRecord::Base
before_create :update_rating_if_already_exists
def update_rating_if_already_exists
original_rating = Rating.where(user: self.user, article: self.article)
if original_rating.blank?
true
else
original_rating[0].update_attribute(:score, self.score)
false
end
end
end
the problem however is that when I use after_create as above, this will not work for normal usage from controller action, as the controller will first build a new instance of the class with given params and then save (not create) the new object.
However, if I change the above to an after_save, it takes care of the controller problem, but then leads to another problem:
original_rating[0].update_attribute(:score, self.score)
will try to update the original record with a save method, which will also trigger the before_save...and on, and on... and this leads to a SystemStackError: stack level too deep error.
This is the dilemma now and my question is how could I go about this?
Thanks for all contributions. :)
You can use before_save with a new_record? condition to avoid executing the callback on an update_attribute call.
before_save :update_rating_if_already_exists, if: :new_record?
def update_rating_if_already_exists
# method code
end

Ruby - How to access module's methods?

I'm installing a forum using the Forem gem. There's an option that allows avatar personalization, since it's possible to login with Facebook. You just specify your method in the User model and that's it.
# Forem initializer
Forem.avatar_user_method = 'forem_avatar'
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
end
end
But I want a fallback on Gravatar for normal, non-facebook accounts. I've found the method on Forem and in theory, I need to call the avatar_url method:
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
else
Forem::PostsHelper.avatar_url self.email
end
end
However, Forem isn't an instance, but a module and I can't call it nor create a new instance. The easy way is to copy the lines of that method, but that's not the point. Is there a way to do it?
Thanks
Update
Both answers are correct, but when I call the method either way, there's this undefined local variable or method 'request' error, which is the last line of the original avatar_url.
Is there a way to globalize that object like in PHP? Do I have to manually pass it that argument?
perhaps reopen the module like this:
module Forem
module PostsHelper
module_function :avatar_url
end
end
then call Forem::PostsHelper.avatar_url
if avatar_url call other module methods, you'll have to "open" them too via module_function
or just include Forem::PostsHelper in your class and use avatar_url directly, without Forem::PostsHelper namespace
If you want to be able to use those methods in the user class, include them and use
class User < ActiveRecord::Base
include Forem::PostsHelper
def forem_avatar
return user_pic if user_pic.present?
avatar_url email
end
end
Another way would be to set the Forem.avatar_user_method dynamically since the Forem code checks it it exists before using it and defaults to avatar_url if it does not.
class User < ActiveRecord::Base
# This is run after both User.find and User.new
after_initialize :set_avatar_user_method
# Only set avatar_user_method when pic is present
def set_avatar_user_method
unless self.user_pic.empty?
Forem.avatar_user_method = 'forem_avatar'
end
end
def forem_avatar
self.user_pic
end
end
This way you dont pollute your model with unnecessary methods from Forem and don't monkey patch Forem itself.

Redefine a single method on an instance to call superclass method

We have two Rails models: Person and Administrator. We're disallowing removal of Administrators at the model level:
class Person < ActiveRecord::Base
end
class Administrator < Person
def destroy
raise "Can't remove administrators."
end
end
me = Administrator.new
me.destroy # raises an exception
I'd like to be able to get around this during testing, but only for specific instances created during setup and teardown. I don't want to change the behavior of the class, so class_eval and remove_method aren't feasible.
I tried to redefine the actual instance's #destroy method:
def me.destroy
super
end
or redefine it on the singleton class:
class << me
def destroy
super
end
end
but those still raised the exception. I couldn't figure out how to get it to call the superclass method implicitly. I ended up creating my own destroy! method (since that's not actually a method in ActiveRecord), which sort of violates my desire not to change the behavior of the class:
def destroy!
ActiveRecord::Persistence.instance_method(:destroy).bind(self).call
end
Is there any simple way to tell a single instance method to call its superclass method?
Final Answer: Based on the article Holger Just linked to, I was able to simply call the superclass method explicitly:
def me.destroy
self.class.superclass.instance_method(:destroy).bind(self).call
end
I'd try to refactor the behavior to be more test-friendly. E.g. you could allow an optional parameter to destroy e.g. i_know_what_im_doing that has to be set to true to carry out the destroy. Alternatively you could cancel the destroy with a before_destroy hook like
class Administrator < Person
def before_destroy(record)
# You can't destroy me
false
end
end
In your tests, you can then call Administrator.skip_callback :before_destroy to ignore it and to have a proper destroy.
Finally, you could overwrite / stub the method in your tests. While you say you don't want to modify the class's behavior, you still have to do that (and implicitly do that with your destroy! method today).
I'm not familiar with Ruby metaprograming, so I wont answer if you can a call a method of the super class on an instance without modifying it. But you can create a hook to a superclass method with alias :
class Administrator < Person
alias :force_destroy :destroy
def destroy
raise "Can't remove administrators."
end
end
With this, admin.destroy will raise an exception, but admin.force_destroy will actually call the ActiveRecord destroy.

How to get true after_destroy in Rails?

I have an after_destroy model callback that regenerates cache after the model instance has been destroyed. It does this by calling open("http://domain.com/page-to-cache") for as many pages as need to be re-cached.
The problem is that the model instance apparently isn't fully destroyed yet at this time, because those open url requests still register its presence, and the regenerated cache looks exactly like the pre-destroy cache. How can I run those calls after the model instance has been actually destroyed?
You may be able to use an after_commit callback to do something after the entire transaction has gone through to the database. This is different depending on the version of Rails you're using (2.3.x versus 3.x.x), but is essentially something like the following:
# model_observer.rb
class ModelObserver < ActiveRecord::Observer
def after_commit(instance)
do_something if instance.destroyed?
end
end
You can read some documentation about the Rails 3 after_commit callback here. If your version of Rails doesn't have an after_commit hook, you can try using this gem which will provide the functionality.
You could try adding an after_save callback like:
after_save :my_after_save_callback
def my_after_save_callback
do_something if destroyed?
end

custom method_missing ignored if instance is defined via an ActiveRecord association

I have submissions that might be in various states and wrote a method_missing override that allows me to check their state with calls like
submission.draft?
submission.published?
This works wonderfully.
I also, for various reasons that might not be so great, have a model called Packlet that belongs_to a meeting and belongs_to a submission. However, I was surprised to find that
packlet.submission.draft?
returns a NoMethodError. On the other hand, if I hard-code a #draft? method into Submission, the above method call works.
How do I get my method_missing methods to be recognized even when the instance is defined via an ActiveRecord association?
Have you added the draft? method to your respond_to? method for that object? My guess would be that the issue might arise there. What happens when you type:
submission.respond_to?(:draft?)
To fix this, actually write a respond_to? method like this:
def respond_to?(method, include_priv = false) #:nodoc:
case method.to_sym
when :draft?, :published?
true
else
super(method, include_priv)
end
end
My recommendation would be to implement this without using method_missing instead though, so by doing some meta-programming like this:
class Submission
[:draft, :published].each do |status|
define_method "#{status}?" do
status == "#{status}?"
end
end
end

Resources