15

I am trying to assign a message to flash[:notice] in a model observer.

This question has already been asked: Ruby on Rails: Observers and flash[:notice] messages?

However, I get the following error message when I try to access it in my model:

undefined local variable or method `flash' for #<ModelObserver:0x2c1742c>

Here is my code:

class ModelObserver < ActiveRecord::Observer
  observe A, B, C

  def after_save(model)
    puts "Model saved"
    flash[:notice] = "Model saved"
  end
end

I know the method is being called because "Model saved" is printed to the terminal.

Is it possible to access the flash inside an observer, and if so, how?

Community
  • 1
  • 1
titaniumdecoy
  • 18,900
  • 17
  • 96
  • 133
  • 2
    Technically valid solution that breaks MVC: http://stackoverflow.com/questions/393395/how-to-call-expire-fragment-from-rails-observer-model/608700#608700 – titaniumdecoy Jun 16 '12 at 06:27

2 Answers2

19

No, you set it in the controller where the saving is occurring. flash is a method defined on ActionController::Base.

Ryan Bigg
  • 106,965
  • 23
  • 235
  • 261
  • 7
    Ryan's right though. You should be setting the flash in the controller ... it's a function of the view presentation layer. The "answer" above is a lot of dangerous heavy lifting to get this working. – Toby Hede Apr 24 '10 at 03:50
  • 1
    As I said in my post, setting the flash in the controller impractical (if it is even possible) in my application. I need add a message to the flash every time a model is updated; I am unaware of another method of doing so--at least without throwing a plate of spaghetti code at the wall. – titaniumdecoy Apr 24 '10 at 06:44
  • I wrestled with this briefly today as well, but after having solved my own dilemma I see that you've answered your question in the comment right above mine. "I need add a message to the flash every time a model is updated." I know you said it's impractical but I placed my flash inside my controller's update method. (Was catching an exception and then flashing an error.) – Tass Jun 01 '12 at 21:34
  • 6
    Sometimes the world is not as simple as you'd like it to be. I need to set the flash from a monkey patched extension to Sunspot/RSolr which is fired from a model callback on a different thread during the request cycle. Please don't answer questions with the obvious. You can certainly mention that it is normally done one way, but unless it directly answers the question it is not helpful at all. – Chloe Mar 28 '14 at 18:43
  • $0.02 -> Often when you think you _have to_ do something a "dirty" (or non scalable, or risky, or too-complex) way, its likely that you "have the wrong abstraction" somewhere. The architecture containing this scenario needs refactoring. Choosing to violate the conventions because you feel you _have to_ can result in having painted one's self into a corner months or years later (and I also dont advocate pushing tail risk onto future devs that are not you). In short, what Ryan Bigg said. – Todd Jan 31 '19 at 01:23
  • 2
    I see no problem with defining a Model attr_accessor like `validation_messages` and then checking it in a Controller `after_action` callback to set flash (inside the controller) according to validation_messages value. – Darme Aug 28 '20 at 07:02
14

I needed to set flash[:notice] in the model to override the generic "@model was successfully updated".

This what I did

  1. Created a virtual attribute in the respective model called validation_message
  2. Then I set the virtual attribute in the respective model when needed
  3. Used an after_action when this virtual attribute was not blank to override the default flash

You can see my controller and model how I accomplished this below:

class Reservation < ActiveRecord::Base

  belongs_to :retailer
  belongs_to :sharedorder
  accepts_nested_attributes_for :sharedorder
  accepts_nested_attributes_for :retailer

  attr_accessor :validation_code, :validation_messages

  validate :first_reservation, :if => :new_record_and_unvalidated

  def new_record_and_unvalidated
    if !self.new_record? && !self.retailer.validated?
      true
    else
      false
    end
  end

  def first_reservation
    if self.validation_code != "test" || self.validation_code.blank?
      errors.add_to_base("Validation code was incorrect") 
    else
      self.retailer.update_attribute(:validated, true)
      self.validation_message = "Your validation is successful and you will not need to do that again"
    end
  end
end

class ReservationsController < ApplicationController

  before_filter :authenticate_retailer!
  after_filter :validation_messages, :except => :index

  def validation_messages
    return unless @reservation.validation_message.present?

    flash[:notice] = @reservation.validation_message
  end
end

One possible refactor would be to move the actual message in a proper file (e.g. a locale) and pass to validation_message only the proper key.

Should you need more than one notice it's easy enough to turn validation_message into an array or a hash and call it validation_messages instead.

Darme
  • 6,984
  • 5
  • 37
  • 52
thenengah
  • 42,557
  • 33
  • 113
  • 157
  • 1
    You simplify your `if` condition to `@reservation.flash_notice.present?` instead of "`not blank?`" – Besi Mar 14 '16 at 23:18