arturo 0.2.3.8 → 1.0.0

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