act_as_dirty 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ /nbproject/private/
6
+ /nbproject/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1
2
+
3
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in act_as_dirty.gemspec
4
+ gemspec
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
@@ -0,0 +1,5 @@
1
+ Add 'act_as_dirty' to your model to gain dirty_messages functionality
2
+
3
+ Add an observer to your project and save your changes to the database to create an activity log.
4
+
5
+ More details coming soon.
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
@@ -0,0 +1,8 @@
1
+ require "act_as_dirty/version"
2
+ require "act_as_dirty/active_model"
3
+ require "act_as_dirty/active_record"
4
+ require "act_as_dirty/railtie" if defined? Rails
5
+
6
+ module ActAsDirty
7
+
8
+ end
@@ -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,9 @@
1
+ require 'act_as_dirty/active_model'
2
+ require 'act_as_dirty/active_record/cleans'
3
+
4
+ module ActAsDirty
5
+ module ActiveRecord
6
+ extend ActiveSupport::Autoload
7
+ autoload :Cleans
8
+ end
9
+ 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
@@ -0,0 +1,10 @@
1
+ module ActAsDirty
2
+ class Railtie < Rails::Railtie
3
+ initializer 'act_as_dirty' do
4
+ ActiveSupport.on_load :active_record do
5
+ include ActAsDirty::ActiveModel::Cleans
6
+ include ActAsDirty::ActiveRecord::Cleans
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module ActAsDirty
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ module ActAsDirty
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActAsDirty do
4
+
5
+ it "should have tests" do
6
+ pending "Port your tests to the gem to prove the world you are not an idiot."
7
+ end
8
+
9
+ end
@@ -0,0 +1,2 @@
1
+ require 'rails'
2
+ require 'act_as_dirty'
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: []