Class: Noteikumi::Rule

Inherits:
Object
  • Object
show all
Defined in:
lib/noteikumi/rule.rb

Overview

A class that represents an individual rule used by the engine

Rules are generally stored in files named something_rule.rb in a rule directory, there are several samples of these in the examples dir and in docs on the wiki at GitHub

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ Rule

Creates a new rule

Parameters:

  • name (String, Symbol)

    the name of the rule



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/noteikumi/rule.rb', line 71

def initialize(name)
  @name = name
  @priority = 500
  @concurrent = :unsafe
  @needs = []
  @conditions = {}
  @state = nil
  @file = "unknown file"
  @run_count = 0

  run_when { true }
  run { raise("No execution logic provided for rule") }
end

Instance Attribute Details

#concurrent:safe, :unsafe (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The concurrency safe level

Returns:

  • (:safe, :unsafe)

See Also:

  • {concurrency=}


33
34
35
# File 'lib/noteikumi/rule.rb', line 33

def concurrent
  @concurrent
end

#conditionsHash (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Named conditions for this rule

Returns:

  • (Hash)

See Also:

  • {condition}


21
22
23
# File 'lib/noteikumi/rule.rb', line 21

def conditions
  @conditions
end

#fileString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The file the rule was found in

Returns:

  • (String)


55
56
57
# File 'lib/noteikumi/rule.rb', line 55

def file
  @file
end

#loggerLogger

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The logger used by this rule

Returns:

  • (Logger)


65
66
67
# File 'lib/noteikumi/rule.rb', line 65

def logger
  @logger
end

#nameString, Symbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The rule name

Returns:

  • (String, Symbol)


60
61
62
# File 'lib/noteikumi/rule.rb', line 60

def name
  @name
end

#needsArray (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Items the rule expect on the state

Returns:

  • (Array)

See Also:

  • {requirement}


27
28
29
# File 'lib/noteikumi/rule.rb', line 27

def needs
  @needs
end

#priorityFixnum (readonly)

The priority for this fule

Returns:

  • (Fixnum)


10
11
12
# File 'lib/noteikumi/rule.rb', line 10

def priority
  @priority
end

#run_conditionProc (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The run conditions for this rule

Returns:

  • (Proc)

See Also:

  • {run_when}


39
40
41
# File 'lib/noteikumi/rule.rb', line 39

def run_condition
  @run_condition
end

#run_countFixnum (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

How many times this rule have been run

Returns:

  • (Fixnum)


50
51
52
# File 'lib/noteikumi/rule.rb', line 50

def run_count
  @run_count
end

#run_logicProc (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The logic to run

Returns:

  • (Proc)

See Also:

  • {run}


45
46
47
# File 'lib/noteikumi/rule.rb', line 45

def run_logic
  @run_logic
end

#stateState? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The state this rule is being evaluated against

Returns:



15
16
17
# File 'lib/noteikumi/rule.rb', line 15

def state
  @state
end

Instance Method Details

#assign_state(state) ⇒ void

This method returns an undefined value.

Assign the provided state to the rule

Parameters:

  • state (State)

    the state to store



98
99
100
# File 'lib/noteikumi/rule.rb', line 98

def assign_state(state)
  @state = state
end

#concurrency=(level) ⇒ :safe, :unsafe

Sets the concurrency safe level

This is mainly not used now but will result in the state becoming immutable when the level is :safe. This is with an eye on supporting parallel or threaded execution of rules in the long term

Parameters:

  • level (:safe, :unsafe)

Returns:

  • (:safe, :unsafe)


250
251
252
253
254
# File 'lib/noteikumi/rule.rb', line 250

def concurrency=(level)
  raise("Concurrency has to be one of :safe, :unsafe") unless [:safe, :unsafe].include?(level)

  @concurrent = level
end

#concurrent_safe?Boolean

Determines if the concurrency level is :safe

Returns:

  • (Boolean)


259
260
261
# File 'lib/noteikumi/rule.rb', line 259

def concurrent_safe?
  @concurrent == :safe
end

#condition(name, &blk) ⇒ void

Note:

these blocks must always return boolean and will be coerced to that

This method returns an undefined value.

Creates a named condition

Examples:

create and use a condition


condition(:weekend?) { Time.now.wday > 5 }
condition(:daytime?) { Time.now.hour.between?(9, 18) }

run_when { weekend? || !daytime? }

Parameters:

  • name (Symbol)

    a unique name for this condition

  • blk (Proc)

    the code to run when this condition is called



276
277
278
279
280
281
282
283
# File 'lib/noteikumi/rule.rb', line 276

def condition(name, &blk)
  raise("Duplicate condition name %s" % name) if @conditions[name]
  raise("A block is required for condition %s" % name) unless block_given?

  @conditions[name] = blk

  nil
end

#has_condition?(condition) ⇒ Boolean

Checks if a condition matching the name has been created on the rule

Parameters:

  • condition (Symbol)

    condition name

Returns:

  • (Boolean)

See Also:

  • Noteikumi::Rule.{{#condition}


90
91
92
# File 'lib/noteikumi/rule.rb', line 90

def has_condition?(condition)
  @conditions.include?(condition)
end

#new_resultResult

Construct a result object for this rule

Returns:



155
156
157
# File 'lib/noteikumi/rule.rb', line 155

def new_result
  Result.new(self)
end

#process(state) ⇒ Result?

Process a rule after first checking all the requirements are met

Parameters:

  • state (State)

    the state to use as scope

Returns:

  • (Result, nil)

    nil when the rule never ran due to state checks



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/noteikumi/rule.rb', line 163

def process(state)
  result = nil

  with_state(state) do
    if state_meets_requirements?
      if satisfies_run_condition?
        logger.debug("Processing rule %s" % self)
        result = run_rule_logic
      else
        logger.debug("Skipping processing rule due to run_when block returning false on %s" % self)
      end
    else
      logger.debug("Skipping processing rule %s due to state check failing" % self)
    end
  end

  result
end

#requirement(*args) ⇒ void

This method returns an undefined value.

Sets a requirement that the state should meet

Examples:

require any scope item with a specific type


requirement nil, String

require that a specific item should be of a type


requirement :thing, String

Parameters:

  • args (Array)

    of requirements



297
298
299
300
301
302
303
304
305
306
# File 'lib/noteikumi/rule.rb', line 297

def requirement(*args)
  case args.size
  when 1
    @needs << [nil, args[0]]
  when 2
    @needs << args
  else
    raise("Unsupported requirement input %s" % args.inspect)
  end
end

#reset_countervoid

This method returns an undefined value.

Resets the run count for the rule to 0



112
113
114
# File 'lib/noteikumi/rule.rb', line 112

def reset_counter
  @run_count = 0
end

#reset_statevoid

This method returns an undefined value.

Resets the state to nil state



105
106
107
# File 'lib/noteikumi/rule.rb', line 105

def reset_state
  @state = nil
end

#rule_priority(priority) ⇒ Fixnum

Sets the rule priority

Parameters:

  • priority (Fixnum)

Returns:

  • (Fixnum)


238
239
240
# File 'lib/noteikumi/rule.rb', line 238

def rule_priority(priority)
  @priority = Integer(priority)
end

#run(&blk) ⇒ void

This method returns an undefined value.

Creates the logic that will be run when all the conditions are met

Parameters:

  • blk (Proc)

    the logic to run

See Also:



229
230
231
232
# File 'lib/noteikumi/rule.rb', line 229

def run(&blk)
  raise("A block is needed to run") unless block_given?
  @run_logic = blk
end

#run_rule_logicResult

Runs the rule logic

Rules are run within an instance of Noteikumi::RuleExecutionScope

Returns:



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/noteikumi/rule.rb', line 133

def run_rule_logic
  @run_count += 1

  result = new_result

  begin
    result.start_processing
    result.output = RuleExecutionScope.new(self).run
  rescue => e
    logger.error("Error during processing of rule: %s: %s: %s" % [self, e.class, e.to_s])
    logger.debug(e.backtrace.join("\n\t"))
    result.exception = e
  ensure
    result.stop_processing
  end

  result
end

#run_when(&blk) ⇒ void

This method returns an undefined value.

Logic to execute once state has met to determine if the rule should be run

Parameters:

  • blk (Proc)

    the checking logic that should return boolean

See Also:



217
218
219
220
# File 'lib/noteikumi/rule.rb', line 217

def run_when(&blk)
  raise("A block is needed to evaluate for run_when") unless block_given?
  @run_condition = blk
end

#satisfies_run_condition?Boolean

Determines if the run_when block is satisfied

Returns:

  • (Boolean)


185
186
187
188
# File 'lib/noteikumi/rule.rb', line 185

def satisfies_run_condition?
  validator = RuleConditionValidator.new(self)
  validator.__should_run?
end

#state_meets_requirements?Boolean

Checks every requirement against the state

Returns:

  • (Boolean)


193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/noteikumi/rule.rb', line 193

def state_meets_requirements?
  @needs.each do |requirement|
    valid, reason = state.meets_requirement?(requirement)

    unless valid
      logger.debug("State does not meet the requirements %s: %s" % [self, reason])
      return false
    end
  end

  true
end

#to_sString

:nodoc:

Returns:

  • (String)


208
209
210
# File 'lib/noteikumi/rule.rb', line 208

def to_s
  "#<%s:%s run_count: %d priority: %d name: %s @ %s>" % [self.class, object_id, run_count, priority, name, file]
end

#with_state(state) ⇒ Object

Assigns the state, yields to the block and resets it

Parameters:

  • state (State)

    a state to act on

Returns:

  • (Object)

    the outcome from the block



120
121
122
123
124
125
126
# File 'lib/noteikumi/rule.rb', line 120

def with_state(state)
  assign_state(state)

  yield
ensure
  reset_state
end