2
votes

Im learning ruby on rails and have a trouble with aasm callbacks and actionmailer. I have a hotels model. Heres a code:

class Hotel < ActiveRecord::Base
  include AASM

  scope :approved_hotels, -> { where(aasm_state: "approved") }

  has_many :comments
    belongs_to :user, :counter_cache => true
    has_many :ratings
  belongs_to :address

  aasm do
    state :pending, initial: true
    state :approved
    state :rejected

    event :approve, :after => :send_email do
      transitions from: :pending, to: :approved 
    end
    event :reject, :after => :send_email do
      transitions from: :pending, to: :rejected
    end
  end

  def send_email

  end
end

As you see user has to get email when state of the hotel he added was changed. Heres what i wrote but its not THE solution cos user gets emails every time admin updates hotel with "pending" state.

class HotelsController < ApplicationController
  before_filter :authenticate_user!, except: [:index, :show, :top5hotels]

  def update
    @hotel = Hotel.find(params[:id])

    if @hotel.aasm_state == "pending"
      @hotel.aasm_state = params[:state]
      UserMailer.changed_state_email(current_user, @hotel.name, 
      @hotel.aasm_state).deliver
    end

    if @hotel.update_attributes!(params[:hotel])
      redirect_to admin_hotel_path(@hotel), notice: "Hotel was successfully updated."
    else
      render "edit"
    end
  end
end

So i think i need to use callback but i dont know how to call

UserMailer.changed_state_email(current_user, @hotel.name, 
        @hotel.aasm_state).deliver

from the model. I tried

UserMailer.changed_state_email(User.find(:id), Hotel.find(:name), 
        Hotel.find(aasm_state)).deliver

but that doesnt work. Im really out of options and looking for any help. Thanks!

1

1 Answers

1
votes

You can't use a callback because you have current_user which is part of the controller context, and you can't access request information in the model context.

Anyway, even if you could use the callback, in this case I strongly encourage you to follow a different path. The ActiveRecord callbacks should be rarely used, in particular you should avoid them for any code that involves interaction with other objects or resources such as mailers or cascade update. The risk is that the callback will be triggered even when you don't need it (e.g. tests) increasing overhead, or that it will conflict with other callbacks when the complexity of the project will increase.

In this case the solution is really simple. Define a new method in the model (I'm not introducing you to service objects for now...) that you use to change the state and deliver the email.

class Hotel
  def user_state_change(user, new_state)
    return unless pending? && new_state.present?

    if update_attribute(:aasm_state, new_state)
      UserMailer.changed_state_email(user, name, aasm_state).deliver
    end
  end
end

Your controller will become

class HotelsController < ApplicationController
  before_filter :authenticate_user!, except: [:index, :show, :top5hotels]

  def update
    @hotel = Hotel.find(params[:id])
    @hotel.user_state_change(current_user, params[:state])

    if @hotel.update_attributes!(params[:hotel])
      redirect_to admin_hotel_path(@hotel), notice: "Hotel was successfully updated."
    else
      render "edit"
    end
  end
end

As a side note, you may want to use the state machine transition methods, rather changing the state attribute. In fact, using the state machine transition will ensure the transition validation is triggered.