activerecord-userstamp 2.1.1

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 (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
+ */