Skip to content
blizz edited this page Sep 12, 2010 · 29 revisions

OUT OF DATE

Current lifecycle documentation here http://cookbook.hobocentral.net/manual/lifecycles

Lifecycles are a feature of Hobo that lets you describe the possible states that a model object can be in, and define transitions between those states. Hobo can then use this information to generate some controller code for you (e.g. it automatically creates basic pages for all transitions).

Lifecycle definition

Lifecycles are declared in the model class inside a block:


lifecycle do
  ...
end

Inside the block, you can use the methods listed below to define the states and transitions:


state :active [, :inactive, :blocked, ...]

Defines a list of states in which the model object can be.


state :active { ... }

The code inside the given block will be executed on the object after it completes transition to the given state.


initial_state :inactive { ... }

Defines a state, indicating that after the object is created, it should be set to this state.


create :anybody, :signup,
    :params => [:username, :email, :password, ...],
    :become => :inactive,
    :new_key => true,
    :if => Proc.new {...} / :method_name
    do ... end

Defines a creator action, that is, an action which creates the model object. Declaring such action (named “signup” here) will make Hobo automatically create two controller actions for you:

  • signup, which shows a form;
  • do_signup, which processes the form, creates a new model object, assigns parameters from the form to object’s fields, checks all conditions, saves the object and runs callback code.

The parameters that create accepts are:

  • first parameter – tells who can run the action:
    • :anybody – anyone can (even guest users); in the case of the user signup action, we obviously want guest users to be able to sign up
    • :nobody – no one can call it directly, and the controller actions will not be created; this action will be called only manually in your code, like this: @object.lifecycle.signup(:nobody)
    • :self – can only be called if self == current_user (only makes sense in User model)
    • name of a relationship (e.g. :owner) – can be called if current user is assigned to that field (current_user == self.owner); if the field is empty, then current_user is assigned to it during the action
    • [:owner, :admin, …] – as above, but gives several possibilities
  • second parameter – name of the action
  • :params – a list of form fields whose values will be assigned to the object’s attributes (other fields are ignored)
  • :become – the state that the object will be put in
  • :new_key – if true, it will generate a new activation key and store it in a model field
  • :if – a condition that must be met before the action can be run; given either as a block { |model_object| … }, or as a method name (:method_name, called as @model_object.method_name(current_user))
  • if a block is given, it will be executed on the object after the action is completed

transition :with_key, :activate,
    {:inactive => :active} / {[:inactive, :blocked, ...] => :active},
    :update => [:password, :password_confirmation, ...],
    :new_key => true,
    :if => ...,
    do ... end

Defines a transition action, i.e. an action that switches the model object from one state to another. Just like create definition, this will create two actions for you. The do_something action will assign parameters from the form to an existing object, check conditions, change its state, save it, and run callback code.

Parameters to transition definition are:

  • two first parameters – just like in create, except the “who” parameter can take one additional option: :with_key, which means that anyone can call it as long as they provide a valid activation key in a URL parameter
  • third parameter – defines the state before and after the transition; either {:from_state => :to_state}, or {[list of possible entry states] => :target_state}
  • :update – like :params in create
  • :new_key, :if, block – like in create

precondition { ... }

Precondition block defines conditions that must be satisfied before any creator action may be executed. Commands in the block are executed on the object that is being created (using instance_eval), and it will only be created if the last one returns true.


invariant { ... }

This is similar to precondition in that it also defines a condition, but this time the condition is verified before any transition or creation action.

Lifecycle usage

And this is how you can use the lifecycle from your code:


# generate an activation key
@obj.lifecycle.generate_key

# run a creator action 'signup'
@obj.lifecycle.signup(current_user/:nobody, [attributes...])

# run a transition 'activate'
@obj.lifecycle.activate(current_user/:nobody, [attributes...])

# check if an action can be called
@obj.lifecycle.can_signup?(user, [attributes...])
@obj.lifecycle.can_activate?(user, [attributes...])

# switch to a state, without calling a transition
# (it takes into account the invariants and state callback blocks)
@obj.lifecycle.become :active

Example – email verification after signup

Take a look at the lifecycle block in the user.rb model, originally it looks like this


lifecycle do initial_state :active create :anybody, :signup, :params => [:username, :email_address, :password, :password_confirmation], :become => :active, :if => proc {|r| r.acting_user.guest?} transition :nobody, :request_password_reset, { :active => :active }, :new_key => true do UserMailer.deliver_forgot_password(self, lifecycle.key) end transition :with_key, :reset_password, { :active => :active }, :update => [ :password, :password_confirmation ] end

To add the activation step, change the initial state to :inactive and add the :active state back


  lifecycle do

    initial_state :inactive

    state :active
...

The state should remain :inactive during creation and the user gets a new key


    create :anybody, :signup, 
           :params => [:username, :email_address, :password, :password_confirmation],
           :become => :inactive, :if => proc {|r| r.acting_user.guest?}, :new_key => true

A code block is added to create to get it to email the new user an activation link


    create :anybody, :signup, 
           :params => [:username, :email_address, :password, :password_confirmation],
           :become => :inactive, :if => proc {|r| r.acting_user.guest?}, :new_key => true do
      UserMailer.deliver_activation(self, lifecycle.key)
    end

Then a transition from :inactive to :active which requires a valid key


    transition :with_key, :activate, { :inactive => :active } 

To get the activation email sent to the user, in the UserMailer model add an activation method.


  def activation(user, key)
    host = Hobo::Controller.request_host
    app_name = Hobo::Controller.app_name || host
    @subject    = "#{app_name} -- Activate"
    @body       = { :user => user, :key => key, :host => host, :app_name => app_name }
    @recipients = user.email_address
    @from       = "activate@#{host}"
    @sent_on    = Time.now
    @headers    = {}
  end    

This will require an erb template to format the email: app/views/user_mailer/activation.erb


<%= @user %>,

To activate your login for <%= @app_name %> click on this link:

  <%= user_activate_url :host => @host, :id => @user, :key => @key %>

Thanks very much,

The <%= @app_name %> team.

Hobo automatically creates activate and do_activate actions for us. So when a user clicks on the link, they will get a page with an “activate” button and no additional fields, and when they click it, the account will be activated. If you want the activation to happen automatically after the user clicks the link, override the activate method so that it calls what do_activate calls:


class UsersController < ApplicationController

  hobo_user_controller

  auto_actions :all, :except => [ :index, :create ]

  def activate
    do_transition_action :activate do
      flash[:notice] = "Your account was successfully activated, you may login now"
      redirect_to(login_url(User))
    end
  end

end

For email delivery to work there needs to be a functioning email delivery system on your server e.g. postfix.

During development, one can tail -f the development.log, the email will show up there, and paste the link back out of it into a browser to activate the account.

Clone this wiki locally