act_as_dirty 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +5 -0
- data/Rakefile +1 -0
- data/act_as_dirty.gemspec +24 -0
- data/act_as_dirty.gemspec~ +24 -0
- data/lib/act_as_dirty.rb +8 -0
- data/lib/act_as_dirty/active_model.rb +13 -0
- data/lib/act_as_dirty/active_model/cleaner.rb +54 -0
- data/lib/act_as_dirty/active_model/cleans.rb +122 -0
- data/lib/act_as_dirty/active_model/dirt.rb +284 -0
- data/lib/act_as_dirty/active_record.rb +9 -0
- data/lib/act_as_dirty/active_record/cleans.rb +31 -0
- data/lib/act_as_dirty/railtie.rb +10 -0
- data/lib/act_as_dirty/version.rb +3 -0
- data/lib/act_as_dirty/version.rb~ +3 -0
- data/spec/act_as_dirty/dirty_spec.rb +9 -0
- data/spec/spec_helper.rb +2 -0
- metadata +79 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 OrangeBrule
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "act_as_dirty/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "act_as_dirty"
|
7
|
+
s.version = ActAsDirty::VERSION
|
8
|
+
s.authors = ["Mathieu Gagne"]
|
9
|
+
s.email = ["mathieu@orangebrule.com"]
|
10
|
+
s.homepage = "http://github.com/orangebrule/act_as_dirty"
|
11
|
+
s.summary = %q{Create a message of all changes made to a record each time it saves using ActiveModel::Dirty. }
|
12
|
+
s.description = %q{Keep a log of what every user does on your system. Very useful in CRM, it would allow you to easily keep track of what every user has done to the record.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "act_as_dirty"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "act_as_dirty/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "act_as_dirty"
|
7
|
+
s.version = ActAsDirty::VERSION
|
8
|
+
s.authors = ["Mathieu Gagne"]
|
9
|
+
s.email = ["mathieu@orangebrule.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{TODO: Write a gem summary}
|
12
|
+
s.description = %q{TODO: Write a gem description}
|
13
|
+
|
14
|
+
s.rubyforge_project = "act_as_dirty"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
data/lib/act_as_dirty.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'act_as_dirty/active_model/cleaner'
|
2
|
+
require 'act_as_dirty/active_model/cleans'
|
3
|
+
require 'act_as_dirty/active_model/dirt'
|
4
|
+
|
5
|
+
module ActAsDirty
|
6
|
+
module ActiveModel
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
autoload :Cleans
|
10
|
+
autoload :Cleaner
|
11
|
+
autoload :Dirt
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActAsDirty
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
|
5
|
+
# == DirtyMe Cleaner
|
6
|
+
#
|
7
|
+
#
|
8
|
+
class Cleaner
|
9
|
+
attr_reader :options, :attributes
|
10
|
+
|
11
|
+
# Accepts options that will be made available through the +options+ reader.
|
12
|
+
def initialize(options)
|
13
|
+
@attributes = Array.wrap(options[:attributes])
|
14
|
+
raise ":attributes cannot be blank" if @attributes.empty?
|
15
|
+
@options = options.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
# Performs cleaning on the supplied record. By default this will call
|
19
|
+
# +clean_each+ to determine cleanliness therefore subclasses should
|
20
|
+
# override +clean_each+ with cleaning logic.
|
21
|
+
def clean(record)
|
22
|
+
return unless record.changed?
|
23
|
+
attributes.each do |attribute|
|
24
|
+
next unless record.changes[attribute.to_s]
|
25
|
+
clean_each(record, attribute)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def clean_each(record, attribute)
|
30
|
+
record.dirt.set(attribute, generate_message(record, attribute))
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def generate_message record, attribute
|
36
|
+
changes = record.read_changes_for_cleaning(attribute)
|
37
|
+
if record.new_record?
|
38
|
+
if @options[:create]
|
39
|
+
message = @options[:create].call(record)
|
40
|
+
else
|
41
|
+
message = "Added #{record.class.to_s} #{attribute.to_s.humanize} #{changes[1]}"
|
42
|
+
end
|
43
|
+
elsif @options[:update]
|
44
|
+
message = @options[:update].call(record)
|
45
|
+
else
|
46
|
+
message = "Updated #{record.class.to_s} #{attribute.to_s.humanize} from #{changes[0]} to #{changes[1]}"
|
47
|
+
end
|
48
|
+
message
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module ActAsDirty
|
2
|
+
module ActiveModel
|
3
|
+
module Cleans
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveSupport::Callbacks
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_accessor :cleaning_context
|
10
|
+
define_callbacks :clean, :scope => :name
|
11
|
+
|
12
|
+
class_attribute :_cleaners
|
13
|
+
self._cleaners = Hash.new { |h,k| h[k] = [] }
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def act_as_dirty
|
19
|
+
has_many :dirty_messages, :as => :dirtable, dependent: :destroy
|
20
|
+
accepts_nested_attributes_for :dirty_messages, :reject_if => lambda { |a| a[:message].blank? }, :allow_destroy => true
|
21
|
+
end
|
22
|
+
|
23
|
+
def cleans(*attributes)
|
24
|
+
defaults = attributes.extract_options!
|
25
|
+
options = defaults.slice!(*_cleaning_default_keys)
|
26
|
+
|
27
|
+
raise ArgumentError, 'Specify at least one attribute you would like DirtyMe to handle the message for' if attributes.empty?
|
28
|
+
|
29
|
+
attributes = self.attribute_names.map(&:to_sym) if attributes.include? :all
|
30
|
+
defaults.merge!(:attributes => attributes)
|
31
|
+
|
32
|
+
attributes.each do |attr, options|
|
33
|
+
raise ArgumentError, "The attribute '#{attr}' doesn't correspond to a column in the database" unless self.columns_hash[attr.to_s]
|
34
|
+
cleans_with(ActAsDirty::ActiveModel::Cleaner, defaults)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cleans_with(*args, &block)
|
39
|
+
options = args.extract_options!
|
40
|
+
args.each do |klass|
|
41
|
+
cleaner = klass.new(options, &block)
|
42
|
+
cleaner.setup(self) if cleaner.respond_to?(:setup)
|
43
|
+
|
44
|
+
if cleaner.respond_to?(:attributes) && !cleaner.attributes.empty?
|
45
|
+
cleaner.attributes.each do |attribute|
|
46
|
+
_cleaners[attribute.to_sym] << cleaner
|
47
|
+
end
|
48
|
+
else
|
49
|
+
_cleaners[nil] << cleaner
|
50
|
+
end
|
51
|
+
|
52
|
+
clean(cleaner, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def clean(*args, &block)
|
57
|
+
options = args.extract_options!
|
58
|
+
if options.key?(:on)
|
59
|
+
options = options.dup
|
60
|
+
options[:if] = Array.wrap(options[:if])
|
61
|
+
options[:if].unshift("cleaning_context == :#{options[:on]}")
|
62
|
+
end
|
63
|
+
args << options
|
64
|
+
set_callback(:clean, *args, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# List all trackers that are being used to create the message using
|
68
|
+
def cleaners
|
69
|
+
_cleaners.values.flatten.uniq
|
70
|
+
end
|
71
|
+
|
72
|
+
# List all validators that being used to validate a specific attribute.
|
73
|
+
def cleaners_on(*attributes)
|
74
|
+
attributes.map do |attribute|
|
75
|
+
_cleaners[attribute.to_sym]
|
76
|
+
end.flatten
|
77
|
+
end
|
78
|
+
|
79
|
+
# Copy validators on inheritance.
|
80
|
+
def inherited(base)
|
81
|
+
dup = _cleaners.dup
|
82
|
+
base._cleaners = dup.each { |k, v| dup[k] = v.dup }
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def _cleaning_default_keys
|
89
|
+
[:create, :update, :delete, :using, :allow_blank, :allow_nil]
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def dirt
|
95
|
+
@dirt ||= Dirt.new(self)
|
96
|
+
end
|
97
|
+
|
98
|
+
def clean?(context= nil)
|
99
|
+
current_context, self.cleaning_context = cleaning_context, context
|
100
|
+
dirt.clear
|
101
|
+
run_cleaners!
|
102
|
+
ensure
|
103
|
+
self.cleaning_context = current_context
|
104
|
+
end
|
105
|
+
|
106
|
+
def dirty?(context = nil)
|
107
|
+
!clean?(context)
|
108
|
+
end
|
109
|
+
|
110
|
+
def read_changes_for_cleaning(key)
|
111
|
+
[self.changes[key][0], self.changes[key][1]]
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
|
116
|
+
def run_cleaners!
|
117
|
+
run_callbacks :clean
|
118
|
+
dirt.empty?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'active_support/core_ext/array/wrap'
|
4
|
+
require 'active_support/core_ext/array/conversions'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
require 'active_support/core_ext/object/blank'
|
7
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
8
|
+
require 'active_support/ordered_hash'
|
9
|
+
|
10
|
+
module ActAsDirty
|
11
|
+
module ActiveModel
|
12
|
+
class Dirt
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
attr_reader :messages
|
16
|
+
|
17
|
+
def initialize(base)
|
18
|
+
@base = base
|
19
|
+
@messages = ActiveSupport::OrderedHash.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Clear the messages
|
23
|
+
def clear
|
24
|
+
messages.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
# Do the dirty messages include a message for the attribute +attribute+?
|
28
|
+
def include?(attribute)
|
29
|
+
(v = messages[attribute]) && v.any?
|
30
|
+
end
|
31
|
+
alias :has_key? :include?
|
32
|
+
|
33
|
+
# Get messages for +key+
|
34
|
+
def get(key)
|
35
|
+
messages[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set messages for +key+ to +value+
|
39
|
+
def set(key, value)
|
40
|
+
messages[key] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
# Delete messages for +key+
|
44
|
+
def delete(key)
|
45
|
+
messages.delete(key)
|
46
|
+
end
|
47
|
+
|
48
|
+
# When passed a symbol or a name of a method, returns an array of messages
|
49
|
+
# for the method.
|
50
|
+
#
|
51
|
+
# p.dirt[:name] # => ["has been updated from Bob to John"]
|
52
|
+
# p.dirt['name'] # => ["has been updated from Bob to John"]
|
53
|
+
def [](attribute)
|
54
|
+
get(attribute.to_sym) || set(attribute.to_sym, [])
|
55
|
+
end
|
56
|
+
|
57
|
+
# Iterates through each error key, value pair in the error messages hash.
|
58
|
+
# Yields the attribute and the error for that attribute. If the attribute
|
59
|
+
# has more than one error message, yields once for each error message.
|
60
|
+
#
|
61
|
+
# p.errors.add(:name, "can't be blank")
|
62
|
+
# p.errors.each do |attribute, errors_array|
|
63
|
+
# # Will yield :name and "can't be blank"
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# p.errors.add(:name, "must be specified")
|
67
|
+
# p.errors.each do |attribute, errors_array|
|
68
|
+
# # Will yield :name and "can't be blank"
|
69
|
+
# # then yield :name and "must be specified"
|
70
|
+
# end
|
71
|
+
def each
|
72
|
+
messages.each_key do |attribute|
|
73
|
+
yield attribute
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the number of error messages.
|
78
|
+
#
|
79
|
+
# p.dirt.add(:name, "has been updated from Bob to John")
|
80
|
+
# p.dirt.size # => 1
|
81
|
+
# p.dirt.add(:name, "has been updated from Bob to John")
|
82
|
+
# p.dirt.size # => 2
|
83
|
+
# def size
|
84
|
+
# values.flatten.size
|
85
|
+
# end
|
86
|
+
|
87
|
+
# Returns all message values
|
88
|
+
def values
|
89
|
+
messages.values
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns all message keys
|
93
|
+
def keys
|
94
|
+
messages.keys
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns an array of dirty messages, with the attribute name included
|
98
|
+
#
|
99
|
+
# p.dirt.add(:name, "has been updated from Bob to John")
|
100
|
+
# p.dirt.add(:nickname, "has been updated from Bobby to Johnny")
|
101
|
+
# p.dirt.to_a # => ["Name has been updated from Bob to John", "Nickname has been updated from Bobby to Johnny"]
|
102
|
+
def to_a
|
103
|
+
full_messages
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the number of dirty messages.
|
107
|
+
def count
|
108
|
+
to_a.size
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true if no dirty messages are found, false otherwise.
|
112
|
+
# If the dirty message is a string it can be empty.
|
113
|
+
def empty?
|
114
|
+
values.compact.empty?
|
115
|
+
end
|
116
|
+
alias_method :blank?, :empty?
|
117
|
+
|
118
|
+
# Returns an xml formatted representation of the Dirt hash.
|
119
|
+
#
|
120
|
+
# p.dirt.add(:name, "has been updated from Bob to John")
|
121
|
+
# p.dirt.add(:nickname, "has been updated from Bobby to Johnny")
|
122
|
+
# p.dirt.to_xml
|
123
|
+
# # =>
|
124
|
+
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
125
|
+
# # <dirts>
|
126
|
+
# # <dirt>name has been updated from Bob to John</dirt>
|
127
|
+
# # <dirt>name has been updated from Bobby to Johnny</dirt>
|
128
|
+
# # </dirts>
|
129
|
+
def to_xml(options={})
|
130
|
+
to_a.to_xml options.reverse_merge(:root => "dirts", :skip_types => true)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
|
134
|
+
def as_json(options=nil)
|
135
|
+
to_hash
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_hash
|
139
|
+
messages.dup
|
140
|
+
end
|
141
|
+
|
142
|
+
# Adds +message+ to the dirty messages on +attribute+. More than one error can be added to the same
|
143
|
+
# +attribute+.
|
144
|
+
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
|
145
|
+
#
|
146
|
+
# If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_dirt+).
|
147
|
+
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
|
148
|
+
# def add(attribute, message = nil, options = {})
|
149
|
+
# message = normalize_message(attribute, message, options)
|
150
|
+
# self[attribute] << message
|
151
|
+
# end
|
152
|
+
|
153
|
+
# Will add a dirty message to each of the attributes in +attributes+ that is empty.
|
154
|
+
# def add_on_empty(attributes, options = {})
|
155
|
+
# [attributes].flatten.each do |attribute|
|
156
|
+
# value = @base.send(:read_attribute_for_cleaning, attribute)
|
157
|
+
# is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
158
|
+
# add(attribute, :empty, options) if value.nil? || is_empty
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# # Will add a dirty message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
163
|
+
# def add_on_blank(attributes, options = {})
|
164
|
+
# [attributes].flatten.each do |attribute|
|
165
|
+
# value = @base.send(:read_attribute_for_cleaning, attribute)
|
166
|
+
# add(attribute, :blank, options) if value.blank?
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
|
170
|
+
# Returns true if a dirty message on the attribute with the given message is present, false otherwise.
|
171
|
+
# +message+ is treated the same as for +add+.
|
172
|
+
# p.dirt.add :name, :blank
|
173
|
+
# p.dirt.added? :name, :blank # => true
|
174
|
+
# def added?(attribute, message = nil, options = {})
|
175
|
+
# message = normalize_message(attribute, message, options)
|
176
|
+
# self[attribute].include? message
|
177
|
+
# end
|
178
|
+
|
179
|
+
def added?(attribute)
|
180
|
+
keys.include? attribute && messages[attribute].present?
|
181
|
+
end
|
182
|
+
|
183
|
+
# # Returns all the full error messages in an array.
|
184
|
+
# #
|
185
|
+
# # class User
|
186
|
+
# # cleans :name, :nickname, :email
|
187
|
+
# # end
|
188
|
+
# #
|
189
|
+
# # company = Company.create(:name => "John Doe", :nickname => "Johnny", :email => "john@example.com")
|
190
|
+
# # company.dirt.full_messages # =>
|
191
|
+
# # ["Added John Doe as a name", "Added Johnny as a nickname", "Added john@example.com as an email"]
|
192
|
+
def full_messages
|
193
|
+
# map { |attribute, message| full_message(attribute, message) }
|
194
|
+
values.flatten
|
195
|
+
end
|
196
|
+
|
197
|
+
# # Returns a full message for a given attribute.
|
198
|
+
# #
|
199
|
+
# # user.dirt.full_message(:name) # =>
|
200
|
+
# # "Added John Doe as a name"
|
201
|
+
# def full_message(attribute, message)
|
202
|
+
# return message if attribute == :base
|
203
|
+
# attr_name = attribute.to_s.gsub('.', '_').humanize
|
204
|
+
# attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
|
205
|
+
# I18n.t(:"errors.format", {
|
206
|
+
# :default => "%{attribute} %{message}",
|
207
|
+
# :attribute => attr_name,
|
208
|
+
# :message => message
|
209
|
+
# })
|
210
|
+
# end
|
211
|
+
|
212
|
+
# Translates a dirty message in its default scope
|
213
|
+
# (<tt>activemodel.dirty.messages</tt>).
|
214
|
+
#
|
215
|
+
# Dirty messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
|
216
|
+
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not
|
217
|
+
# there also, it returns the translation of the default message
|
218
|
+
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
|
219
|
+
# translated attribute name and the value are available for interpolation.
|
220
|
+
#
|
221
|
+
# When using inheritance in your models, it will check all the inherited
|
222
|
+
# models too, but only if the model itself hasn't been found. Say you have
|
223
|
+
# <tt>class Admin < User; end</tt> and you wanted the translation for
|
224
|
+
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
|
225
|
+
# it looks for these translations:
|
226
|
+
#
|
227
|
+
# * <tt>activemodel.dirty.models.admin.attributes.title.blank</tt>
|
228
|
+
# * <tt>activemodel.dirty.models.admin.blank</tt>
|
229
|
+
# * <tt>activemodel.dirty.models.user.attributes.title.blank</tt>
|
230
|
+
# * <tt>activemodel.dirty.models.user.blank</tt>
|
231
|
+
# * any default you provided through the +options+ hash (in the <tt>activemodel.dirty</tt> scope)
|
232
|
+
# * <tt>activemodel.dirty.messages.blank</tt>
|
233
|
+
# * <tt>dirty.attributes.title.blank</tt>
|
234
|
+
# * <tt>dirty.messages.blank</tt>
|
235
|
+
#
|
236
|
+
def generate_message(attribute, type = :invalid, options = {})
|
237
|
+
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
238
|
+
|
239
|
+
if @base.class.respond_to?(:i18n_scope)
|
240
|
+
defaults = @base.class.lookup_ancestors.map do |klass|
|
241
|
+
[ :"#{@base.class.i18n_scope}.dirty.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
242
|
+
:"#{@base.class.i18n_scope}.dirty.models.#{klass.model_name.i18n_key}.#{type}" ]
|
243
|
+
end
|
244
|
+
else
|
245
|
+
defaults = []
|
246
|
+
end
|
247
|
+
|
248
|
+
defaults << options.delete(:message)
|
249
|
+
defaults << :"#{@base.class.i18n_scope}.dirty.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
|
250
|
+
defaults << :"dirty.attributes.#{attribute}.#{type}"
|
251
|
+
defaults << :"dirty.messages.#{type}"
|
252
|
+
|
253
|
+
defaults.compact!
|
254
|
+
defaults.flatten!
|
255
|
+
|
256
|
+
key = defaults.shift
|
257
|
+
value = (attribute != :base ? @base.send(:read_attribute_for_cleaning, attribute) : nil)
|
258
|
+
|
259
|
+
options = {
|
260
|
+
:default => defaults,
|
261
|
+
:model => @base.class.model_name.human,
|
262
|
+
:attribute => @base.class.human_attribute_name(attribute),
|
263
|
+
:value => value
|
264
|
+
}.merge(options)
|
265
|
+
|
266
|
+
I18n.translate(key, options)
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
def normalize_message(attribute, message, options)
|
271
|
+
message ||= :invalid
|
272
|
+
|
273
|
+
if message.is_a?(Symbol)
|
274
|
+
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
275
|
+
elsif message.is_a?(Proc)
|
276
|
+
message.call
|
277
|
+
else
|
278
|
+
message
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActAsDirty
|
2
|
+
module ActiveRecord
|
3
|
+
module Cleans
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ActAsDirty::ActiveModel::Cleans
|
6
|
+
|
7
|
+
def save(options={})
|
8
|
+
perform_cleanings(options)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def clean?(context = nil)
|
13
|
+
context ||= (new_record? ? :create : :update)
|
14
|
+
output = super(context)
|
15
|
+
dirt.empty? && output
|
16
|
+
end
|
17
|
+
|
18
|
+
def dirty?(context = nil)
|
19
|
+
!clean?(context)
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def perform_cleanings(options={})
|
25
|
+
perform_cleaning = options[:clean] != false
|
26
|
+
perform_cleaning ? clean?(options[:context]) : true
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: act_as_dirty
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mathieu Gagne
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &12388040 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *12388040
|
25
|
+
description: Keep a log of what every user does on your system. Very useful in CRM,
|
26
|
+
it would allow you to easily keep track of what every user has done to the record.
|
27
|
+
email:
|
28
|
+
- mathieu@orangebrule.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- .rspec
|
35
|
+
- CHANGELOG.md
|
36
|
+
- Gemfile
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- act_as_dirty.gemspec
|
41
|
+
- act_as_dirty.gemspec~
|
42
|
+
- lib/act_as_dirty.rb
|
43
|
+
- lib/act_as_dirty/active_model.rb
|
44
|
+
- lib/act_as_dirty/active_model/cleaner.rb
|
45
|
+
- lib/act_as_dirty/active_model/cleans.rb
|
46
|
+
- lib/act_as_dirty/active_model/dirt.rb
|
47
|
+
- lib/act_as_dirty/active_record.rb
|
48
|
+
- lib/act_as_dirty/active_record/cleans.rb
|
49
|
+
- lib/act_as_dirty/railtie.rb
|
50
|
+
- lib/act_as_dirty/version.rb
|
51
|
+
- lib/act_as_dirty/version.rb~
|
52
|
+
- spec/act_as_dirty/dirty_spec.rb
|
53
|
+
- spec/spec_helper.rb
|
54
|
+
homepage: http://github.com/orangebrule/act_as_dirty
|
55
|
+
licenses: []
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project: act_as_dirty
|
74
|
+
rubygems_version: 1.8.17
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Create a message of all changes made to a record each time it saves using
|
78
|
+
ActiveModel::Dirty.
|
79
|
+
test_files: []
|