activerecord-userstamp 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/CHANGELOG +26 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +21 -0
  8. data/README.md +220 -0
  9. data/Rakefile +17 -0
  10. data/activerecord-userstamp.gemspec +34 -0
  11. data/lib/active_record/userstamp.rb +14 -0
  12. data/lib/active_record/userstamp/controller_additions.rb +41 -0
  13. data/lib/active_record/userstamp/migration_additions.rb +16 -0
  14. data/lib/active_record/userstamp/model_additions.rb +3 -0
  15. data/lib/active_record/userstamp/stampable.rb +174 -0
  16. data/lib/active_record/userstamp/stamper.rb +39 -0
  17. data/lib/active_record/userstamp/version.rb +4 -0
  18. data/lib/activerecord/userstamp.rb +1 -0
  19. data/spec/controllers/posts_controller_spec.rb +46 -0
  20. data/spec/controllers/users_controller_spec.rb +41 -0
  21. data/spec/coverage_helper.rb +65 -0
  22. data/spec/dummy/README.rdoc +28 -0
  23. data/spec/dummy/Rakefile +6 -0
  24. data/spec/dummy/app/assets/images/.keep +0 -0
  25. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  26. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  27. data/spec/dummy/app/controllers/application_controller.rb +13 -0
  28. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  29. data/spec/dummy/app/controllers/posts_controller.rb +32 -0
  30. data/spec/dummy/app/controllers/users_controller.rb +18 -0
  31. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  32. data/spec/dummy/app/mailers/.keep +0 -0
  33. data/spec/dummy/app/models/comment.rb +6 -0
  34. data/spec/dummy/app/models/concerns/.keep +0 -0
  35. data/spec/dummy/app/models/foo.rb +3 -0
  36. data/spec/dummy/app/models/person.rb +4 -0
  37. data/spec/dummy/app/models/post.rb +13 -0
  38. data/spec/dummy/app/models/user.rb +4 -0
  39. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  40. data/spec/dummy/bin/bundle +3 -0
  41. data/spec/dummy/bin/rails +4 -0
  42. data/spec/dummy/bin/rake +4 -0
  43. data/spec/dummy/bin/setup +29 -0
  44. data/spec/dummy/config.ru +4 -0
  45. data/spec/dummy/config/application.rb +30 -0
  46. data/spec/dummy/config/boot.rb +5 -0
  47. data/spec/dummy/config/database.yml +25 -0
  48. data/spec/dummy/config/environment.rb +5 -0
  49. data/spec/dummy/config/environments/development.rb +41 -0
  50. data/spec/dummy/config/environments/production.rb +79 -0
  51. data/spec/dummy/config/environments/test.rb +37 -0
  52. data/spec/dummy/config/initializers/assets.rb +11 -0
  53. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  54. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  55. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  56. data/spec/dummy/config/initializers/inflections.rb +16 -0
  57. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  58. data/spec/dummy/config/initializers/session_store.rb +3 -0
  59. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/spec/dummy/config/locales/en.yml +23 -0
  61. data/spec/dummy/config/routes.rb +56 -0
  62. data/spec/dummy/config/secrets.yml +22 -0
  63. data/spec/dummy/db/schema.rb +55 -0
  64. data/spec/dummy/lib/assets/.keep +0 -0
  65. data/spec/dummy/log/.keep +0 -0
  66. data/spec/dummy/public/404.html +67 -0
  67. data/spec/dummy/public/422.html +67 -0
  68. data/spec/dummy/public/500.html +66 -0
  69. data/spec/dummy/public/favicon.ico +0 -0
  70. data/spec/lib/compatibility_stamping_spec.rb +69 -0
  71. data/spec/lib/migration_spec.rb +26 -0
  72. data/spec/lib/stamping_spec.rb +170 -0
  73. data/spec/lib/userstamp_spec.rb +7 -0
  74. data/spec/rails_helper.rb +7 -0
  75. data/spec/spec_helper.rb +97 -0
  76. data/spec/support/database_helpers.rb +22 -0
  77. data/spec/support/with_temporary_table.rb +50 -0
  78. metadata +276 -0
@@ -0,0 +1,174 @@
1
+ module ActiveRecord::Userstamp
2
+ # Determines what default columns to use for recording the current stamper.
3
+ # By default this is set to false, so the plug-in will use columns named
4
+ # <tt>creator_id</tt>, <tt>updater_id</tt>, and <tt>deleter_id</tt>.
5
+ #
6
+ # To turn compatibility mode on, place the following line in your environment.rb
7
+ # file:
8
+ #
9
+ # Ddb::Userstamp.compatibility_mode = true
10
+ #
11
+ # This will cause the plug-in to use columns named <tt>created_by</tt>,
12
+ # <tt>updated_by</tt>, and <tt>deleted_by</tt>.
13
+ mattr_accessor :compatibility_mode
14
+ @@compatibility_mode = false
15
+
16
+ # Extends the stamping functionality of ActiveRecord by automatically recording the model
17
+ # responsible for creating, updating, and deleting the current object. See the Stamper
18
+ # and Userstamp modules for further documentation on how the entire process works.
19
+ module Stampable
20
+ def self.included(base) #:nodoc:
21
+ super
22
+
23
+ base.extend(ClassMethods)
24
+ base.class_eval do
25
+ include InstanceMethods
26
+
27
+ # Should ActiveRecord record userstamps? Defaults to true.
28
+ class_attribute :record_userstamp
29
+ self.record_userstamp = true
30
+
31
+ # Which class is responsible for stamping? Defaults to :user.
32
+ class_attribute :stamper_class_name
33
+
34
+ # What column should be used for the creator stamp?
35
+ # Defaults to :creator_id when compatibility mode is off
36
+ # Defaults to :created_by when compatibility mode is on
37
+ class_attribute :creator_attribute
38
+
39
+ # What column should be used for the updater stamp?
40
+ # Defaults to :updater_id when compatibility mode is off
41
+ # Defaults to :updated_by when compatibility mode is on
42
+ class_attribute :updater_attribute
43
+
44
+ # What column should be used for the deleter stamp?
45
+ # Defaults to :deleter_id when compatibility mode is off
46
+ # Defaults to :deleted_by when compatibility mode is on
47
+ class_attribute :deleter_attribute
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+ # This method is automatically called on for all classes that inherit from
53
+ # ActiveRecord, but if you need to customize how the plug-in functions, this is the
54
+ # method to use. Here's an example:
55
+ #
56
+ # class Post < ActiveRecord::Base
57
+ # stampable :stamper_class_name => :person,
58
+ # :creator_attribute => :create_user,
59
+ # :updater_attribute => :update_user,
60
+ # :deleter_attribute => :delete_user,
61
+ # :deleter => true,
62
+ # :with_deleted => true
63
+ # end
64
+ #
65
+ # The method will automatically setup all the associations,
66
+ # and create <tt>before_validation</tt> & <tt>before_destroy</tt> callbacks for doing the stamping.
67
+ #
68
+ # By default, the deleter association and before filter are not defined unless
69
+ # you set the :deleter_attribute or set the :deleter option to true.
70
+ #
71
+ # When using the new acts_as_paranoid gem (https://github.com/goncalossilva/rails3_acts_as_paranoid)
72
+ # the :with_deleted option can be used to setup the associations to return objects that have been soft deleted.
73
+ #
74
+ def stampable(options = {})
75
+ compatability = ActiveRecord::Userstamp.compatibility_mode
76
+ defaults = {
77
+ :stamper_class_name => :user,
78
+ :creator_attribute => (compatability ? :created_by : :creator_id),
79
+ :updater_attribute => (compatability ? :updated_by : :updater_id),
80
+ :deleter_attribute => (compatability ? :deleted_by : :deleter_id),
81
+ :deleter => options.has_key?(:deleter_attribute),
82
+ :with_deleted => false
83
+ }.merge(options)
84
+
85
+ self.stamper_class_name = defaults[:stamper_class_name].to_sym
86
+ self.creator_attribute = defaults[:creator_attribute].to_sym
87
+ self.updater_attribute = defaults[:updater_attribute].to_sym
88
+ self.deleter_attribute = defaults[:deleter_attribute].to_sym
89
+
90
+ class_eval do
91
+ klass = "::#{stamper_class_name.to_s.singularize.camelize}"
92
+
93
+ if defaults[:with_deleted]
94
+ belongs_to :creator, :class_name => klass, :foreign_key => creator_attribute, :with_deleted => true
95
+ belongs_to :updater, :class_name => klass, :foreign_key => updater_attribute, :with_deleted => true
96
+ else
97
+ belongs_to :creator, :class_name => klass, :foreign_key => creator_attribute
98
+ belongs_to :updater, :class_name => klass, :foreign_key => updater_attribute
99
+ end
100
+
101
+ before_validation :set_updater_attribute
102
+ before_validation :set_creator_attribute, :on => :create
103
+ before_save :set_updater_attribute
104
+ before_save :set_creator_attribute, :on => :create
105
+
106
+ if defaults[:deleter]
107
+ if defaults[:with_deleted]
108
+ belongs_to :deleter, :class_name => klass, :foreign_key => deleter_attribute, :with_deleted => true
109
+ else
110
+ belongs_to :deleter, :class_name => klass, :foreign_key => deleter_attribute
111
+ end
112
+
113
+ before_destroy :set_deleter_attribute
114
+
115
+ end
116
+ end
117
+ end
118
+
119
+ # Temporarily allows you to turn stamping off. For example:
120
+ #
121
+ # Post.without_stamps do
122
+ # post = Post.find(params[:id])
123
+ # post.update_attributes(params[:post])
124
+ # post.save
125
+ # end
126
+ def without_stamps
127
+ original_value = self.record_userstamp
128
+ self.record_userstamp = false
129
+ yield
130
+ ensure
131
+ self.record_userstamp = original_value
132
+ end
133
+
134
+ def stamper_class #:nodoc:
135
+ stamper_class_name.to_s.camelize.constantize rescue nil
136
+ end
137
+ end
138
+
139
+ module InstanceMethods #:nodoc:
140
+ private
141
+ def has_stamper?
142
+ !self.class.stamper_class.nil? && !self.class.stamper_class.stamper.nil? rescue false
143
+ end
144
+
145
+ def set_creator_attribute
146
+ return unless self.record_userstamp
147
+ if respond_to?(self.creator_attribute.to_sym) && has_stamper?
148
+ if self.send(self.creator_attribute.to_sym).blank?
149
+ self.send("#{self.creator_attribute}=".to_sym, self.class.stamper_class.stamper)
150
+ end
151
+ end
152
+ end
153
+
154
+ def set_updater_attribute
155
+ return unless self.record_userstamp
156
+ # only set updater if the record is new or has changed
157
+ # or contains a serialized attribute (in which case the attribute value is always updated)
158
+ return unless self.new_record? || self.changed? || self.class.serialized_attributes.present?
159
+ if respond_to?(self.updater_attribute.to_sym) && has_stamper?
160
+ self.send("#{self.updater_attribute}=".to_sym, self.class.stamper_class.stamper)
161
+ end
162
+ end
163
+
164
+ def set_deleter_attribute
165
+ return unless self.record_userstamp
166
+ if respond_to?(self.deleter_attribute.to_sym) && has_stamper?
167
+ self.send("#{self.deleter_attribute}=".to_sym, self.class.stamper_class.stamper)
168
+ save
169
+ end
170
+ end
171
+ #end private
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveRecord::Userstamp
2
+ module Stamper
3
+ def self.included(base) # :nodoc:
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def model_stamper
9
+ # don't allow multiple calls
10
+ return if self.included_modules.include?(ActiveRecord::Userstamp::Stamper::InstanceMethods)
11
+ send(:extend, ActiveRecord::Userstamp::Stamper::InstanceMethods)
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ # Used to set the stamper for a particular request. See the Userstamp module for more
17
+ # details on how to use this method.
18
+ def stamper=(object)
19
+ object_stamper = if object.is_a?(ActiveRecord::Base)
20
+ object.send("#{object.class.primary_key}".to_sym)
21
+ else
22
+ object
23
+ end
24
+
25
+ Thread.current["#{self.to_s.downcase}_#{self.object_id}_stamper"] = object_stamper
26
+ end
27
+
28
+ # Retrieves the existing stamper for the current request.
29
+ def stamper
30
+ Thread.current["#{self.to_s.downcase}_#{self.object_id}_stamper"]
31
+ end
32
+
33
+ # Sets the stamper back to +nil+ to prepare for the next request.
34
+ def reset_stamper
35
+ Thread.current["#{self.to_s.downcase}_#{self.object_id}_stamper"] = nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveRecord; end
2
+ module ActiveRecord::Userstamp
3
+ VERSION = '2.1.1'
4
+ end
@@ -0,0 +1 @@
1
+ require 'active_record/userstamp'
@@ -0,0 +1,46 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe PostsController, type: :controller do
4
+ controller do
5
+ end
6
+
7
+ before(:each) do
8
+ reset_to_defaults
9
+ end
10
+
11
+ context 'when updating a Post' do
12
+ it 'sets the correct updater' do
13
+ request.session = { person_id: @delynn.id }
14
+ post :update, id: @first_post.id, post: { title: 'Different' }
15
+
16
+ expect(response.status).to eq(200)
17
+ expect(controller.instance_variable_get(:@post).title).to eq('Different')
18
+ expect(controller.instance_variable_get(:@post).updater).to eq(@delynn)
19
+ end
20
+ end
21
+
22
+ context 'when handling multiple requests' do
23
+ def simulate_second_request
24
+ old_request_session = request.session
25
+ request.session = { person_id: @nicole.id }
26
+
27
+ post :update, id: @first_post.id, post: { title: 'Different Second'}
28
+ expect(controller.instance_variable_get(:@post).updater).to eq(@nicole)
29
+ ensure
30
+ request.session = old_request_session
31
+ end
32
+
33
+ it 'sets the correct updater' do
34
+ request.session = { person_id: @delynn.id }
35
+ get :edit, id: @first_post.id
36
+ expect(response.status).to eq(200)
37
+
38
+ simulate_second_request
39
+
40
+ post :update, id: @first_post.id, post: { title: 'Different' }
41
+ expect(response.status).to eq(200)
42
+ expect(controller.instance_variable_get(:@post).title).to eq('Different')
43
+ expect(controller.instance_variable_get(:@post).updater).to eq(@delynn)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe UsersController, type: :controller do
4
+ controller do
5
+ end
6
+
7
+ before(:each) do
8
+ reset_to_defaults
9
+ end
10
+
11
+ context 'when updating a User' do
12
+ it 'sets the correct updater' do
13
+ request.session = { user_id: @hera.id }
14
+ patch :update, id: @hera.id, user: { name: 'Different'}
15
+
16
+ expect(response.status).to eq(200)
17
+ expect(controller.instance_variable_get(:@user).name).to eq('Different')
18
+ expect(controller.instance_variable_get(:@user).updater).to eq(@hera)
19
+ end
20
+ end
21
+
22
+ context 'when handling multiple requests' do
23
+ def simulate_second_request
24
+ old_request_session = request.session
25
+ request.session = { user_id: @zeus.id }
26
+
27
+ post :update, id: @hera.id, user: { name: 'Different Second' }
28
+ expect(controller.instance_variable_get(:@user).updater).to eq(@zeus)
29
+ ensure
30
+ request.session = old_request_session
31
+ end
32
+
33
+ it 'sets the correct updater' do
34
+ request.session = { user_id: @hera.id }
35
+ get :edit, id: @hera.id
36
+ expect(response.status).to eq(200)
37
+
38
+ simulate_second_request
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ # Simultaneous code coverage reporting to Coveralls and Code Climate.
2
+ # Latest version can be found at https://gist.github.com/lowjoel/6c2f2d3a08bb3786994f
3
+ require 'simplecov'
4
+
5
+ module CoverageHelper
6
+ class << self
7
+ # Helper to include Coveralls/Code Climate coverage, but not require developers to install the
8
+ # gem.
9
+ #
10
+ # @param [String] name The name of the module to require.
11
+ # @param [Proc] initializer The block to execute when the module is required successfully.
12
+ def load(name, &initializer)
13
+ old_formatter = SimpleCov.formatter
14
+ require name
15
+ initializer.call
16
+
17
+ merge_formatters(old_formatter, SimpleCov.formatter)
18
+ rescue LoadError => e
19
+ if e.path == name
20
+ puts format('Cannot find \'%s\', ignoring', name) if ENV['CI']
21
+ else
22
+ raise e
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # Merge two SimpleCov formatters into a single MultiFormatter.
29
+ #
30
+ # This method is idempotent if the old and new formatters are the same.
31
+ def merge_formatters(old_formatter, new_formatter)
32
+ return if old_formatter == new_formatter
33
+
34
+ old_formatter = [*expand_formatter(old_formatter)]
35
+ new_formatter = [*expand_formatter(new_formatter)]
36
+ formatters = old_formatter + new_formatter
37
+
38
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[*formatters]
39
+ end
40
+
41
+ # Extracts the formatters from a MultiFormatter so we do not nest them.
42
+ def expand_formatter(formatter)
43
+ return formatter unless formatter.is_a?(SimpleCov::Formatter::MultiFormatter)
44
+ formatter.formatters
45
+ end
46
+ end
47
+ end
48
+
49
+ if ENV['CI']
50
+ # Coveralls
51
+ CoverageHelper.load('coveralls') do
52
+ Coveralls.wear!('rails')
53
+ end
54
+
55
+ # Code Climate
56
+ CoverageHelper.load('codeclimate-test-reporter') do
57
+ CodeClimate::TestReporter.start
58
+ end
59
+
60
+ # Code coverage exclusions
61
+ SimpleCov.start do
62
+ # SimpleCov configuration
63
+ # add_filter '/lib/extensions/active_record/connection_adapters/table_definition.rb'
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
File without changes
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */