diff --git a/CHANGELOG.md b/CHANGELOG.md index d9ffc910..def4ab8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ `create` so that an exception gets raised if it is appropriate to do so. - [#199](https://github.com/airblade/paper_trail/pull/199) - Rails 4 compatibility. - [#165](https://github.com/airblade/paper_trail/pull/165) - Namespaced the version class under the `PaperTrail` module. + - [#119](https://github.com/airblade/paper_trail/issues/119) - Support for [Sinatra](http://www.sinatrarb.com/); decoupled gem from `Rails`. ## 2.7.2 diff --git a/README.md b/README.md index 55c7fc6c..91ccd11a 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,22 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/ 4. Add `has_paper_trail` to the models you want to track. +### Sinatra + +PaperTrail provides a helper extension that acts similar to the controller mixin it provides for `Rails` applications. + +It will set `PaperTrail.whodunnit` to whatever is returned by a method named `user_for_paper_trail` which you can define inside your Sinatra Application. (by default it attempts to invoke a method named `current_user`) + +If you're using the modular [Sinatra::Base](http://www.sinatrarb.com/intro.html#Modular%20vs.%20Classic%20Style) style of application, you will need to register the extension: + +```ruby +# bleh_app.rb +require 'sinatra/base' + +class BlehApp < Sinatra::Base + register PaperTrail::Sinatra +end +``` ## API Summary diff --git a/gemfiles/3.0.gemfile b/gemfiles/3.0.gemfile index f66bf913..17d23354 100644 --- a/gemfiles/3.0.gemfile +++ b/gemfiles/3.0.gemfile @@ -1,6 +1,5 @@ source 'https://rubygems.org' -gem 'railties', '~> 3.0' gem 'activerecord', '~> 3.0' group :development, :test do @@ -8,6 +7,13 @@ group :development, :test do gem 'shoulda', '~> 3.5' gem 'ffaker', '>= 1.15' + # Testing of Rails + gem 'railties', '~> 3.0' + + # Testing of Sinatra + gem 'sinatra', '~> 1.0' + gem 'rack-test', '>= 0.6' + # Use sqlite3 gem for regular Ruby gem 'sqlite3', '~> 1.2', :platform => :ruby diff --git a/lib/paper_trail.rb b/lib/paper_trail.rb index 3d5f9fe9..f58da83f 100644 --- a/lib/paper_trail.rb +++ b/lib/paper_trail.rb @@ -1,10 +1,10 @@ require 'paper_trail/config' -require 'paper_trail/controller' require 'paper_trail/has_paper_trail' require 'paper_trail/cleaner' -require 'paper_trail/serializers/yaml' -require 'paper_trail/serializers/json' +# Require all frameworks and serializers +Dir[File.join(File.dirname(__FILE__), 'paper_trail', 'frameworks', '*.rb')].each { |file| require file } +Dir[File.join(File.dirname(__FILE__), 'paper_trail', 'serializers', '*.rb')].each { |file| require file } # PaperTrail's module methods can be called in both models and controllers. module PaperTrail @@ -88,9 +88,7 @@ module PaperTrail # Thread-safe hash to hold PaperTrail's data. # Initializing with needed default values. def self.paper_trail_store - Thread.current[:paper_trail] ||= { - :request_enabled_for_controller => true - } + Thread.current[:paper_trail] ||= { :request_enabled_for_controller => true } end # Returns PaperTrail's configuration object. @@ -110,6 +108,8 @@ ActiveSupport.on_load(:active_record) do include PaperTrail::Model end -ActiveSupport.on_load(:action_controller) do - include PaperTrail::Controller +if defined?(ActionController) + ActiveSupport.on_load(:action_controller) do + include PaperTrail::Rails::Controller + end end diff --git a/lib/paper_trail/controller.rb b/lib/paper_trail/controller.rb deleted file mode 100644 index 6bbbe70a..00000000 --- a/lib/paper_trail/controller.rb +++ /dev/null @@ -1,75 +0,0 @@ -module PaperTrail - module Controller - - def self.included(base) - base.before_filter :set_paper_trail_enabled_for_controller - base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info - end - - protected - - # Returns the user who is responsible for any changes that occur. - # By default this calls `current_user` and returns the result. - # - # Override this method in your controller to call a different - # method, e.g. `current_person`, or anything you like. - def user_for_paper_trail - current_user if defined?(current_user) - end - - # Returns any information about the controller or request that you - # want PaperTrail to store alongside any changes that occur. By - # default this returns an empty hash. - # - # Override this method in your controller to return a hash of any - # information you need. The hash's keys must correspond to columns - # in your `versions` table, so don't forget to add any new columns - # you need. - # - # For example: - # - # {:ip => request.remote_ip, :user_agent => request.user_agent} - # - # The columns `ip` and `user_agent` must exist in your `versions` # table. - # - # Use the `:meta` option to `PaperTrail::Model::ClassMethods.has_paper_trail` - # to store any extra model-level data you need. - def info_for_paper_trail - {} - end - - # Returns `true` (default) or `false` depending on whether PaperTrail should - # be active for the current request. - # - # Override this method in your controller to specify when PaperTrail should - # be off. - def paper_trail_enabled_for_controller - true - end - - private - - # Tells PaperTrail whether versions should be saved in the current request. - def set_paper_trail_enabled_for_controller - ::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller - end - - # Tells PaperTrail who is responsible for any changes that occur. - def set_paper_trail_whodunnit - ::PaperTrail.whodunnit = user_for_paper_trail if paper_trail_enabled_for_controller - end - - # DEPRECATED: please use `set_paper_trail_whodunnit` instead. - def set_whodunnit - logger.warn '[PaperTrail]: the `set_whodunnit` controller method has been deprecated. Please rename to `set_paper_trail_whodunnit`.' - set_paper_trail_whodunnit - end - - # Tells PaperTrail any information from the controller you want - # to store alongside any changes that occur. - def set_paper_trail_controller_info - ::PaperTrail.controller_info = info_for_paper_trail if paper_trail_enabled_for_controller - end - - end -end diff --git a/lib/paper_trail/frameworks/rails.rb b/lib/paper_trail/frameworks/rails.rb new file mode 100644 index 00000000..cbef5d9f --- /dev/null +++ b/lib/paper_trail/frameworks/rails.rb @@ -0,0 +1,79 @@ +module PaperTrail + module Rails + module Controller + + def self.included(base) + if defined?(ActionController) && base == ActionController::Base + base.before_filter :set_paper_trail_enabled_for_controller + base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info + end + end + + protected + + # Returns the user who is responsible for any changes that occur. + # By default this calls `current_user` and returns the result. + # + # Override this method in your controller to call a different + # method, e.g. `current_person`, or anything you like. + def user_for_paper_trail + current_user if defined?(current_user) + end + + # Returns any information about the controller or request that you + # want PaperTrail to store alongside any changes that occur. By + # default this returns an empty hash. + # + # Override this method in your controller to return a hash of any + # information you need. The hash's keys must correspond to columns + # in your `versions` table, so don't forget to add any new columns + # you need. + # + # For example: + # + # {:ip => request.remote_ip, :user_agent => request.user_agent} + # + # The columns `ip` and `user_agent` must exist in your `versions` # table. + # + # Use the `:meta` option to `PaperTrail::Model::ClassMethods.has_paper_trail` + # to store any extra model-level data you need. + def info_for_paper_trail + {} + end + + # Returns `true` (default) or `false` depending on whether PaperTrail should + # be active for the current request. + # + # Override this method in your controller to specify when PaperTrail should + # be off. + def paper_trail_enabled_for_controller + true + end + + private + + # Tells PaperTrail whether versions should be saved in the current request. + def set_paper_trail_enabled_for_controller + ::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller + end + + # Tells PaperTrail who is responsible for any changes that occur. + def set_paper_trail_whodunnit + ::PaperTrail.whodunnit = user_for_paper_trail if paper_trail_enabled_for_controller + end + + # DEPRECATED: please use `set_paper_trail_whodunnit` instead. + def set_whodunnit + logger.warn '[PaperTrail]: the `set_whodunnit` controller method has been deprecated. Please rename to `set_paper_trail_whodunnit`.' + set_paper_trail_whodunnit + end + + # Tells PaperTrail any information from the controller you want + # to store alongside any changes that occur. + def set_paper_trail_controller_info + ::PaperTrail.controller_info = info_for_paper_trail if paper_trail_enabled_for_controller + end + + end + end +end diff --git a/lib/paper_trail/frameworks/sinatra.rb b/lib/paper_trail/frameworks/sinatra.rb new file mode 100644 index 00000000..eec7d4f3 --- /dev/null +++ b/lib/paper_trail/frameworks/sinatra.rb @@ -0,0 +1,35 @@ +module PaperTrail + module Sinatra + + # Register this module inside your Sinatra application to gain access to controller-level methods used by PaperTrail + def self.registered(app) + app.helpers PaperTrail::Sinatra + app.before { set_paper_trail_whodunnit } + end + + protected + + # Returns the user who is responsible for any changes that occur. + # By default this calls `current_user` and returns the result. + # + # Override this method in your controller to call a different + # method, e.g. `current_person`, or anything you like. + def user_for_paper_trail + current_user if defined?(current_user) + end + + private + + # Tells PaperTrail who is responsible for any changes that occur. + def set_paper_trail_whodunnit + ::PaperTrail.whodunnit = user_for_paper_trail if ::PaperTrail.enabled? + end + + end +end + +if defined?(Sinatra) + module Sinatra + register PaperTrail::Sinatra + end +end diff --git a/paper_trail.gemspec b/paper_trail.gemspec index d1194140..1470eb41 100644 --- a/paper_trail.gemspec +++ b/paper_trail.gemspec @@ -15,12 +15,14 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] - s.add_dependency 'railties', ['>= 3.0', '< 5.0'] s.add_dependency 'activerecord', ['>= 3.0', '< 5.0'] s.add_development_dependency 'rake' s.add_development_dependency 'shoulda', '~> 3.5' s.add_development_dependency 'ffaker', '>= 1.15' + s.add_development_dependency 'railties', ['>= 3.0', '< 5.0'] + s.add_development_dependency 'sinatra', '~> 1.0' + s.add_development_dependency 'rack-test', '>= 0.6' # JRuby support for the test ENV unless defined?(JRUBY_VERSION) diff --git a/test/functional/sinatra_test.rb b/test/functional/sinatra_test.rb new file mode 100644 index 00000000..8c698582 --- /dev/null +++ b/test/functional/sinatra_test.rb @@ -0,0 +1,88 @@ +require 'test_helper' +require 'sinatra/base' + +# --- Tests for modular `Sinatra::Base` style ---- +class BaseApp < Sinatra::Base + ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: File.expand_path('../../dummy/db/test.sqlite3', __FILE__)) + register PaperTrail::Sinatra + + get '/test' do + w = Widget.create!(name: 'foo') + 'Hello' + end + + def current_user + 'foobar' + end +end + +class PaperTrailModularSinatraTest < ActiveSupport::TestCase + include Rack::Test::Methods + + test 'baseline' do + assert_nil Widget.first + assert_nil Widget.create.versions.first.whodunnit + end + + context "`PaperTrail::Sinatra` in a `Sinatra::Base` application" do + + def app + @app ||= BaseApp + end + + should "sets the `user_for_paper_trail` from the `current_user` method" do + get '/test' + assert_equal 'Hello', last_response.body + widget = Widget.first + assert_not_nil widget + assert_equal 1, widget.versions.size + assert_equal 'foobar', widget.versions.first.whodunnit + end + + end +end + +# --- Tests for non-modular `Sinatra::Application` style ---- +class Sinatra::Application + ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: File.expand_path('../../dummy/db/test.sqlite3', __FILE__)) + + get '/test' do + w = Widget.create!(name: 'foo') + 'Hello' + end + + def current_user + 'foobar' + end +end + +class PaperTrailSinatraTest < ActiveSupport::TestCase + include Rack::Test::Methods + + def app + @app ||= Sinatra::Application + end + + test 'baseline' do + assert_nil Widget.first + assert_nil Widget.create.versions.first.whodunnit + end + + context "`PaperTrail::Sinatra` in a `Sinatra::Application` application" do + + def app + @app ||= BaseApp + end + + should "sets the `user_for_paper_trail` from the `current_user` method" do + get '/test' + assert_equal 'Hello', last_response.body + widget = Widget.first + assert_not_nil widget + assert_equal 1, widget.versions.size + assert_equal 'foobar', widget.versions.first.whodunnit + end + + end +end +