Module: HasState::ClassMethods

Defined in:
app/models/concerns/has_state.rb

Overview

Class methods

Instance Method Summary collapse

Instance Method Details

#allow_transition(transition_name, roles) ⇒ Object

Macro-style method to define which roles are allowed to trigger a transition.

It works by defining a method called allow_xxx? (where xxx is the name of the transition). If more complex check is needed, the method can be explicity defined (not using allow_transition).

Parameters:

  • transition_name (#to_sym)

    one transition

  • roles (Object)

    can be the name of a role (or the special value :requester) or an array of names (also supporting :requester)



242
243
244
245
246
247
# File 'app/models/concerns/has_state.rb', line 242

def allow_transition(transition_name, roles)
  roles = [roles].flatten.map(&:to_sym)
  define_method :"allow_#{transition_name}?" do |role_name|
    roles.include? role_name.to_sym
  end
end

#assign_state(state_name, opts = {}) ⇒ Object

Macro-style method to define the role which is responsible of the next action for a given state. Currently, this definition is only used to set default filters on lists.

Examples:

assign_state :tsp_pending, :to => :tsp

Parameters:

  • state_name (#to_sym)

    name of the state

  • opts (Hash) (defaults to: {})

    :to is the only expected (and mandatory) key



168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/models/concerns/has_state.rb', line 168

def assign_state(state_name, opts = {})
  to = opts[:to]
  if to.blank?
    false
  else
    @assigned_states ||= {}
    @assigned_states[to.to_sym] ||= []
    @assigned_states[to.to_sym] << state_name.to_sym
    @assigned_roles ||= {}
    @assigned_roles[state_name.to_sym] ||= []
    @assigned_roles[state_name.to_sym] << to.to_sym
    true
  end
end

#notify_inactiveObject

Sends notifications for all the objects that are stuck in a given state, according to the timeouts and roles specified using notify_to

See Also:

  • HasState.notify_to


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'app/models/concerns/has_state.rb', line 293

def notify_inactive
  # Leaf classes send notifications
  if subclasses.blank?
    return true unless @reminder_timeouts

    machines = joins(:user)
    @reminder_timeouts.each do |state, time|
      next unless time

      machines.where(state: state).where(["COALESCE(state_updated_at, #{table_name}.created_at) < ?", time.ago]).each do |m|
        people = @reminder_roles[state].map { |i| i.to_sym == :requester ? m.user : i }
        HasStateMailer.notify_to(people, :state, m)
      end
    end
  # But 'abstract' subclasses delegate to subclasses
  else
    subclasses.each(&:notify_inactive)
    true
  end
end

#notify_state(state_name, opts = {}) ⇒ Object

Macro-style method to define who should be notified in every state.

Understands 3 different options, all of them optional

:to Role or array of roles. Who should be notified when the machine enters the state. The special value :requester represent the requester user, despite the user's role.

:remind_after Threshold in seconds. If the machine has been in the same state for a period of time longer than the threshold, a reminder notification is sent.

:remind_to Role or array of roles. Reminders will use this, if present, instead of :to.

Examples:

notify_state :incomplete, :to => [:tsp, :requester],
                          :remind_to => :requester,
                          :remind_after => 5.days

notify_state :tsp_pending, :to => [:tsp, :requester],
                           :remind_after => 10.days

Parameters:

  • state_name (#to_sym)

    name of the state

  • opts (Hash) (defaults to: {})

    options, as explained in description.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/models/concerns/has_state.rb', line 208

def notify_state(state_name, opts = {})
  @notified_roles ||= {}
  @reminder_timeouts ||= {}
  @reminder_roles ||= {}

  st = state_name.to_sym
  to = opts[:to]
  @notified_roles[st] = if to.blank?
                          []
                        else
                          [to].flatten.map(&:to_sym)
                        end

  remind = opts.key?(:remind_to) ? opts[:remind_to] : to
  if remind.blank?
    @reminder_roles[st] = []
    @reminder_timeouts[st] = nil
  else
    @reminder_roles[st] = [remind].flatten.map(&:to_sym)
    @reminder_timeouts[st] = opts[:remind_after]
  end
  true
end

#roles_assigned_to(state) ⇒ Array

Queries the state - role assignation performed via assign_state

Parameters:

  • state (#to_sym)

    state to query

Returns:

  • (Array)

    array of roles assigned to the given state

See Also:



275
276
277
# File 'app/models/concerns/has_state.rb', line 275

def roles_assigned_to(state)
  @assigned_roles[state.to_sym] ? @assigned_roles[state.to_sym].dup : []
end

#roles_notified_when(state) ⇒ Array

Queries the state - role assignation performed via notify_state

Parameters:

  • state (#to_sym)

    state to query

Returns:

  • (Array)

    array of roles to notify when the machine reaches the given state

See Also:



285
286
287
# File 'app/models/concerns/has_state.rb', line 285

def roles_notified_when(state)
  @notified_roles[state.to_sym] ? @notified_roles[state.to_sym].dup : []
end

#states_assigned_to(role) ⇒ Array

Queries the state - role assignation performed via assign_state

Parameters:

  • role (Role, #to_sym)

    role to query

Returns:

  • (Array)

    array of states assigned to the given role

See Also:



261
262
263
264
265
266
267
268
# File 'app/models/concerns/has_state.rb', line 261

def states_assigned_to(role)
  name = if role.is_a?(UserRole)
           role.name
         else
           role.to_sym
         end
  @assigned_states[name] ? @assigned_states[name].dup : []
end

#transitionsArray<Symbol>

Defined transitions (including :cancel)

Returns:

  • (Array<Symbol>)

    transition names, as symbols



252
253
254
# File 'app/models/concerns/has_state.rb', line 252

def transitions
  state_machines[:state].events.map(&:name) + [:cancel]
end