Class: Trebuchet

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/trebuchet.rb,
lib/trebuchet/version.rb,
lib/trebuchet/strategy/stub.rb,
lib/trebuchet/feature/stubbing.rb

Defined Under Namespace

Modules: ActionController, Backend, Strategy Classes: ActionControllerFilter, BackendError, BackendInitializationError, Error, Feature

Constant Summary collapse

SHA1 =

initialize a single one to save object allocations Todo perhaps choose a better hash instead of sha1

Digest::SHA1.new
VERSION =
"0.12.1".freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(current_user, request = nil) ⇒ Trebuchet

Returns a new instance of Trebuchet.



136
137
138
139
140
# File 'lib/trebuchet.rb', line 136

def initialize(current_user, request = nil)
  @current_user = current_user
  @request = request
  @result_cache = {}
end

Class Attribute Details

.exception_handlerObject

Returns the value of attribute exception_handler.



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

def exception_handler
  @exception_handler
end

.threadsafe_stateObject Also known as: threadsafe_state?

Returns the value of attribute threadsafe_state.



16
17
18
# File 'lib/trebuchet.rb', line 16

def threadsafe_state
  @threadsafe_state
end

Class Method Details

.aim(feature_name, *args) ⇒ Object



96
97
98
# File 'lib/trebuchet.rb', line 96

def self.aim(feature_name, *args)
  Feature.find(feature_name).aim(*args)
end

.backendObject



18
19
20
21
# File 'lib/trebuchet.rb', line 18

def backend
  self.backend = :memory unless @backend
  @backend
end

.currentObject



57
58
59
60
# File 'lib/trebuchet.rb', line 57

def current
  state.current ||= current_block.call if current_block.respond_to?(:call)
  state.current || new(nil) # return an blank Trebuchet instance if @current is not set
end

.current=(other) ⇒ Object



53
54
55
# File 'lib/trebuchet.rb', line 53

def current=(other)
  state.current = other
end

.define_request_aware_strategy(name, &block) ⇒ Object



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

def self.define_request_aware_strategy(name, &block)
  Strategy::CustomRequestAware.define(name, block)
end

.define_strategy(name, &block) ⇒ Object



108
109
110
# File 'lib/trebuchet.rb', line 108

def self.define_strategy(name, &block)
  Strategy::Custom.define(name, block)
end

.dismantle(feature_name) ⇒ Object



100
101
102
# File 'lib/trebuchet.rb', line 100

def self.dismantle(feature_name)
  Feature.find(feature_name).dismantle
end

.dismantle_stubsObject



104
105
106
# File 'lib/trebuchet.rb', line 104

def self.dismantle_stubs
  Feature.dismantle_stubs
end

.exportObject



171
172
173
174
175
176
177
# File 'lib/trebuchet.rb', line 171

def self.export
  {}.tap do |features|
    Trebuchet.backend.get_feature_names.map do |fn|
      features[fn] = self.feature(fn).strategy.export
    end
  end
end

.feature(name) ⇒ Object



132
133
134
# File 'lib/trebuchet.rb', line 132

def self.feature(name)
  Feature.find(name)
end

.history(include_archived = false) ⇒ Object



179
180
181
182
183
184
# File 'lib/trebuchet.rb', line 179

def self.history(include_archived = false)
  return [] unless Trebuchet.backend.respond_to?(:get_all_history)
  Trebuchet.backend.get_all_history(include_archived).map do |row|
    [Time.at(row.first), Feature.find(row.last)]
  end
end

.initialize_logsObject

Logging done at class level TODO: split by user identifier so instance can return scoped to one user (in case multiple users have user.trebuchet called)



40
41
42
# File 'lib/trebuchet.rb', line 40

def initialize_logs
  state.logs = {}
end

.log(feature_name, result) ⇒ Object



44
45
46
47
# File 'lib/trebuchet.rb', line 44

def log(feature_name, result)
  initialize_logs if state.logs.nil?
  logs[feature_name] = result
end

.logsObject



49
50
51
# File 'lib/trebuchet.rb', line 49

def logs
  state.logs
end

.reset_current!Object



62
63
64
# File 'lib/trebuchet.rb', line 62

def reset_current!
  self.current = nil
end

.set_backend(backend_type, *args) ⇒ Object Also known as: backend=



23
24
25
26
27
28
29
30
# File 'lib/trebuchet.rb', line 23

def set_backend(backend_type, *args)
  if backend_type.is_a?(Symbol)
    require "trebuchet/backend/#{backend_type}"
    @backend = Backend.lookup(backend_type).new(*args)
  elsif backend_type.class.name =~ /Trebuchet::Backend/
    @backend = backend_type
  end
end

.stateObject

state is a representation of the current context of Trebuchet such as current and current_proc, which are expected to be different between threads or fibers. exception_handler and backend are not included in this state object as they are not expected to change from fiber to fiber or request to request, therefore they must be thread/fibersafe on their own accord.



75
76
77
78
79
80
81
# File 'lib/trebuchet.rb', line 75

def state
  if threadsafe_state?
    Thread.current[thread_local_key] ||= State.new
  else
    @state ||= State.new
  end
end

.state=(new_state) ⇒ Object



83
84
85
86
87
88
89
# File 'lib/trebuchet.rb', line 83

def state=(new_state)
  if threadsafe_state?
    Thread.current[thread_local_key] = new_state
  else
    @state = new_state
  end
end

.thread_local_keyObject



66
67
68
# File 'lib/trebuchet.rb', line 66

def thread_local_key
  :trebuchet_state
end

.use_with_rails!Object



126
127
128
129
130
# File 'lib/trebuchet.rb', line 126

def self.use_with_rails!
  if defined?(ActionController::Base)
    ActionController::Base.send(:include, Trebuchet::ActionController)
  end
end

.visitor_id=(id_or_proc) ⇒ Object



116
117
118
119
120
121
122
123
124
# File 'lib/trebuchet.rb', line 116

def self.visitor_id=(id_or_proc)
  if id_or_proc.is_a?(Proc)
    state.visitor_id = id_or_proc
  elsif id_or_proc.is_a?(Integer)
    state.visitor_id = proc { |request| id_or_proc }
  else
    state.visitor_id = nil
  end
end

Instance Method Details

#handle_exception(exception, feature = nil) ⇒ Object



163
164
165
166
167
168
169
# File 'lib/trebuchet.rb', line 163

def handle_exception(exception, feature = nil)
  if self.class.exception_handler.is_a?(Proc)
    argc = self.class.exception_handler.arity
    argc = 3 if argc < 0
    self.class.exception_handler.call *[exception, feature, self][0,argc]
  end
end

#launch(feature, &block) ⇒ Object



142
143
144
145
146
# File 'lib/trebuchet.rb', line 142

def launch(feature, &block)
  if launch?(feature)
    yield if block_given?
  end
end

#launch?(feature) ⇒ Boolean

Returns:

  • (Boolean)


148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/trebuchet.rb', line 148

def launch?(feature)
  result = @result_cache[feature]

  if result.nil?
    result = @result_cache[feature] =
      !!Feature.find(feature).launch_at?(@current_user, @request)
    Trebuchet.log(feature, result)
  end

  result
rescue => e
  handle_exception(e, feature)
  return false
end