arturo 0.2.3.8 → 1.0.0

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.
data/README.md CHANGED
@@ -1,10 +1,3 @@
1
- ## Rails 2.3
2
-
3
- This is the Rails 2.3 branch of Arturo. It is available as a gem with the
4
- version number `0.2.3.x`. See [Installation](#installation), below. For
5
- Rails 3.0 support, see the
6
- [master branch](http://github.com/jamesarosen/arturo).
7
-
8
1
  ## What
9
2
 
10
3
  Arturo provides feature sliders for Rails. It lets you turn features on and off
@@ -55,7 +48,7 @@ the feature:
55
48
 
56
49
  # in app/controllers/postings_controller:
57
50
  class PostingsController < ApplicationController
58
- require_feature :live_postings, :only => :recent
51
+ require_feature! :live_postings, :only => :recent
59
52
  # ...
60
53
  end
61
54
 
@@ -71,27 +64,25 @@ Vijay can bump the deployment percentage up to 50%. A few more days go by
71
64
  and they clean up the last few bugs they found with the "live_postings"
72
65
  feature and deploy it to all users.
73
66
 
74
- ## <span id='installation'>Installation</span>
67
+ ## Installation
75
68
 
76
- Arturo's support for Rails 2.3 is available as a gem with version
77
- numbers matching `0.2.3.x`. The best strategy is to use the
78
- "pessimistic" gem version `"~> 0.2.3"`, which will get you the
79
- latest version of Rails 2.3 support.
69
+ ### In Rails 3, with Bundler
80
70
 
81
- ### In Rails 2.3, with Bundler
71
+ gem 'arturo', '~> 1.0'
82
72
 
83
- In your `Gemfile`:
73
+ ### In Rails 3, without Bundler
84
74
 
85
- gem 'arturo', :git => 'git://github.com/jamesarosen/arturo.git',
86
- :version => '~> 0.2.3'
75
+ $ gem install arturo --version="~> 1.0"
76
+
77
+ **Note**: the following two sections describe the **intended** use of
78
+ Arturo with Rails 2. Arturo does not yet have Rails 2 support.
87
79
 
88
- Unfortunately, Rails 2.3 won't automatically load Arturo as a Plugin
89
- (and thus not pick up its "engine-ness") without **also** declaring
90
- it as a Rails gem. Thus, in `config/environment.rb`:
80
+ ### In Rails 2, with Bundler
91
81
 
92
- config.gem 'arturo', :version => '~> 0.2.3'
82
+ gem 'arturo', :git => 'git://github.com/jamesarosen/arturo.git',
83
+ :tag => 'rails_2_3'
93
84
 
94
- ### In Rails 2.3, without Bundler
85
+ ### In Rails 2, without Bundler
95
86
 
96
87
  Put the `rails_2_3` branch of `git://github.com/jamesarosen/arturo.git` into
97
88
  your `vendor/plugins/` directory. You can use Git submodules or a simple
@@ -101,18 +92,12 @@ checkout.
101
92
 
102
93
  ### In Rails
103
94
 
104
- #### Run the generators:
105
-
106
- $ script/generate arturo:migration
107
- $ script/generate arturo:initializer
108
- $ script/generate arturo:route
109
- $ script/generate arturo:assets
110
-
111
- #### Edit the generated migration as necessary
112
-
113
- #### Run the migration:
95
+ #### Run the migrations:
114
96
 
115
- $ rake db:migrate
97
+ $ rails g arturo:migration
98
+ $ rails g arturo:initializer
99
+ $ rails g arturo:route
100
+ $ rails g arturo:assets
116
101
 
117
102
  #### Edit the configuration
118
103
 
@@ -121,10 +106,10 @@ checkout.
121
106
  Open up the newly-generated `config/initializers/arturo_initializer.rb`.
122
107
  There are configuration options for the following:
123
108
 
124
- * the method that determines whether a user has permission to manage features
109
+ * the block that determines whether a user has permission to manage features
125
110
  (see [admin permissions](#adminpermissions))
126
- * the method that returns the object that has features
127
- (e.g. User, Person, or Account; see
111
+ * the block that yields the object that has features
112
+ (a User, Person, or Account, see
128
113
  [feature recipients](#featurerecipients))
129
114
  * whitelists and blacklists for features
130
115
  (see [white- and blacklisting](#wblisting))
@@ -147,19 +132,20 @@ work with you on support for your favorite framework.
147
132
 
148
133
  ### <span id='adminpermissions'>Admin Permissions</span>
149
134
 
150
- `Arturo::FeatureManagement#may_manage_features?` is a method that is run in
151
- the context of a Controller or View instance. It should return `true` if
152
- and only if the current user may manage permissions. The default implementation
153
- is as follows:
154
-
155
- current_user.present? && current_user.admin?
156
-
157
- You can change the implementation in
135
+ `Arturo.permit_management` is a block that is run in the context of
136
+ a Controller instance. It should return `true` iff the current user
137
+ can manage permissions. Configure the block in
158
138
  `config/initializers/arturo_initializer.rb`. A reasonable implementation
159
139
  might be
160
140
 
161
141
  Arturo.permit_management do
162
- signed_in? && current_user.can?(:manage_features)
142
+ current_user.admin?
143
+ end
144
+
145
+ or
146
+
147
+ Arturo.permit_management do
148
+ signed_in? && signed_in_person.can?(:manage_features)
163
149
  end
164
150
 
165
151
  ### <span id='featurerecipients'>Feature Recipients</span>
@@ -171,23 +157,35 @@ early days -- may have deployed features on a per-university basis. It wouldn't
171
157
  make much sense to deploy a feature to one user of a Basecamp project but not
172
158
  to others, so 37Signals would probably want a per-project or per-account basis.
173
159
 
174
- `Arturo::FeatureAvailability#feature_recipient` is intended to support these
175
- many use cases. It is a method that returns the current "thing" (a user, account,
176
- project, university, ...) that is a member of the category that is the basis for
177
- deploying new features. It should return an `Object` that responds to `#id`.
160
+ `Arturo.feature_recipient` is intended to support these many use cases. It is a
161
+ block that returns the current "thing" (a user, account, project, university,
162
+ ...) that is a member of the category that is the basis for deploying new
163
+ features. Like `Arturo.permit_management`, it is configured in
164
+ `config/initializers/arturo_initializer.rb`. It should return an `Object` that
165
+ responds to `#id`. If you want to deploy features on a per-user basis, a
166
+ reasonable implementation might be
178
167
 
179
- The default implementation simply returns `current_user`. Like
180
- `Arturo::FeatureManagement#may_manage_features?`, this method can be configured
181
- in `config/initializers/arturo_initializer.rb`. If you want to deploy features
182
- on a per-account basis, a reasonable implementation might be
168
+ Arturo.thing_that_has_features do
169
+ current_user
170
+ end
171
+
172
+ or
173
+
174
+ Arturo.thing_that_has_features do
175
+ signed_in_person
176
+ end
177
+
178
+ If, on the other hand, you have accounts that have many users and you
179
+ want to deploy features on a per-account basis, a reasonable implementation
180
+ might be
183
181
 
184
- Arturo.feature_recipient do
182
+ Arturo.thing_that_has_features do
185
183
  current_account
186
184
  end
187
185
 
188
186
  or
189
187
 
190
- Arturo.feature_recipient do
188
+ Arturo.thing_that_has_features do
191
189
  current_user.account
192
190
  end
193
191
 
@@ -226,10 +224,10 @@ every action within `BookHoldsController` that is invoked by a user who
226
224
  does not have the `:hold_book` feature.
227
225
 
228
226
  class BookHoldsController < ApplicationController
229
- require_feature :hold_book
227
+ require_feature! :hold_book
230
228
  end
231
229
 
232
- `require_feature` accepts as a second argument a `Hash` that it passes on
230
+ `require_feature!` accepts as a second argument a `Hash` that it passes on
233
231
  to `before_filter`, so you can use `:only` and `:except` to specify exactly
234
232
  which actions are filtered.
235
233
 
@@ -256,34 +254,15 @@ The latter can be used like so:
256
254
  widgets
257
255
  end
258
256
 
259
- #### Rack Middleware
260
-
261
- require 'arturo'
262
- use Arturo::Middleware, :feature => :my_feature
263
-
264
- #### Outside a Controller
265
-
266
- If you want to check availability outside of a controller or view (really
267
- outside of something that has `Arturo::FeatureAvailability` mixed in), you
268
- can ask either
269
-
270
- Arturo.feature_enabled_for?(:foo, recipient)
271
-
272
- or the slightly fancier
273
-
274
- Arturo.foo_enabled_for?(recipient)
275
-
276
- Both check whether the `foo` feature exists and is enabled for `recipient`.
277
-
278
257
  #### Caching
279
258
 
280
259
  **Note**: Arturo does not yet have caching support. Be very careful when
281
260
  caching actions or pages that involve feature detection as you will get
282
- strange behavior when a user who has access to a feature requests a page
261
+ strange behavior when a use who has access to a feature requests a page
283
262
  just after one who does not (and vice versa). The following is the
284
263
  **intended** support for caching.
285
264
 
286
- Both the `require_feature` before filter and the `if_feature_enabled` block
265
+ Both the `require_feature!` before filter and the `if_feature_enabled` block
287
266
  evaluation automatically append a string based on the feature's
288
267
  `last_modified` timestamp to cache keys that Rails generates. Thus, you don't
289
268
  have to worry about expiring caches when you increase a feature's deployment
@@ -293,4 +272,4 @@ percentage. See `Arturo::CacheSupport` for more information.
293
272
 
294
273
  Arturo gets its name from
295
274
  [Professor Maximillian Arturo](http://en.wikipedia.org/wiki/Maximillian_Arturo)
296
- on [Sliders](http://en.wikipedia.org/wiki/Sliders).
275
+ on [Sliders](http://en.wikipedia.org/wiki/Sliders).
@@ -1,23 +1,30 @@
1
1
  require 'action_controller'
2
2
 
3
+ # TODO: this doesn't do anything radically out of the ordinary.
4
+ # Are there Rails 3 patterns/mixins/methods I can use
5
+ # to clean it up a bit?
3
6
  module Arturo
4
7
 
8
+ begin
9
+ require 'application_controller'
10
+ rescue LoadError
11
+ # do nothing
12
+ end
13
+
14
+ base = Object.const_defined?(:ApplicationController) ? ApplicationController : ActionController::Base
15
+
5
16
  # Handles all Feature actions. Clients of the Arturo engine
6
17
  # should redefine Arturo::FeaturesController#permitted? to
7
18
  # return true only for users who are permitted to manage features.
8
- class FeaturesController < ApplicationController
9
- include Arturo::FeatureManagement
10
-
19
+ class FeaturesController < base
20
+ unloadable
21
+ respond_to :html, :json, :xml
11
22
  before_filter :require_permission
12
23
  before_filter :load_feature, :only => [ :show, :edit, :update, :destroy ]
13
24
 
14
25
  def index
15
26
  @features = Arturo::Feature.all
16
- respond_to do |format|
17
- format.html { }
18
- format.json { render :json => @features }
19
- format.xml { render :xml => @features }
20
- end
27
+ respond_with @features
21
28
  end
22
29
 
23
30
  def update_all
@@ -43,20 +50,12 @@ module Arturo
43
50
  end
44
51
 
45
52
  def show
46
- respond_to do |format|
47
- format.html { }
48
- format.json { render :json => @feature }
49
- format.xml { render :xml => @feature }
50
- end
53
+ respond_with @feature
51
54
  end
52
55
 
53
56
  def new
54
57
  @feature = Arturo::Feature.new(params[:feature])
55
- respond_to do |format|
56
- format.html { }
57
- format.json { render :json => @feature }
58
- format.xml { render :xml => @feature }
59
- end
58
+ respond_with @feature
60
59
  end
61
60
 
62
61
  def create
@@ -71,11 +70,7 @@ module Arturo
71
70
  end
72
71
 
73
72
  def edit
74
- respond_to do |format|
75
- format.html { }
76
- format.json { render :json => @feature }
77
- format.xml { render :xml => @feature }
78
- end
73
+ respond_with @feature
79
74
  end
80
75
 
81
76
  def update
@@ -83,7 +78,7 @@ module Arturo
83
78
  flash[:notice] = t('arturo.features.flash.updated', :name => @feature.to_s)
84
79
  redirect_to feature_path(@feature)
85
80
  else
86
- flash[:alert] = t('arturo.features.flash.error_updating', :name => @feature.to_s)
81
+ flash[:alert] = t('arturo.features.flash.error_updating', :name => @feature.to_s)
87
82
  render :action => 'edit'
88
83
  end
89
84
  end
@@ -100,7 +95,7 @@ module Arturo
100
95
  protected
101
96
 
102
97
  def require_permission
103
- unless may_manage_features?
98
+ unless Arturo.permit_management?(self)
104
99
  render :action => 'forbidden', :status => 403
105
100
  return false
106
101
  end
@@ -23,5 +23,15 @@ module Arturo
23
23
  def deployment_percentage_output_tag(id, value)
24
24
  content_tag(:output, value, { 'for' => id, 'class' => 'deployment_percentage no_js' })
25
25
  end
26
+
27
+ def error_messages_for(feature, attribute)
28
+ if feature.errors[attribute].any?
29
+ content_tag(:ul, :class => 'errors') do
30
+ feature.errors[attribute].map { |msg| content_tag(:li, msg, :class => 'error') }.join(''.html_safe)
31
+ end
32
+ else
33
+ ''
34
+ end
35
+ end
26
36
  end
27
37
  end
@@ -8,7 +8,7 @@ module Arturo
8
8
  include Arturo::SpecialHandling
9
9
 
10
10
  Arturo::Feature::SYMBOL_REGEX = /^[a-zA-z][a-zA-Z0-9_]*$/
11
- DEFAULT_ATTRIBUTES = { :deployment_percentage => 0 }.with_indifferent_access
11
+ DEFAULT_ATTRIBUTES = { :deployment_percentage => 0 }
12
12
 
13
13
  attr_readonly :symbol
14
14
 
@@ -25,11 +25,7 @@ module Arturo
25
25
  # @return [Arturo::Feature, nil] the Feature if found, else nil
26
26
  def self.to_feature(feature_or_symbol)
27
27
  return feature_or_symbol if feature_or_symbol.kind_of?(self)
28
- self.find(:first, :conditions => { :symbol => feature_or_symbol.to_s })
29
- end
30
-
31
- def self.find_feature(*args)
32
- to_feature(*args)
28
+ self.where(:symbol => feature_or_symbol.to_sym).first
33
29
  end
34
30
 
35
31
  # Create a new Feature
@@ -60,29 +56,21 @@ module Arturo
60
56
  end
61
57
 
62
58
  def to_param
63
- new_record? ? nil : "#{id}-#{symbol.to_s.parameterize}"
59
+ persisted? ? "#{id}-#{symbol.to_s.parameterize}" : nil
64
60
  end
65
61
 
66
62
  def inspect
67
63
  "<Arturo::Feature #{name}, deployed to #{deployment_percentage}%>"
68
64
  end
69
65
 
70
- def symbol
71
- sym = read_attribute(:symbol).to_s
72
- sym.blank? ? nil : sym.to_sym
73
- end
74
-
75
- def symbol=(sym)
76
- write_attribute(:symbol, sym.to_s)
77
- end
78
-
79
66
  protected
80
67
 
81
68
  def passes_threshold?(feature_recipient)
82
69
  threshold = self.deployment_percentage || 0
83
- return false if threshold == 0 || !feature_recipient.id
70
+ return false if threshold == 0
84
71
  return true if threshold == 100
85
- (((feature_recipient.id + (self.id || 1) + 17) * 13) % 100) < threshold
72
+ (((feature_recipient.id + 17) * 13) % 100) < threshold
73
+
86
74
  end
87
75
  end
88
76
  end
@@ -1,19 +1,15 @@
1
- <% form_for(:feature, feature,
2
- :url => (feature.new_record? ? features_path : feature_path(feature)),
3
- :html => {
4
- :method => (feature.new_record? ? :post : :put)
5
- }) do |form| %>
1
+ <%= form_for(feature, :as => 'feature', :url => (feature.new_record? ? features_path : feature_path(feature))) do |form| %>
6
2
  <fieldset>
7
3
  <legend><%= legend %></legend>
8
4
 
9
- <%= error_messages_for(:feature, :object => feature) %>
10
-
11
5
  <%= form.label(:symbol) %>
12
6
  <%= form.text_field(:symbol, :required => 'required', :pattern => Arturo::Feature::SYMBOL_REGEX.source, :class => 'symbol') %>
7
+ <%= error_messages_for(feature, :symbol) %>
13
8
 
14
9
  <%= form.label(:deployment_percentage) %>
15
10
  <%= form.range_field(:deployment_percentage, :min => '0', :max => '100', :step => '1', :class => 'deployment_percentage') %>
16
11
  <%= deployment_percentage_output_tag 'feature_deployment_percentage', feature.deployment_percentage %>
12
+ <%= error_messages_for(feature, :deployment_percentage) %>
17
13
 
18
14
  <footer><%= form.submit %></footer>
19
15
  </fieldset>
@@ -1,5 +1,5 @@
1
1
  <h2><%= t('.title') %></h2>
2
- <% form_tag(features_path, :method => 'put', 'data-update-path' => feature_path(:id => ':id'), :remote => true) do %>
2
+ <%= form_tag(features_path, :method => 'put', 'data-update-path' => feature_path(:id => ':id'), :remote => true) do %>
3
3
  <fieldset>
4
4
  <legend><%= t('.title') %></legend>
5
5
  <table class='features'>
data/config/routes.rb ADDED
@@ -0,0 +1,14 @@
1
+ # In Rails edge, the engine can have its own route set
2
+ # and be mounted within an application at a sub-URL.
3
+ # In 3.0.1, this is not yet available.
4
+
5
+ # TODO replace this with the commented-out version below
6
+ Rails.application.routes.draw do
7
+ resources :features, :controller => 'arturo/features'
8
+ put 'features', :to => 'arturo/features#update_all', :as => 'features'
9
+ end
10
+
11
+ # Arturo::Engine.routes.draw do
12
+ # resources :features, :controller => 'arturo/features'
13
+ # put 'features', :to => 'arturo/features#update_all', :as => 'features'
14
+ # end
data/lib/arturo.rb CHANGED
@@ -1,38 +1,9 @@
1
1
  module Arturo
2
2
 
3
+ require 'arturo/configuration'
3
4
  require 'arturo/special_handling'
4
5
  require 'arturo/feature_availability'
5
- require 'arturo/feature_management'
6
- require 'arturo/feature_caching'
7
6
  require 'arturo/controller_filters'
8
- require 'arturo/range_form_support'
9
- require 'arturo/middleware'
10
- require 'arturo/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 2 && Rails::VERSION::MINOR == 3
7
+ require 'arturo/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
11
8
 
12
- class <<self
13
-
14
- # Quick check for whether a feature is enabled for a recipient.
15
- # @param [String, Symbol] feature_name
16
- # @param [#id] recipient
17
- # @return [true,false] whether the feature exists and is enabled for the recipient
18
- def feature_enabled_for?(feature_name, recipient)
19
- f = self::Feature.to_feature(feature_name)
20
- f && f.enabled_for?(recipient)
21
- end
22
-
23
- ENABLED_FOR_METHOD_NAME = /^(\w+)_enabled_for\?$/
24
-
25
- def respond_to?(symbol)
26
- symbol.to_s =~ ENABLED_FOR_METHOD_NAME || super(symbol)
27
- end
28
-
29
- def method_missing(symbol, *args, &block)
30
- if (args.length == 1 && match = ENABLED_FOR_METHOD_NAME.match(symbol.to_s))
31
- feature_enabled_for?(match[1], args[0])
32
- else
33
- super(symbol, *args, &block)
34
- end
35
- end
36
-
37
- end
38
9
  end