arturo 0.2.3.7 → 0.2.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,10 @@ module Arturo
28
28
  self.find(:first, :conditions => { :symbol => feature_or_symbol.to_s })
29
29
  end
30
30
 
31
+ def self.find_feature(*args)
32
+ to_feature(*args)
33
+ end
34
+
31
35
  # Create a new Feature
32
36
  def initialize(attributes = {})
33
37
  super(DEFAULT_ATTRIBUTES.merge(attributes || {}))
@@ -76,7 +80,7 @@ module Arturo
76
80
 
77
81
  def passes_threshold?(feature_recipient)
78
82
  threshold = self.deployment_percentage || 0
79
- return false if threshold == 0
83
+ return false if threshold == 0 || !feature_recipient.id
80
84
  return true if threshold == 100
81
85
  (((feature_recipient.id + (self.id || 1) + 17) * 13) % 100) < threshold
82
86
  end
@@ -16,13 +16,21 @@ module Arturo
16
16
  # to use a shared cache like Memcached.
17
17
  module FeatureCaching
18
18
 
19
+ # A marker in the cache to record the fact that the feature with the
20
+ # given symbol doesn't exist.
21
+ NO_SUCH_FEATURE = :NO_SUCH_FEATURE
22
+
19
23
  def self.extended(base)
20
- class <<base
24
+ class << base
21
25
  alias_method_chain :to_feature, :caching
22
- attr_accessor :cache_ttl, :feature_cache
26
+ attr_accessor :cache_ttl, :feature_cache, :feature_caching_strategy
27
+ end
28
+ base.send(:after_save) do |f|
29
+ f.class.feature_caching_strategy.expire(f.class.feature_cache, f.symbol.to_sym) if f.class.caches_features?
23
30
  end
24
31
  base.cache_ttl = 0
25
32
  base.feature_cache = Arturo::FeatureCaching::Cache.new
33
+ base.feature_caching_strategy = AllStrategy
26
34
  end
27
35
 
28
36
  def caches_features?
@@ -31,28 +39,75 @@ module Arturo
31
39
 
32
40
  # Wraps Arturo::Feature.to_feature with in-memory caching.
33
41
  def to_feature_with_caching(feature_or_symbol)
34
- if !self.caches_features?
35
- return to_feature_without_caching(feature_or_symbol)
36
- elsif (feature_or_symbol.kind_of?(Arturo::Feature))
37
- feature_cache.write(feature_or_symbol.symbol, feature_or_symbol, :expires_in => cache_ttl)
42
+ if !caches_features?
43
+ to_feature_without_caching(feature_or_symbol)
44
+ elsif feature_or_symbol.kind_of?(Arturo::Feature)
38
45
  feature_or_symbol
39
- elsif (cached_feature = feature_cache.read(feature_or_symbol.to_sym))
40
- cached_feature
41
- elsif (f = to_feature_without_caching(feature_or_symbol))
42
- feature_cache.write(f.symbol, f, :expires_in => cache_ttl)
43
- f
46
+ else
47
+ symbol = feature_or_symbol.to_sym
48
+ feature = feature_caching_strategy.fetch(feature_cache, symbol) { to_feature_without_caching(symbol) }
49
+ feature unless feature == NO_SUCH_FEATURE
50
+ end
51
+ end
52
+
53
+ def warm_cache!
54
+ warn "Deprecated, no longer necessary!"
55
+ end
56
+
57
+ class AllStrategy
58
+ class << self
59
+ def fetch(cache, symbol, &block)
60
+ features = cache.read("arturo.all")
61
+
62
+ unless cache_is_current?(cache, features)
63
+ features = Hash[Arturo::Feature.all.map { |f| [f.symbol.to_sym, f] }]
64
+ mark_as_current!(cache)
65
+ cache.write("arturo.all", features, :expires_in => Arturo::Feature.cache_ttl * 10)
66
+ end
67
+
68
+ features[symbol] || NO_SUCH_FEATURE
69
+ end
70
+
71
+ def expire(cache, symbol)
72
+ cache.delete("arturo.all")
73
+ end
74
+
75
+ private
76
+
77
+ def cache_is_current?(cache, features)
78
+ return unless features
79
+ return true if cache.read("arturo.current")
80
+ return false if features.values.map(&:updated_at).compact.max != Arturo::Feature.maximum(:updated_at)
81
+ mark_as_current!(cache)
82
+ end
83
+
84
+ def mark_as_current!(cache)
85
+ cache.write("arturo.current", true, :expires_in => Arturo::Feature.cache_ttl)
86
+ end
44
87
  end
45
88
  end
46
89
 
47
- protected
90
+ class OneStrategy
91
+ def self.fetch(cache, symbol, &block)
92
+ if feature = cache.read("arturo.#{symbol}")
93
+ feature
94
+ else
95
+ cache.write("arturo.#{symbol}", yield || NO_SUCH_FEATURE, :expires_in => Arturo::Feature.cache_ttl)
96
+ end
97
+ end
98
+
99
+ def self.expire(cache, symbol)
100
+ cache.delete("arturo.#{symbol}")
101
+ end
102
+ end
48
103
 
49
104
  # Quack like a Rails cache.
50
105
  class Cache
51
106
  def initialize
52
107
  @data = {} # of the form {key => [value, expires_at or nil]}
53
108
  end
109
+
54
110
  def read(name, options = nil)
55
- name = name.to_s
56
111
  value, expires_at = *@data[name]
57
112
  if value && (expires_at.blank? || expires_at > Time.now)
58
113
  value
@@ -60,8 +115,12 @@ module Arturo
60
115
  nil
61
116
  end
62
117
  end
118
+
119
+ def delete(name)
120
+ @data.delete(name)
121
+ end
122
+
63
123
  def write(name, value, options = nil)
64
- name = name.to_s
65
124
  expires_at = if options && options.respond_to?(:[]) && options[:expires_in]
66
125
  Time.now + options.delete(:expires_in)
67
126
  else
@@ -71,6 +130,7 @@ module Arturo
71
130
  @data[name] = [value, expires_at]
72
131
  end
73
132
  end
133
+
74
134
  def clear
75
135
  @data.clear
76
136
  end
@@ -78,4 +138,4 @@ module Arturo
78
138
 
79
139
  end
80
140
 
81
- end
141
+ end
@@ -9,6 +9,7 @@ Arturo.instance_eval do
9
9
  def enable_feature!(name)
10
10
  feature = Arturo::Feature.to_feature(name)
11
11
  if feature
12
+ feature = feature.class.find(feature.id) if feature.frozen?
12
13
  feature.update_attributes(:deployment_percentage => 100)
13
14
  else
14
15
  Arturo::Feature.create!(:symbol => name, :deployment_percentage => 100)
@@ -23,6 +24,7 @@ Arturo.instance_eval do
23
24
  # @param [Symbol, String] name the feature name
24
25
  def disable_feature!(name)
25
26
  if (feature = Arturo::Feature.to_feature(name))
27
+ feature = feature.class.find(feature.id) if feature.frozen?
26
28
  feature.update_attributes(:deployment_percentage => 0)
27
29
  end
28
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arturo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3.7
4
+ version: 0.2.3.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-01 00:00:00.000000000Z
12
+ date: 2013-10-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &2160470160 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,31 @@ dependencies:
21
21
  version: 2.3.8
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2160470160
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.3.8
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: mocha
27
- requirement: &2160469760 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.13.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.13.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
28
49
  none: false
29
50
  requirements:
30
51
  - - ! '>='
@@ -32,10 +53,15 @@ dependencies:
32
53
  version: '0'
33
54
  type: :development
34
55
  prerelease: false
35
- version_requirements: *2160469760
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
36
62
  - !ruby/object:Gem::Dependency
37
- name: rake
38
- requirement: &2160469300 !ruby/object:Gem::Requirement
63
+ name: bump
64
+ requirement: !ruby/object:Gem::Requirement
39
65
  none: false
40
66
  requirements:
41
67
  - - ! '>='
@@ -43,21 +69,47 @@ dependencies:
43
69
  version: '0'
44
70
  type: :development
45
71
  prerelease: false
46
- version_requirements: *2160469300
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
47
78
  - !ruby/object:Gem::Dependency
48
- name: redgreen
49
- requirement: &2160468800 !ruby/object:Gem::Requirement
79
+ name: minitest
80
+ requirement: !ruby/object:Gem::Requirement
50
81
  none: false
51
82
  requirements:
52
- - - ~>
83
+ - - <
84
+ - !ruby/object:Gem::Version
85
+ version: '5'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - <
53
92
  - !ruby/object:Gem::Version
54
- version: '1.2'
93
+ version: '5'
94
+ - !ruby/object:Gem::Dependency
95
+ name: minitest-rg
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
55
102
  type: :development
56
103
  prerelease: false
57
- version_requirements: *2160468800
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
58
110
  - !ruby/object:Gem::Dependency
59
111
  name: sqlite3-ruby
60
- requirement: &2160468300 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
61
113
  none: false
62
114
  requirements:
63
115
  - - ~>
@@ -65,10 +117,15 @@ dependencies:
65
117
  version: '1.3'
66
118
  type: :development
67
119
  prerelease: false
68
- version_requirements: *2160468300
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '1.3'
69
126
  - !ruby/object:Gem::Dependency
70
127
  name: factory_girl
71
- requirement: &2160467840 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
72
129
  none: false
73
130
  requirements:
74
131
  - - ~>
@@ -76,10 +133,15 @@ dependencies:
76
133
  version: '1.3'
77
134
  type: :development
78
135
  prerelease: false
79
- version_requirements: *2160467840
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '1.3'
80
142
  - !ruby/object:Gem::Dependency
81
143
  name: timecop
82
- requirement: &2160467380 !ruby/object:Gem::Requirement
144
+ requirement: !ruby/object:Gem::Requirement
83
145
  none: false
84
146
  requirements:
85
147
  - - ~>
@@ -87,7 +149,12 @@ dependencies:
87
149
  version: '0.3'
88
150
  type: :development
89
151
  prerelease: false
90
- version_requirements: *2160467380
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: '0.3'
91
158
  description: Deploy features incrementally to your users
92
159
  email: james.a.rosen@gmail.com
93
160
  executables: []
@@ -147,8 +214,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
214
  version: '0'
148
215
  requirements: []
149
216
  rubyforge_project:
150
- rubygems_version: 1.8.6
217
+ rubygems_version: 1.8.25
151
218
  signing_key:
152
- specification_version: 2
219
+ specification_version: 3
153
220
  summary: Feature sliders, wrapped up in an engine
154
221
  test_files: []
222
+ has_rdoc: