ee_arturo 1.3.4

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.
Files changed (36) hide show
  1. checksums.yaml +15 -0
  2. data/HISTORY.md +16 -0
  3. data/README.md +295 -0
  4. data/app/controllers/arturo/features_controller.rb +105 -0
  5. data/app/helpers/arturo/features_helper.rb +27 -0
  6. data/app/models/arturo/feature.rb +76 -0
  7. data/app/views/arturo/features/_feature.html.erb +5 -0
  8. data/app/views/arturo/features/_form.html.erb +16 -0
  9. data/app/views/arturo/features/edit.html.erb +2 -0
  10. data/app/views/arturo/features/forbidden.html.erb +2 -0
  11. data/app/views/arturo/features/index.html.erb +29 -0
  12. data/app/views/arturo/features/new.html.erb +2 -0
  13. data/app/views/arturo/features/show.html.erb +2 -0
  14. data/config/locales/en.yml +40 -0
  15. data/config/routes.rb +14 -0
  16. data/lib/arturo/controller_filters.rb +33 -0
  17. data/lib/arturo/engine.rb +10 -0
  18. data/lib/arturo/feature_availability.rb +37 -0
  19. data/lib/arturo/feature_caching.rb +83 -0
  20. data/lib/arturo/feature_factories.rb +4 -0
  21. data/lib/arturo/feature_management.rb +23 -0
  22. data/lib/arturo/middleware.rb +60 -0
  23. data/lib/arturo/special_handling.rb +62 -0
  24. data/lib/arturo/test_support.rb +30 -0
  25. data/lib/arturo.rb +37 -0
  26. data/lib/generators/arturo/assets_generator.rb +18 -0
  27. data/lib/generators/arturo/initializer_generator.rb +13 -0
  28. data/lib/generators/arturo/migration_generator.rb +27 -0
  29. data/lib/generators/arturo/routes_generator.rb +15 -0
  30. data/lib/generators/arturo/templates/arturo.css +67 -0
  31. data/lib/generators/arturo/templates/arturo.js +23 -0
  32. data/lib/generators/arturo/templates/arturo_customizations.css +1 -0
  33. data/lib/generators/arturo/templates/initializer.rb +29 -0
  34. data/lib/generators/arturo/templates/migration.rb +17 -0
  35. data/lib/generators/arturo/templates/semicolon.png +0 -0
  36. metadata +175 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjI3NWU5NDQ3N2EwZGY5Y2RjZjA2M2ZkNzIzMDUzZjllN2FjMDM5MA==
5
+ data.tar.gz: !binary |-
6
+ MjkzYzk0NDQxMDJjYjI4YjJmYjFjYjAwM2JjYzk5ZmUzNDQwZDUyMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZjNjM2QzMDg5MjRkNTcxYzRhMjZiNDUxZDI4NTZjODMzNjczNzU0N2YwN2Y0
10
+ ZDRmYjRjOTk4YTk5NjI1Yjk5NjU0N2ZlYjFiOWY1NDZjMGRiOTM1MDUwMjE2
11
+ Y2JlNTA5YmFiOWExYjM0MjFkM2MyODIxYTY5NGI5MTExYmRlMDg=
12
+ data.tar.gz: !binary |-
13
+ YmEyYjFiZGZiYWM4NDk1MTcxOWIyNTgyZTZjMGFjMDQ1ZTJjNDczYjVlMjM3
14
+ ZmFmOGZhZmRjZWZlY2I4ZmNjZjMxYWVmOWFlZTUyYmMxYWRkNjc1YWEzYjU4
15
+ ZTJlM2Y1Y2EzNjE3MmNlYzc1YmE1M2UyOWE2Y2EyYmQ2YmEyMmQ=
data/HISTORY.md ADDED
@@ -0,0 +1,16 @@
1
+ ## v 1.1.0 - cleanup
2
+ * changed `require_feature!` to `require_feature`
3
+ * replaced `Arturo.permit_management` and `Arturo.feature_recipient`
4
+ blocks with instance methods
5
+ `Arturo::FeatureManagement.may_manage_features?` and
6
+ `Arturo::FeatureAvailability.feature_recipient`
7
+
8
+ ## v 1.0.0 - Initial Release
9
+ * `require_feature!` controller filter
10
+ * `if_feature_enabled` controller and view method
11
+ * `feature_enabled?` controller and view method
12
+ * CRUD for features
13
+ * `Arturo.permit_management` to configure management permission
14
+ * `Arturo.feature_recipient` to configure on what basis features are deployed
15
+ * whitelists and blacklists
16
+
data/README.md ADDED
@@ -0,0 +1,295 @@
1
+ ## What
2
+
3
+ Arturo provides feature sliders for Rails. It lets you turn features on and off
4
+ just like
5
+ [feature flippers](http://code.flickr.com/blog/2009/12/02/flipping-out/),
6
+ but offers more fine-grained control. It supports deploying features only for
7
+ a given percent* of your users and whitelisting and blacklisting users based
8
+ on any criteria you can express in Ruby.
9
+
10
+ * The selection isn't random. It's not even pseudo-random. It's completely
11
+ deterministic. This assures that if a user has a feature on Monday, the
12
+ user will still have it on Tuesday (unless, of course, you *decrease*
13
+ the feature's deployment percentage or change its white- or blacklist
14
+ settings).
15
+
16
+ ### A quick example
17
+
18
+ Trish, a developer is working on a new feature: a live feed of recent postings
19
+ in the user's city that shows up in the user's sidebar. First, she uses Arturo's
20
+ view helpers to control who sees the sidebar widget:
21
+
22
+ <%# in app/views/layout/_sidebar.html.erb: %>
23
+ <% if_feature_enabled(:live_postings) do %>
24
+ <div class='widget'>
25
+ <h3>Recent Postings</h3>
26
+ <ol id='live_postings'>
27
+ </ol>
28
+ </div>
29
+ <% end %>
30
+
31
+ Then Trish writes some Javascript that will poll the server for recent
32
+ postings and put them in the sidebar widget:
33
+
34
+ // in public/javascript/live_postings.js:
35
+ $(function() {
36
+ var livePostingsList = $('#live_postings');
37
+ if (livePostingsList.length > 0) {
38
+ var updatePostingsList = function() {
39
+ livePostingsList.load('/listings/recent');
40
+ setTimeout(updatePostingsList, 30);
41
+ }
42
+ updatePostingsList();
43
+ }
44
+ });
45
+
46
+ Trish uses Arturo's Controller filters to control who has access to
47
+ the feature:
48
+
49
+ # in app/controllers/postings_controller:
50
+ class PostingsController < ApplicationController
51
+ require_feature :live_postings, :only => :recent
52
+ # ...
53
+ end
54
+
55
+ Trish then deploys this code to production. Nobody will see the feature yet,
56
+ since it's not on for anyone. (In fact, the feature doesn't yet exist
57
+ in the database, which is the same as being deployed to 0% of users.) A week
58
+ later, when the company is ready to start deploying the feature to a few
59
+ people, the product manager, Vijay, signs in to their site and navigates
60
+ to `/features`, adds a new feature called "live_postings" and sets its
61
+ deployment percentage to 3%. After a few days, the operations team decides
62
+ that the increase in traffic is not going to overwhelm their servers, and
63
+ Vijay can bump the deployment percentage up to 50%. A few more days go by
64
+ and they clean up the last few bugs they found with the "live_postings"
65
+ feature and deploy it to all users.
66
+
67
+ ## Installation
68
+
69
+ ### In Rails 3, with Bundler
70
+
71
+ gem 'arturo', '~> 1.0'
72
+
73
+ ### In Rails 3, without Bundler
74
+
75
+ $ gem install arturo --version="~> 1.0"
76
+
77
+ ### In Rails 2.3
78
+
79
+ For Rails 2.3 support, see the
80
+ [`rails_2_3` branch](http://github.com/jamesarosen/arturo/tree/rails_2_3)
81
+ of Arturo.
82
+
83
+ ## Configuration
84
+
85
+ ### In Rails
86
+
87
+ #### Run the generators:
88
+
89
+ $ rails g arturo:migration
90
+ $ rails g arturo:initializer
91
+ $ rails g arturo:route
92
+ $ rails g arturo:assets
93
+
94
+ #### Run the migration:
95
+
96
+ $ rake db:migrate
97
+
98
+ #### Edit the generated migration as necessary
99
+
100
+ #### Edit the configuration
101
+
102
+ ##### Initializer
103
+
104
+ Open up the newly-generated `config/initializers/arturo_initializer.rb`.
105
+ There are configuration options for the following:
106
+
107
+ * the method that determines whether a user has permission to manage features
108
+ (see [admin permissions](#adminpermissions))
109
+ * the method that returns the object that has features
110
+ (e.g. User, Person, or Account; see
111
+ [feature recipients](#featurerecipients))
112
+ * whitelists and blacklists for features
113
+ (see [white- and blacklisting](#wblisting))
114
+
115
+ ##### CSS
116
+
117
+ Open up the newly-generated `public/stylehseets/arturo_customizations.css`.
118
+ You can add any overrides you like to the feature configuration page styles
119
+ here. **Do not** edit `public/stylehseets/arturo.css` as that file may be
120
+ overwritten in future updates to Arturo.
121
+
122
+ ### In other frameworks
123
+
124
+ Arturo is a Rails engine. I want to promote reuse on other frameworks by
125
+ extracting key pieces into mixins, though this isn't done yet. Open an
126
+ [issue](http://github.com/jamesarosen/arturo/issues) and I'll be happy to
127
+ work with you on support for your favorite framework.
128
+
129
+ ## Deep-Dive
130
+
131
+ ### <span id='adminpermissions'>Admin Permissions</span>
132
+
133
+ `Arturo::FeatureManagement#may_manage_features?` is a method that is run in
134
+ the context of a Controller or View instance. It should return `true` if
135
+ and only if the current user may manage permissions. The default implementation
136
+ is as follows:
137
+
138
+ current_user.present? && current_user.admin?
139
+
140
+ You can change the implementation in
141
+ `config/initializers/arturo_initializer.rb`. A reasonable implementation
142
+ might be
143
+
144
+ Arturo.permit_management do
145
+ signed_in? && current_user.can?(:manage_features)
146
+ end
147
+
148
+ ### <span id='featurerecipients'>Feature Recipients</span>
149
+
150
+ Clients of Arturo may want to deploy new features on a per-user, per-project,
151
+ per-account, or other basis. For example, it is likely Twitter deployed
152
+ "#newtwitter" on a per-user basis. Conversely, Facebook -- at least in its
153
+ early days -- may have deployed features on a per-university basis. It wouldn't
154
+ make much sense to deploy a feature to one user of a Basecamp project but not
155
+ to others, so 37Signals would probably want a per-project or per-account basis.
156
+
157
+ `Arturo::FeatureAvailability#feature_recipient` is intended to support these
158
+ many use cases. It is a method that returns the current "thing" (a user, account,
159
+ project, university, ...) that is a member of the category that is the basis for
160
+ deploying new features. It should return an `Object` that responds to `#id`.
161
+
162
+ The default implementation simply returns `current_user`. Like
163
+ `Arturo::FeatureManagement#may_manage_features?`, this method can be configured
164
+ in `config/initializers/arturo_initializer.rb`. If you want to deploy features
165
+ on a per-account basis, a reasonable implementation might be
166
+
167
+ Arturo.feature_recipient do
168
+ current_account
169
+ end
170
+
171
+ or
172
+
173
+ Arturo.feature_recipient do
174
+ current_user.account
175
+ end
176
+
177
+ If the block returns `nil`, the feature will be disabled.
178
+
179
+ ### <span id='wblisting'>Whitelists & Blacklists</span>
180
+
181
+ Whitelists and blacklists allow you to control exactly which users or accounts
182
+ will have a feature. For example, if all premium users should have the
183
+ `:awesome` feature, place the following in
184
+ `config/initializers/arturo_initializer.rb`:
185
+
186
+ Arturo::Feature.whitelist(:awesome) do |user|
187
+ user.account.premium?
188
+ end
189
+
190
+ If, on the other hand, no users on the free plan should have the
191
+ `:awesome` feature, place the following in
192
+ `config/initializers/arturo_initializer.rb`:
193
+
194
+ Arturo::Feature.blacklist(:awesome) do |user|
195
+ user.account.free?
196
+ end
197
+
198
+ ### Feature Conditionals
199
+
200
+ All that configuration is just a waste of time if Arturo didn't modify the
201
+ behavior of your application based on feature availability. There are a few
202
+ ways to do so.
203
+
204
+ #### Controller Filters
205
+
206
+ If an action should only be available to those with a feature enabled,
207
+ use a before filter. The following will raise a 403 Forbidden error for
208
+ every action within `BookHoldsController` that is invoked by a user who
209
+ does not have the `:hold_book` feature.
210
+
211
+ class BookHoldsController < ApplicationController
212
+ require_feature :hold_book
213
+ end
214
+
215
+ `require_feature` accepts as a second argument a `Hash` that it passes on
216
+ to `before_filter`, so you can use `:only` and `:except` to specify exactly
217
+ which actions are filtered.
218
+
219
+ If you want to customize the page that is rendered on 403 Forbidden
220
+ responses, put the view in
221
+ `RAILS_ROOT/app/views/arturo/features/forbidden.html.erb`. Rails will
222
+ check there before falling back on Arturo's forbidden page.
223
+
224
+ #### Conditional Evaluation
225
+
226
+ Both controllers and views have access to the `if_feature_enabled` and
227
+ `feature_enabled?` methods. The former is used like so:
228
+
229
+ <% if_feature_enabled?(:reserve_table) %>
230
+ <%= link_to 'Reserve a table', new_restaurant_reservation_path(:restaurant_id => @restaurant) %>
231
+ <% end %>
232
+
233
+ The latter can be used like so:
234
+
235
+ def widgets_for_sidebar
236
+ widgets = []
237
+ widgets << twitter_widget if feature_enabled?(:twitter_integration)
238
+ ...
239
+ widgets
240
+ end
241
+
242
+ #### Rack Middleware
243
+
244
+ require 'arturo'
245
+ use Arturo::Middleware, :feature => :my_feature
246
+
247
+ #### Outside a Controller
248
+
249
+ If you want to check availability outside of a controller or view (really
250
+ outside of something that has `Arturo::FeatureAvailability` mixed in), you
251
+ can ask either
252
+
253
+ Arturo.feature_enabled_for?(:foo, recipient)
254
+
255
+ or the slightly fancier
256
+
257
+ Arturo.foo_enabled_for?(recipient)
258
+
259
+ Both check whether the `foo` feature exists and is enabled for `recipient`.
260
+
261
+ #### Caching
262
+
263
+ **Note**: Arturo does not yet have caching support. Be very careful when
264
+ caching actions or pages that involve feature detection as you will get
265
+ strange behavior when a user who has access to a feature requests a page
266
+ just after one who does not (and vice versa). The following is the
267
+ **intended** support for caching.
268
+
269
+ Both the `require_feature` before filter and the `if_feature_enabled` block
270
+ evaluation automatically append a string based on the feature's
271
+ `last_modified` timestamp to cache keys that Rails generates. Thus, you don't
272
+ have to worry about expiring caches when you increase a feature's deployment
273
+ percentage. See `Arturo::CacheSupport` for more information.
274
+
275
+ ## The Name
276
+
277
+ Arturo gets its name from
278
+ [Professor Maximillian Arturo](http://en.wikipedia.org/wiki/Maximillian_Arturo)
279
+ on [Sliders](http://en.wikipedia.org/wiki/Sliders).
280
+
281
+ ## Contributing ##
282
+
283
+ For bug reports, open an [issue](https://github.com/jamesarosen/Timecop.js/issues)
284
+ on GitHub.
285
+
286
+ Timecop.js has a ‘commit-bit’ policy, much like the Rubinius project
287
+ and Gemcutter. Submit a patch that is accepted, and you can get full
288
+ commit access to the project. All you have to do is open an issue
289
+ asking for access and I'll add you as a collaborator.
290
+ Feel free to fork the project though and have fun in your own sandbox.
291
+
292
+ ## Authors ##
293
+
294
+ * [https://github.com/jamesarosen](James A. Rosen)
295
+ * [https://github.com/plukevdh](Luke van der Hoeven)
@@ -0,0 +1,105 @@
1
+ require 'action_controller'
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?
6
+ module Arturo
7
+
8
+ # Handles all Feature actions. Clients of the Arturo engine
9
+ # should redefine Arturo::FeaturesController#permitted? to
10
+ # return true only for users who are permitted to manage features.
11
+ class FeaturesController < ApplicationController
12
+ include Arturo::FeatureManagement
13
+
14
+ unloadable
15
+ respond_to :html, :json, :xml
16
+ before_filter :require_permission
17
+ before_filter :load_feature, :only => [ :show, :edit, :update, :destroy ]
18
+
19
+ def index
20
+ @features = Arturo::Feature.all
21
+ respond_with @features
22
+ end
23
+
24
+ def update_all
25
+ updated_count = 0
26
+ errors = []
27
+ features_params = params[:features] || {}
28
+ features_params.each do |id, attributes|
29
+ feature = Arturo::Feature.find_by_id(id)
30
+ if feature.blank?
31
+ errors << t('arturo.features.flash.no_such_feature', :id => id)
32
+ elsif feature.update_attributes(attributes)
33
+ updated_count += 1
34
+ else
35
+ errors << t('arturo.features.flash.error_updating', :id => id)
36
+ end
37
+ end
38
+ if errors.any?
39
+ flash[:error] = errors
40
+ else
41
+ flash[:success] = t('arturo.features.flash.updated_many', :count => updated_count)
42
+ end
43
+ redirect_to features_path
44
+ end
45
+
46
+ def show
47
+ respond_with @feature
48
+ end
49
+
50
+ def new
51
+ @feature = Arturo::Feature.new(params[:feature])
52
+ respond_with @feature
53
+ end
54
+
55
+ def create
56
+ @feature = Arturo::Feature.new(params[:feature])
57
+ if @feature.save
58
+ flash[:notice] = t('arturo.features.flash.created', :name => @feature.to_s)
59
+ redirect_to features_path
60
+ else
61
+ flash[:alert] = t('arturo.features.flash.error_creating', :name => @feature.to_s)
62
+ render :action => 'new'
63
+ end
64
+ end
65
+
66
+ def edit
67
+ respond_with @feature
68
+ end
69
+
70
+ def update
71
+ if @feature.update_attributes(params[:feature])
72
+ flash[:notice] = t('arturo.features.flash.updated', :name => @feature.to_s)
73
+ redirect_to feature_path(@feature)
74
+ else
75
+ flash[:alert] = t('arturo.features.flash.error_updating', :name => @feature.to_s)
76
+ render :action => 'edit'
77
+ end
78
+ end
79
+
80
+ def destroy
81
+ if @feature.destroy
82
+ flash[:notice] = t('arturo.features.flash.removed', :name => @feature.to_s)
83
+ else
84
+ flash[:alert] = t('arturo.features.flash.error_removing', :name => @feature.to_s)
85
+ end
86
+ redirect_to features_path
87
+ end
88
+
89
+ protected
90
+
91
+ def require_permission
92
+ unless may_manage_features?
93
+ render :action => 'forbidden', :status => 403
94
+ return false
95
+ end
96
+ end
97
+
98
+ def load_feature
99
+ @feature ||= Arturo::Feature.find(params[:id])
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
@@ -0,0 +1,27 @@
1
+ require 'action_view/helpers/tag_helper'
2
+ require 'action_view/helpers/form_tag_helper'
3
+
4
+ module Arturo
5
+ module FeaturesHelper
6
+ include ActionView::Helpers::TagHelper
7
+
8
+ def deployment_percentage_range_and_output_tags(name, value, options = {})
9
+ id = sanitize_to_id(name)
10
+ options = {
11
+ 'type' => 'range',
12
+ 'name' => name,
13
+ 'id' => id,
14
+ 'value' => value,
15
+ 'min' => '0',
16
+ 'max' => '100',
17
+ 'step' => '1',
18
+ 'class' => 'deployment_percentage'
19
+ }.update(options.stringify_keys)
20
+ tag(:input, options) + deployment_percentage_output_tag(id, value)
21
+ end
22
+
23
+ def deployment_percentage_output_tag(id, value)
24
+ content_tag(:output, value, { 'for' => id, 'class' => 'deployment_percentage no_js' })
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,76 @@
1
+ require 'active_record'
2
+
3
+ # a stub
4
+ # possible TODO: remove and and refactor into an acts_as_feature mixin
5
+ module Arturo
6
+ class Feature < ::ActiveRecord::Base
7
+
8
+ include Arturo::SpecialHandling
9
+
10
+ Arturo::Feature::SYMBOL_REGEX = /^[a-zA-z][a-zA-Z0-9_]*$/
11
+ DEFAULT_ATTRIBUTES = { :deployment_percentage => 0 }.with_indifferent_access
12
+
13
+ attr_accessible :symbol, :deployment_percentage if ActiveRecord::VERSION::MAJOR < 4
14
+ attr_readonly :symbol
15
+
16
+ validates_presence_of :symbol, :deployment_percentage
17
+ validates_uniqueness_of :symbol, :allow_blank => true
18
+ validates_numericality_of :deployment_percentage,
19
+ :only_integer => true,
20
+ :allow_blank => true,
21
+ :greater_than_or_equal_to => 0,
22
+ :less_than_or_equal_to => 100
23
+
24
+ # Looks up a feature by symbol. Also accepts a Feature as input.
25
+ # @param [Symbol, Arturo::Feature] feature_or_name a Feature or the Symbol of a Feature
26
+ # @return [Arturo::Feature, nil] the Feature if found, else nil
27
+ def self.to_feature(feature_or_symbol)
28
+ return feature_or_symbol if feature_or_symbol.kind_of?(self)
29
+ self.where(:symbol => feature_or_symbol.to_sym).first
30
+ end
31
+
32
+ # Create a new Feature
33
+ def initialize(attributes = {})
34
+ super(DEFAULT_ATTRIBUTES.merge(attributes || {}))
35
+ end
36
+
37
+ # @param [Object] feature_recipient a User, Account,
38
+ # or other model with an #id method
39
+ # @return [true,false] whether or not this feature is enabled
40
+ # for feature_recipient
41
+ # @see Arturo::SpecialHandling#whitelisted?
42
+ # @see Arturo::SpecialHandling#blacklisted?
43
+ def enabled_for?(feature_recipient)
44
+ return false if feature_recipient.nil?
45
+ return false if blacklisted?(feature_recipient)
46
+ return true if whitelisted?(feature_recipient)
47
+ passes_threshold?(feature_recipient)
48
+ end
49
+
50
+ def name
51
+ return I18n.translate("arturo.feature.nameless") if symbol.blank?
52
+ I18n.translate("arturo.feature.#{symbol}", :default => symbol.to_s.titleize)
53
+ end
54
+
55
+ def to_s
56
+ "Feature #{name}"
57
+ end
58
+
59
+ def to_param
60
+ persisted? ? "#{id}-#{symbol.to_s.parameterize}" : nil
61
+ end
62
+
63
+ def inspect
64
+ "<Arturo::Feature #{name}, deployed to #{deployment_percentage}%>"
65
+ end
66
+
67
+ protected
68
+
69
+ def passes_threshold?(feature_recipient)
70
+ threshold = self.deployment_percentage || 0
71
+ return false if threshold == 0
72
+ return true if threshold == 100
73
+ (((feature_recipient.id + (self.id || 1) + 17) * 13) % 100) < threshold
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ <tr class='feature' id="feature_<%= feature.id %>">
2
+ <td><code class='ruby symbol'><%= feature.symbol %></code></td>
3
+ <td><%= deployment_percentage_range_and_output_tags("features[#{feature.id}][deployment_percentage]", feature.deployment_percentage) %></td>
4
+ <td><%= link_to t('.edit'), edit_feature_path(feature), :rel => 'edit', :class => 'edit' %></td>
5
+ </tr>
@@ -0,0 +1,16 @@
1
+ <%= form_for(feature, :as => 'feature', :url => (feature.new_record? ? features_path : feature_path(feature))) do |form| %>
2
+ <fieldset>
3
+ <legend><%= legend %></legend>
4
+
5
+ <%= form.label(:symbol) %>
6
+ <%= form.text_field(:symbol, :required => 'required', :pattern => Arturo::Feature::SYMBOL_REGEX.source, :class => 'symbol') %>
7
+ <%= error_messages_for(feature, :symbol) %>
8
+
9
+ <%= form.label(:deployment_percentage) %>
10
+ <%= form.range_field(:deployment_percentage, :min => '0', :max => '100', :step => '1', :class => 'deployment_percentage') %>
11
+ <%= deployment_percentage_output_tag 'feature_deployment_percentage', feature.deployment_percentage %>
12
+ <%= error_messages_for(feature, :deployment_percentage) %>
13
+
14
+ <footer><%= form.submit %></footer>
15
+ </fieldset>
16
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <h2><%= t('.title', :name => @feature.name) %></h2>
2
+ <%= render :partial => 'form', :locals => { :feature => @feature, :legend => t('.legend', :name => @feature.name) } %>
@@ -0,0 +1,2 @@
1
+ <h2><%= t('.title') %></h2>
2
+ <p><%= t('.text') %></p>
@@ -0,0 +1,29 @@
1
+ <h2><%= t('.title') %></h2>
2
+ <%= form_tag(features_path, :method => 'put', 'data-update-path' => feature_path(:id => ':id'), :remote => true) do %>
3
+ <fieldset>
4
+ <legend><%= t('.title') %></legend>
5
+ <table class='features'>
6
+ <col class='name' />
7
+ <col class='deployment_percentage' />
8
+ <col class='edit' />
9
+ <thead>
10
+ <tr>
11
+ <th><%= t('activerecord.attributes.arturo/feature.name') %></th>
12
+ <th><%= t('activerecord.attributes.arturo/feature.deployment_percentage') %></th>
13
+ <th>&nbsp;</th>
14
+ </tr>
15
+ </thead>
16
+ <tfoot>
17
+ <tr><th colspan='4'><%= link_to t('.new'), new_feature_path %> <%= submit_tag %></th></tr>
18
+ </tfoot>
19
+ <tbody>
20
+ <% @features.each do |f| %>
21
+ <%= render :partial => 'feature', :locals => { :feature => f } %>
22
+ <% end %>
23
+ <% if @features.none? %>
24
+ <tr class='if_no_features'><td colspan='4'><%= t('.none_yet') %></td></tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
28
+ </fieldset>
29
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <h2><%= t('.title') %></h2>
2
+ <%= render :partial => 'form', :locals => { :feature => @feature, :legend => t('.legend') } %>
@@ -0,0 +1,2 @@
1
+ <h2><%= t('.title', :name => @feature.name) %></h2>
2
+ <p>Deployment percentage: <%= @feature.deployment_percentage %></p>
@@ -0,0 +1,40 @@
1
+ en:
2
+ activerecord:
3
+ models:
4
+ "arturo/feature": "Feature"
5
+ attributes:
6
+ "arturo/feature":
7
+ symbol: "Symbol"
8
+ name: "Name"
9
+ deployment_percentage: "Deployment Percentage"
10
+ arturo:
11
+ feature:
12
+ nameless: "(no name)"
13
+ features:
14
+ index:
15
+ title: 'Features'
16
+ new: 'New'
17
+ none_yet: No features yet.
18
+ new:
19
+ title: New Feature
20
+ legend: "New Feature"
21
+ edit:
22
+ title: "Edit Feature %{name}"
23
+ legend: "Edit Feature %{name}"
24
+ feature:
25
+ edit: 'Edit'
26
+ show:
27
+ title: "Feature %{name}"
28
+ forbidden:
29
+ title: Forbidden
30
+ text: You do not have permission to access that resource.
31
+ flash:
32
+ no_such_feature: "No such feature: %{id}"
33
+ error_updating: "Error updating feature %{id}"
34
+ updated_many: "Updated %{count} feature(s)"
35
+ created: "Created %{name}"
36
+ error_creating: "Sorry, there was an error creating the feature."
37
+ updated: "Updated %{name}"
38
+ error_updating: "Sorry, there was an error updating %{name}"
39
+ removed: "Removed %{name}"
40
+ error_removing: "Sorry, there was an error removing %{name}"
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