GithubHelp home page GithubHelp logo

zendesk / arturo Goto Github PK

View Code? Open in Web Editor NEW
196.0 277.0 24.0 571 KB

Feature Sliders for Rails

Home Page: http://github.com/zendesk/arturo

License: Other

Ruby 87.77% JavaScript 1.77% CSS 5.42% HTML 5.04%

arturo's Introduction

What

Arturo provides feature sliders for Rails. It lets you turn features on and off just like feature flippers, but offers more fine-grained control. It supports deploying features only for a given percentage of your users and whitelisting and blacklisting users based on any criteria you can express in Ruby.

The selection is deterministic. So if a user has a feature on Monday, the user will still have it on Tuesday (unless you decrease the feature's deployment percentage or change its white- or blacklist settings).

A quick example

Trish, a developer is working on a new feature: a live feed of recent postings in the user's city that shows up in the user's sidebar. First, she uses Arturo's view helpers to control who sees the sidebar widget:

<%# in app/views/layout/_sidebar.html.erb: %>
  <% if_feature_enabled(:live_postings) do %>
  <div class='widget'>
    <h3>Recent Postings</h3>
    <ol id='live_postings'>
    </ol>
  </div>
<% end %>

Then Trish writes some Javascript that will poll the server for recent postings and put them in the sidebar widget:

// in public/javascript/live_postings.js:
$(function() {
  var livePostingsList = $('#live_postings');
  if (livePostingsList.length > 0) {
    var updatePostingsList = function() {
      livePostingsList.load('/listings/recent');
      setTimeout(updatePostingsList, 30);
    }
    updatePostingsList();
  }
});

Trish uses Arturo's Controller filters to control who has access to the feature:

# in app/controllers/postings_controller:
class PostingsController < ApplicationController
  require_feature :live_postings, only: :recent
  # ...
end

Trish then deploys this code to production. Nobody will see the feature yet, since it's not on for anyone. (In fact, the feature doesn't yet exist in the database, which is the same as being deployed to 0% of users.) A week later, when the company is ready to start deploying the feature to a few people, the product manager, Vijay, signs in to their site and navigates to /features, adds a new feature called "live_postings" and sets its deployment percentage to 3%. After a few days, the operations team decides that the increase in traffic is not going to overwhelm their servers, and Vijay can bump the deployment percentage up to 50%. A few more days go by and they clean up the last few bugs they found with the "live_postings" feature and deploy it to all users.

Installation

gem 'arturo'

Configuration

In Rails

Run the generators:

rails g arturo:migration
rails g arturo:initializer
rails g arturo:routes
rails g arturo:assets
rails g arturo:feature_model

Run the migration:

rake db:migrate

Edit the generated migration as necessary

Edit the configuration

Edit the Feature model

By default, the generated model Arturo::Feature inherits from ActiveRecord::Base. However, if you’re using multiple databases your models should inherit from an abstract class that specifies a database connection, not directly from ActiveRecord::Base. Update the generated model in app/models/arturo/feature.rb to make it use a correct database.

Initializer

Open up the newly-generated config/initializers/arturo_initializer.rb. There are configuration options for the following:

CSS

Open up the newly-generated public/stylesheets/arturo_customizations.css. You can add any overrides you like to the feature configuration page styles here. Do not edit public/stylesheets/arturo.css as that file may be overwritten in future updates to Arturo.

In other frameworks

Arturo is a Rails engine. I want to promote reuse on other frameworks by extracting key pieces into mixins, though this isn't done yet. Open an issue and I'll be happy to work with you on support for your favorite framework.

Deep-Dive

Logging

You can provide a logger in order to inspect Arturo usage. A potential implementation for Rails would be:

Arturo.logger = Rails.logger

Admin Permissions

Arturo::FeatureManagement#may_manage_features? is a method that is run in the context of a Controller or View instance. It should return true if and only if the current user may manage permissions. The default implementation is as follows:

current_user.present? && current_user.admin?

You can change the implementation in config/initializers/arturo_initializer.rb. A reasonable implementation might be

Arturo.permit_management do
  signed_in? && current_user.can?(:manage_features)
end

Feature Recipients

Clients of Arturo may want to deploy new features on a per-user, per-project, per-account, or other basis. For example, it is likely Twitter deployed "#newtwitter" on a per-user basis. Conversely, Facebook -- at least in its early days -- may have deployed features on a per-university basis. It wouldn't make much sense to deploy a feature to one user of a Basecamp project but not to others, so 37Signals would probably want a per-project or per-account basis.

Arturo::FeatureAvailability#feature_recipient is intended to support these many use cases. It is a method that returns the current "thing" (a user, account, project, university, ...) that is a member of the category that is the basis for deploying new features. It should return an Object that responds to #id.

The default implementation simply returns current_user. Like Arturo::FeatureManagement#may_manage_features?, this method can be configured in config/initializers/arturo_initializer.rb. If you want to deploy features on a per-account basis, a reasonable implementation might be

Arturo.feature_recipient do
  current_account
end

or

Arturo.feature_recipient do
  current_user.account
end

If the block returns nil, the feature will be disabled.

Whitelists & Blacklists

Whitelists and blacklists allow you to control exactly which users or accounts will have a feature. For example, if all premium users should have the :awesome feature, place the following in config/initializers/arturo_initializer.rb:

Arturo::Feature.whitelist(:awesome) do |user|
  user.account.premium?
end

If, on the other hand, no users on the free plan should have the :awesome feature, place the following in config/initializers/arturo_initializer.rb:

Arturo::Feature.blacklist(:awesome) do |user|
  user.account.free?
end

If you want to whitelist or blacklist large groups of features at once, you can move the feature argument into the block:

Arturo::Feature.whitelist do |feature, user|
  user.account.has?(feature.to_sym)
end

Feature Conditionals

All that configuration is just a waste of time if Arturo didn't modify the behavior of your application based on feature availability. There are a few ways to do so.

Controller Filters

If an action should only be available to those with a feature enabled, use a before filter. The following will raise a 403 Forbidden error for every action within BookHoldsController that is invoked by a user who does not have the :hold_book feature.

class BookHoldsController < ApplicationController
  require_feature :hold_book
end

require_feature accepts as a second argument a Hash that it passes on to before_action, so you can use :only and :except to specify exactly which actions are filtered.

If you want to customize the page that is rendered on 403 Forbidden responses, put the view in RAILS_ROOT/app/views/arturo/features/forbidden.html.erb. Rails will check there before falling back on Arturo's forbidden page.

Conditional Evaluation

Both controllers and views have access to the if_feature_enabled? and feature_enabled? methods. The former is used like so:

<% if_feature_enabled?(:reserve_table) %>
  <%= link_to 'Reserve a table', new_restaurant_reservation_path(:restaurant_id => @restaurant) %>
<% end %>

The latter can be used like so:

def widgets_for_sidebar
  widgets = []
  widgets << twitter_widget if feature_enabled?(:twitter_integration)
  ...
  widgets
end

Rack Middleware

require 'arturo'
use Arturo::Middleware, feature: :my_feature

Outside a Controller

If you want to check availability outside of a controller or view (really outside of something that has Arturo::FeatureAvailability mixed in), you can ask either

Arturo.feature_enabled_for?(:foo, recipient)

or the slightly fancier

Arturo.foo_enabled_for?(recipient)

Both check whether the foo feature exists and is enabled for recipient.

Caching

Note: Arturo has support for caching Feature lookups, but doesn't yet integrate with Rails's caching. This means you should be very careful when caching actions or pages that involve feature detection as you will get strange behavior when a user who has access to a feature requests a page just after one who does not (and vice versa).

To enable caching Feature lookups, mix Arturo::FeatureCaching into Arturo::Feature and set the cache_ttl. This is best done in an initializer:

Arturo::Feature.extend(Arturo::FeatureCaching)
Arturo::Feature.cache_ttl = 10.minutes

You can also warm the cache on startup:

Arturo::Feature.warm_cache!

This will pre-fetch all Features and put them in the cache.

To use the current cache state when you can't fetch updates from origin:

Arturo::Feature.extend_cache_on_failure = true

The following is the intended support for integration with view caching:

Both the require_feature before filter and the if_feature_enabled block evaluation automatically append a string based on the feature's last_modified timestamp to cache keys that Rails generates. Thus, you don't have to worry about expiring caches when you increase a feature's deployment percentage. See Arturo::CacheSupport for more information.

The Name

Arturo gets its name from Professor Maximillian Arturo on Sliders.

Quality Metrics

Build Status

Code Quality

arturo's People

Contributors

angelim avatar apanzerj avatar bquorning avatar dasch avatar datadaode avatar ggrossman avatar grosser avatar heynonster avatar icorson3 avatar jamesarosen avatar lonelyghost avatar mervync avatar plukevdh avatar pranthiks avatar pschambacher avatar razumau avatar sircpl avatar staugaard avatar trotter avatar zdennis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

arturo's Issues

The Rails 3.0 build is failing intermittently.

bundle exec rake
/home/travis/.rvm/rubies/ruby-1.9.3-p327/bin/ruby -I"lib:app/controllers:app/mailers:app/models:lib" -I"/home/travis/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.0.4/lib" "/home/travis/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.0.4/lib/rake/rake_test_loader.rb" "test/**/*_test.rb" 
/home/travis/.rvm/gems/ruby-1.9.3-p327/gems/actionpack-3.0.20/lib/action_view/helpers/date_helper.rb:584:in `<class:DateTimeSelector>': uninitialized constant ActionView::Helpers::TagHelper (NameError)

cf https://travis-ci.org/jamesarosen/arturo/jobs/5747404 and https://travis-ci.org/jamesarosen/arturo/builds/6023468

@grosser can you look at this?

"thing_that_has_features" is confusing terminology

Clients of Arturo may want to deploy new features on a per-user, per-project, per-account, or other basis. For example, it is likely Twitter deployed "#newtwitter" on a per-user basis. Conversely, Facebook -- at least in its early days -- may have deployed features on a per-university basis. It wouldn't make much sense to deploy a feature to one user of a Basecamp project but not to others, so 37Signals would probably want a per-project or per-account basis.

Arturo.thing_that_has_features is intended to support these many use cases. It is a block that returns the current "thing" (a user, account, project, university, ...) that is a member of the category that is the basis for deploying new features. The question is: what's a better way to phrase this?

Add caching of features

Default to using Rails.cache, but allow redefining of Arturo::Feature.fetch(id, &block) that behaves like Rails.cache.fetch.

Tests are failing

The todo branch was bad -> either revert the merge or needs a fix

Roadmap for Arturo 3.0

After some reflection, I'd like to make Arturo more Rack-centric and less Rails-centric, but still make it easy to integrate with Rails. To that end, I'm recommending the following basic interface for v2.0:

use Arturo::Middleware do
  feature_recipient do |env|
    env['warden'].current_user
  end

  # require feature :foo for GET /foo, POST /foo, etc.
  require_feature :foo, '/foo'

  # require feature :bar for PUT /bar
  require_feature :bar, :put, '/bar'

  # require feature :baz for requests that have the X-Baz header set to 1
  require_feature :baz do |env|
    env['HTTP_X_BAZ'] == 1
  end
end

The middleware also sets env['arturo'], which has #feature_enabled?(Symbol) -> Boolean and #enabled_features -> Lazy Set. Use it like so:

class MyMiddlware
  def initialize(app)
    @app = app
  end
  def call(env)
    if ( env['arturo'].feature_enabled?(:foo) )
      @app.call(env)
    else
      some_error
    end
  end
end

Of course, we can still build all the Rails integrations on top of this. The only potential problem I see is that it might be hard for some people to turn feature_recipient into a method that runs against a Rack env Hash.

@plukevdh do you have any thoughts on this?

creating a new feature does not save deployment percentage

  1. go to /features/new
  2. type a symbol
  3. select a deployment percentage
  4. click "save"

The form POSTs to {"feature"=>{"deployment_percentage"=>"56", "symbol"=>"forums2"}, "commit"=>"Submit", "action"=>"create", "controller"=>"arturo/features"} though the feature is saved without it. It doesn't seem to be declared as a protected attribute. Perhaps it's a String-Integer conversion problem? Oddly, the update-all seems to work fine from the index screen.

Stop Feature and FeaturesController from reloading in development

It's currently impossible to modify Feature or FeaturesController in an initializer because the changes will be lost after a page reload. Figure out how to get them into load_once_paths. (Unfortunately, load_once_paths is frozen by the time arturo/engine.rb is loaded.)

Can I deploy some features on a per-user basis and others on a per-account basis?

This question from martinstreicher:

Can the feature_recipient be dynamic? I have an account and a network that might have different features -- which would be the recipient?

That's an interesting point use case that I hadn't considered. The problem is that #feature_recipient can't know which to return since it doesn't know which feature you're asking about. One option would be to change #if_feature_enabled(feature_name, &block) and #feature_enabled?(feature_name) to allow a second, optional argument that takes the place of the result of #feature_recipient. Specifically:

def if_feature_enabled(feature_name, recipient = nil, &block)
  recipient ||= self.feature_recipient
  ...
end

def feature_enabled?(feature_name, recipient = nil)
  recipient ||= self.feature_recipient
  ...
end

config/routes.rb raises Invalid Route name 'features' for Rails 4.0.0+

add_route': Invalid route name, already in use: 'features' (ArgumentError)

Steps to reproduce:

  1. git clone / fork
  2. rake test

My Platform:

  1. Linux Debian 7
  2. ruby 1.9.3-p448

My Quick solution:

  1. In config/routes.rb I renamed put "features" ... as: "features" to this:
Arturo::Engine.routes.draw do
    resources :features, :controller => 'arturo/features'
    put 'features', :to => 'arturo/features#update_all', :as => 'features_all'
end

Now all Tests run green. Any feedback or a better solution would be greatly appreciated, also if you need more details please let me know.

Add Arturo.foo_enabled_for?(feature_recipient) helper

Something like

module Arturo
  ENABLED_FOR_METHOD_NAME = /^(\w+)_enabled_for\?$/
  def method_missing(symbol, *args, &block)
    if (args.length == 1 && match = ENABLED_FOR_METHOD_NAME.match(symbol.to_s))
      f = Arturo::Feature.to_feature(match[1])
      f && f.enabled_for(args.first)
    else
      super(symbol, *args, &block)
    end
  end
  def respond_to?(symbol)
    symbol.to_s =~ ENABLED_FOR_METHOD_NAME || super(symbol)
  end
end

Move enable_feature! and disable_feature! into Arturo::TestSupport

People have found it confusing to have these methods declared on Arturo directly. They feel it makes them seem like methods that can be used in application code rather than methods that are only available in tests. Deprecate the existing methods and remove them in the next major release.

Rails 4 compatibility issue ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Arturo::Feature: deployment_percentage

Creating a new feature doesn't work.

feature = Arturo::Feature.create(:symbol=>:new_one, :deployment_percentage=>0)
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Arturo::Feature: deployment_percentage, symbol

In feature.rb
attr_accessible :symbol, :deployment_percentage if ActiveRecord::VERSION::MAJOR < 4

We don't have attr_accessible in rails 4 :(

feature.symbol not a symbol.

feature.symbol not a symbol.

So when I do X.whitelist(:symbol)
I should be passing a string despite what the docs say.

Strange gotcha.

def two_arg_block(symbol, block)
  return block if symbol.nil?
  lambda do |feature, recipient|
    feature.symbol == symbol && block.call(recipient)

NoMethodError: undefined method `enabled_for?' for :NO_SUCH_FEATURE:Symbol

@jamesarosen I think having missing features just be off is the safest solution, otherwise you have to always create them in the database before deploying your code and that means going to all the weird places like master/staging/qa/foo-testing-server/development/... and creating it or risking a straight up failure. And even if you do it, making a typo means "blowing up the whole app" vs "new feature is not active"

-> Will you merge a pull that removes this ?

getting `uninitialized constant Arturo::FeatureParamsSupport` while editing views

Hi,
I have been updating some views by putting new html.erb views in views/arturo/*/ of my application and making some custom changes.
While I added a button with remote: true attribute on it, I started facing following issue :-

uninitialized constant Arturo::FeatureParamsSupport

stack trace :-

Started GET "/features/1-my_feature/edit" for 127.0.0.1 at 2016-03-01 17:11:31 +0530
** [Raven] User excluded error: #<ActionController::RoutingError: uninitialized constant Arturo::FeatureParamsSupport>

ActionController::RoutingError (uninitialized constant Arturo::FeatureParamsSupport):
  arturo (2.2.0) app/controllers/arturo/features_controller.rb:14:in `<class:FeaturesController>'
  arturo (2.2.0) app/controllers/arturo/features_controller.rb:12:in `<module:Arturo>'
  arturo (2.2.0) app/controllers/arturo/features_controller.rb:7:in `<top (required)>'
  activesupport (4.2.5.1) lib/active_support/inflector/methods.rb:263:in `const_get'
  activesupport (4.2.5.1) lib/active_support/inflector/methods.rb:263:in `block in constantize'
  activesupport (4.2.5.1) lib/active_support/inflector/methods.rb:259:in `each'
  activesupport (4.2.5.1) lib/active_support/inflector/methods.rb:259:in `inject'
  activesupport (4.2.5.1) lib/active_support/inflector/methods.rb:259:in `constantize'
  actionpack (4.2.5.1) lib/action_dispatch/routing/route_set.rb:70:in `controller_reference'
  actionpack (4.2.5.1) lib/action_dispatch/routing/route_set.rb:60:in `controller'
  actionpack (4.2.5.1) lib/action_dispatch/routing/route_set.rb:39:in `serve'
  actionpack (4.2.5.1) lib/action_dispatch/journey/router.rb:43:in `block in serve'
  actionpack (4.2.5.1) lib/action_dispatch/journey/router.rb:30:in `each'
  actionpack (4.2.5.1) lib/action_dispatch/journey/router.rb:30:in `serve'
  actionpack (4.2.5.1) lib/action_dispatch/routing/route_set.rb:815:in `call'
  railties (4.2.5.1) lib/rails/engine.rb:518:in `call'
  railties (4.2.5.1) lib/rails/railtie.rb:194:in `public_send'
  railties (4.2.5.1) lib/rails/railtie.rb:194:in `method_missing'
  actionpack (4.2.5.1) lib/action_dispatch/routing/mapper.rb:51:in `serve'
  actionpack (4.2.5.1) lib/action_dispatch/journey/router.rb:43:in `block in serve'
  actionpack (4.2.5.1) lib/action_dispatch/journey/router.rb:30:in `each'
  actionpack (4.2.5.1) lib/action_dispatch/journey/router.rb:30:in `serve'
  actionpack (4.2.5.1) lib/action_dispatch/routing/route_set.rb:815:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  omniauth (1.2.2) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.2.2) lib/omniauth/strategy.rb:164:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  omniauth (1.2.2) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.2.2) lib/omniauth/strategy.rb:164:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  omniauth (1.2.2) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.2.2) lib/omniauth/strategy.rb:164:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  omniauth (1.2.2) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.2.2) lib/omniauth/strategy.rb:164:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  omniauth (1.2.2) lib/omniauth/strategy.rb:186:in `call!'
  omniauth (1.2.2) lib/omniauth/strategy.rb:164:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  jquery-fileupload-rails (0.4.6) lib/jquery/fileupload/rails/middleware.rb:14:in `_call'
  jquery-fileupload-rails (0.4.6) lib/jquery/fileupload/rails/middleware.rb:10:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/rack/agent_hooks.rb:30:in `traced_call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/rack/browser_monitoring.rb:32:in `traced_call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  warden (1.2.3) lib/warden/manager.rb:35:in `block in call'
  warden (1.2.3) lib/warden/manager.rb:34:in `catch'
  warden (1.2.3) lib/warden/manager.rb:34:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/etag.rb:24:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/conditionalget.rb:25:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/head.rb:13:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/flash.rb:260:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.6.4) lib/rack/session/abstract/id.rb:220:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  activerecord (4.2.5.1) lib/active_record/query_cache.rb:36:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  activerecord (4.2.5.1) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  activerecord (4.2.5.1) lib/active_record/migration.rb:377:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.2.5.1) lib/active_support/callbacks.rb:88:in `__run_callbacks__'
  activesupport (4.2.5.1) lib/active_support/callbacks.rb:778:in `_run_call_callbacks'
  activesupport (4.2.5.1) lib/active_support/callbacks.rb:81:in `run_callbacks'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  railties (4.2.5.1) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.2.5.1) lib/rails/rack/logger.rb:20:in `block in call'
  activesupport (4.2.5.1) lib/active_support/tagged_logging.rb:68:in `block in tagged'
  activesupport (4.2.5.1) lib/active_support/tagged_logging.rb:26:in `tagged'
  activesupport (4.2.5.1) lib/active_support/tagged_logging.rb:68:in `tagged'
  railties (4.2.5.1) lib/rails/rack/logger.rb:20:in `call'
  quiet_assets (1.1.0) lib/quiet_assets.rb:27:in `call_with_quiet_assets'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  request_store (1.2.0) lib/request_store/middleware.rb:8:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/methodoverride.rb:22:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/runtime.rb:18:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  activesupport (4.2.5.1) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/lock.rb:17:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  actionpack (4.2.5.1) lib/action_dispatch/middleware/static.rb:116:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/sendfile.rb:113:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  sentry-raven (0.15.2) lib/raven/integrations/rack.rb:54:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  railties (4.2.5.1) lib/rails/engine.rb:518:in `call'
  railties (4.2.5.1) lib/rails/application.rb:165:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/deflater.rb:35:in `call'
  newrelic_rpm (3.13.2.300) lib/new_relic/agent/instrumentation/middleware_tracing.rb:67:in `call'
  rack (1.6.4) lib/rack/content_length.rb:15:in `call'
  puma (2.14.0) lib/puma/server.rb:541:in `handle_request'
  puma (2.14.0) lib/puma/server.rb:388:in `process_client'
  puma (2.14.0) lib/puma/server.rb:270:in `block in run'
  puma (2.14.0) lib/puma/thread_pool.rb:106:in `call'
  puma (2.14.0) lib/puma/thread_pool.rb:106:in `block in spawn_thread'


  Rendered /home/mypc/.rvm/gems/ruby-2.2.2@mygemset/gems/actionpack-4.2.5.1/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (3.1ms)
  Rendered /home/mypc/.rvm/gems/ruby-2.2.2@mygemset/gems/actionpack-4.2.5.1/lib/action_dispatch/middleware/templates/routes/_route.html.erb (53.7ms)
  Rendered /home/mypc/.rvm/gems/ruby-2.2.2@mygemset/gems/actionpack-4.2.5.1/lib/action_dispatch/middleware/templates/routes/_route.html.erb (1.0ms)
  Rendered /home/mypc/.rvm/gems/ruby-2.2.2@mygemset/gems/actionpack-4.2.5.1/lib/action_dispatch/middleware/templates/routes/_table.html.erb (1.9ms)
  Rendered /home/mypc/.rvm/gems/ruby-2.2.2@mygemset/gems/actionpack-4.2.5.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.7ms)
  Rendered /home/mypc/.rvm/gems/ruby-2.2.2@mygemset/gems/actionpack-4.2.5.1/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb within rescues/layout (328.1ms)

Add Arturo::Feature::Phased mixin

Support for phases:

  • off (equivalent to 0% deployment)
  • beta (internal beta plus a Set of recipients defined in the database and modifiable at runtime)
  • rollout (use the deployment percentage, plus everything in beta)
  • on (equivalent to 100% deployment)

Should provide dynamic association option with Arturo::Features

I can blacklist/whitelist Feature Recipient(user/account/anything) based on any third-party factor as mentioned here .
Rather than that, we should have option to associate with Feature itself with the Feature Recipient.
I can do a PR for the polymorphic association with Arturo::Feature model, it might help users like those who need to do custom subscription of plans and enable based on custom selection.

Thoughts ?

Rails dependency not really a dependency

Hey James,

We are using arturo in a couple of places that don't have a dependency on rails.

I would argue that arturo's rails dependency is a soft dependency and that it should be removed from the gempec.

cc @eac

Rename cache warmer.

I don't find the current name particularly confusing but it's clear that others do.

Some suggestions: "cache preloading", "cache autowarming", "cache eager loading".

Not compatible with Rails 4

I just updated an app that uses Arturo to Rails 4.0.0.beta1. When I try to run bundler, I get the following:

Bundler could not find compatible versions for gem "rails":
  In Gemfile:
    arturo (>= 1.3.0) ruby depends on
      rails (~> 3.0) ruby

    rails (4.0.0.beta1)

Probably just need to update your gemspec from ~> to >=, pending everything working properly.

Lookup of missing features is not cached

development >> Arturo.feature_enabled_for?(:user_geo_location, nil)
  Arturo::Feature Load (0.5ms)   SELECT * FROM `arturo_features` WHERE (`arturo_features`.`symbol` = 'user_geo_location') LIMIT 1
development => nil
development >> Arturo.feature_enabled_for?(:user_geo_location, nil)
  Arturo::Feature Load (0.7ms)   SELECT * FROM `arturo_features` WHERE (`arturo_features`.`symbol` = 'user_geo_location') LIMIT 1
development => nil

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.