arturo 0.2.3.7 → 0.2.3.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: