Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9b9119a
Showing
16 changed files
with
5,830 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2008 [name of plugin creator] | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
CustomChangeMessages | ||
==================== | ||
|
||
CustomChangeMessages is a rails plugin for extending the dirty objects methods introduced in rails 2.1 | ||
http://ryandaigle.com/articles/2008/3/31/what-s-new-in-edge-rails-dirty-objects | ||
It provides customizable messages for the changed attributes on a record, and works nicely with any foriegn | ||
keys that are a belongs_to association. | ||
|
||
ActiveRecord extensions: | ||
|
||
change_message_for(attribute) # Returns a string message representation of the attribute that has changed | ||
change_messages # Returns an array of the messages for each changed attribute | ||
|
||
Installation | ||
============ | ||
|
||
installation: script/plugin install git://github.com/jeremyolliver/custom_message_changes.git | ||
|
||
Requirements: rails v2.1 or greater, if you use an earlier version check out either: | ||
modelnotifier http://github.com/jeremyolliver/modelnotifier/tree/master The original version of this plugin | ||
or http://code.bitsweat.net/svn/dirty a plugin that implements the code for dirty models introduced in 2.1 that | ||
CustomChangeMesssages relies on. | ||
|
||
Example | ||
======= | ||
|
||
The main use of this is to help clean up controller actions, such as: | ||
|
||
class ItemsController < ApplicationController | ||
|
||
def update | ||
@item.attributes = params[:item] | ||
Mailer.deliver_item_update(@item, @item.change_messages.to_sentence) | ||
@item.save! | ||
|
||
rescue ActiveRecord::RecordInvalid => e | ||
flash[:error] = e.reord.error_messages | ||
redirect_to item_url(@item) | ||
end | ||
#... | ||
end | ||
|
||
|
||
@item.change_messages.to_sentence will return human readable mesages such as: | ||
=> "Description has changed from 'Nice and easy' to 'This task is now rather long and arduous', User has changed from 'Jeremy' to 'Guy', and Due Date has been rescheduled from '09/11/2008' to '10/11/2008'" | ||
|
||
The messages for each attribute are also customizable, which is especially handy for dealing with belongs_to | ||
assocations. Here's a more complicated example: | ||
|
||
Use the custom_message_for method to customize the message for the attribute, specifying :display => :name | ||
will use the method/attribute :name for displaying the record that the item belongs_to | ||
|
||
The skip_message_for method can be used to prevent stop any changes to a particular attribute showing up | ||
|
||
class Item < ActiveRecord::Base | ||
belongs_to :person | ||
|
||
custom_message_for :person, :display => :username # display the person's username instead of the id | ||
custom_message_for :due_on, :as => "Due Date", :message => "has been rescheduled", :format => :pretty_print_date | ||
# change the syntax of the message for working with dates, because it makes more sense that way | ||
|
||
# this method is used for formatting the due_on field when it changes | ||
def pretty_print_date(value = self.due_on) | ||
value.strftime("%d/%m/%Y") | ||
end | ||
end | ||
|
||
class Person < ActiveRecord::Base | ||
custom_message_for :username, :as => "Name" | ||
skip_message_for :internal_calculation | ||
end | ||
|
||
p = Person.create!(:username => "Jeremy") | ||
p2 = Person.create!(:username => "Optimus Prime") | ||
i = Item.create!(:name => "My Task", :description => nil, :person => p, :due_on => Date.today) | ||
i.attributes = {:person => p2, :description => "This task is now rather long and arduous"} | ||
|
||
i.change_messages | ||
=> ["Due Date has been rescheduled from '4/12/2008' to '5/12/2008'", "Person has changed from 'Jeremy' to 'Optimus Prime'", "Description has changed from '' to 'This task is now rather long and arduous'"] | ||
|
||
|
||
|
||
Copyright (c) 2008 Jeremy Olliver, released under the MIT license |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
require 'rake' | ||
require 'rake/testtask' | ||
require 'rake/rdoctask' | ||
|
||
desc 'Default: run unit tests.' | ||
task :default => :test | ||
|
||
desc 'Test the model_notifier plugin.' | ||
Rake::TestTask.new(:test) do |t| | ||
t.libs << 'lib' | ||
t.pattern = 'test/**/*_test.rb' | ||
t.verbose = true | ||
end | ||
|
||
desc 'Generate documentation for the model_notifier plugin.' | ||
Rake::RDocTask.new(:rdoc) do |rdoc| | ||
rdoc.rdoc_dir = 'rdoc' | ||
rdoc.title = 'ModelNotifier' | ||
rdoc.options << '--line-numbers' << '--inline-source' | ||
rdoc.rdoc_files.include('README') | ||
rdoc.rdoc_files.include('lib/**/*.rb') | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Install hook code here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require 'custom_change_messages/active_record' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
ActiveRecord::Base.class_eval do | ||
|
||
# hash and array to keep track of the customised messages, belongs_to associations, and any skipped attributes | ||
class_inheritable_hash :custom_dirty_messages | ||
class_inheritable_array :skipped_dirty_attributes | ||
|
||
class << self | ||
|
||
def init_messages | ||
unless self.custom_dirty_messages | ||
self.custom_dirty_messages = {} | ||
self.reflect_on_all_associations(:belongs_to).each do |association| | ||
self.custom_dirty_messages[association.primary_key_name.to_sym] = {:type => :belongs_to, :association_name => association.name, :as => association.name.to_s.capitalize} | ||
end | ||
end | ||
end | ||
|
||
def custom_message_for(*attr_names) | ||
init_messages | ||
options = attr_names.extract_options! | ||
attr_names.each do |attribute| | ||
key = key_for(attribute) | ||
if self.custom_dirty_messages[key] | ||
# if options are being passed for an associations attribute, or an association | ||
self.custom_dirty_messages[key].merge!(options) | ||
else | ||
self.custom_dirty_messages.merge!({attribute.to_sym => options}) | ||
end | ||
end | ||
end | ||
|
||
def skip_message_for(*attr_names) | ||
self.skipped_dirty_attributes ||= [:updated_at, :created_at, :id] | ||
attr_names.extract_options! | ||
self.skipped_dirty_attributes += attr_names | ||
end | ||
|
||
private | ||
|
||
def key_for(attribute) | ||
# first check if it's a belongs_to association | ||
if (assoc = self.reflect_on_association(attribute)) | ||
assoc.primary_key_name.to_sym | ||
else | ||
attribute.to_sym | ||
end | ||
end | ||
|
||
end | ||
|
||
def change_messages | ||
messages = [] | ||
changes.each do |attribute, diff| | ||
attribute = attribute.to_sym | ||
self.class.skipped_dirty_attributes ||= [:updated_at, :created_at, :id] | ||
next if self.class.skipped_dirty_attributes.include?(attribute) | ||
messages << change_message_for(attribute, diff) | ||
end | ||
messages | ||
end | ||
|
||
def change_message_for(attribute, changes = nil) | ||
attribute = key_for(attribute) | ||
changes ||= self.send((attribute.to_s + "_change").to_sym) | ||
val = "#{attr_name(attribute)} #{watch_value(attribute, :message)}" | ||
val += " #{watch_value(attribute, :prefix)} \'#{attr_display(attribute, changes.first)}\'" unless watch_value(attribute, :no_prefix) | ||
val += " #{watch_value(attribute, :suffix)} \'#{attr_display(attribute, changes.last)}\'" unless watch_value(attribute, :no_suffix) | ||
val | ||
end | ||
|
||
private | ||
|
||
def key_for(attribute) | ||
# first check if it's a belongs_to association | ||
if (assoc = self.class.reflect_on_association(attribute)) | ||
assoc.primary_key_name.to_sym | ||
else | ||
attribute.to_sym | ||
end | ||
end | ||
|
||
# check if it's an association name, or if the attribute is being watched | ||
def attr_name(attribute) | ||
if self.class.custom_dirty_messages[attribute] | ||
if (name = self.class.custom_dirty_messages[attribute][:as]) | ||
name | ||
elsif is_association?(attribute) | ||
(n = self.class.custom_dirty_messages[attribute][:association_name]) ? n.to_s.capitalize : attribute.to_s.capitalize | ||
else | ||
attribute.to_s.capitalize | ||
end | ||
else | ||
attribute.to_s.capitalize | ||
end | ||
end | ||
|
||
def attr_display(attribute, value) | ||
attribute = key_for(attribute) | ||
if self.class.custom_dirty_messages[attribute] | ||
if (meth = self.class.custom_dirty_messages[attribute.to_sym][:format]) | ||
return self.send(meth, value) | ||
elsif (meth = self.class.custom_dirty_messages[attribute.to_sym][:display]) | ||
raise ":display option set on an attribute which isn't a belongs_to association" unless is_association?(attribute) | ||
assoc = self.class.reflect_on_association(association_name(attribute)) | ||
finder = ("find_by_" + assoc.klass.primary_key).to_sym | ||
return assoc.klass.send(finder, value).send(meth.to_sym) | ||
end | ||
end | ||
return value.to_s | ||
end | ||
|
||
def association_name(attribute) | ||
self.class.custom_dirty_messages[attribute][:association_name] | ||
end | ||
|
||
def is_association?(attribute) | ||
attribute = key_for(attribute) | ||
self.class.custom_dirty_messages[attribute][:association_name] | ||
end | ||
|
||
def watch_value(attribute, option) | ||
if self.class.custom_dirty_messages[attribute.to_sym] | ||
self.class.custom_dirty_messages[attribute.to_sym][option] || watch_option_defaults[option] | ||
else | ||
watch_option_defaults[option] | ||
end | ||
end | ||
|
||
def watch_option_defaults | ||
{:message => "has changed", :prefix => "from", :suffix => "to"} | ||
end | ||
|
||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require 'custom_change_messages' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# desc "Explaining what the task does" | ||
# task :custom_change_messages do | ||
# # Task goes here | ||
# end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
require File.dirname(__FILE__) + '/test_helper.rb' | ||
|
||
class ActiveRecordTest < Test::Unit::TestCase | ||
|
||
load_schema | ||
|
||
class Item < ActiveRecord::Base | ||
belongs_to :person | ||
|
||
custom_message_for :person, :display => :username | ||
custom_message_for :due_on, :as => "Due Date", :message => "has been rescheduled", :format => :pretty_print_date | ||
|
||
def pretty_print_date(value = self.due_on) | ||
value.strftime("%d/%m/%Y") | ||
end | ||
end | ||
|
||
class Person < ActiveRecord::Base | ||
custom_message_for :username, :as => "Name" | ||
skip_message_for :internal_calculation | ||
end | ||
|
||
def test_active_record_extension | ||
i = Item.create! | ||
assert i.respond_to?(:change_messages) | ||
end | ||
|
||
def test_ignores_timestamps | ||
i = Item.create! | ||
i.attributes = {:created_at => Date.tomorrow, :updated_at => Date.tomorrow} | ||
puts i.change_messages | ||
assert i.change_messages.empty? | ||
end | ||
|
||
def test_unwatching | ||
p = Person.create!(:username => "Robot", :internal_calculation => 1) | ||
p.internal_calculation = 42 | ||
assert p.change_messages.empty? | ||
end | ||
|
||
def test_labeling_attributes | ||
# ensure associations are given the correct name. In this case username has been renamed to 'Name' | ||
u = Person.create!(:username => "Jeremy") | ||
u.username = "Jeremy O" | ||
|
||
assert_equal "Name has changed from \'Jeremy\' to \'Jeremy O\'", u.change_message_for(:username) | ||
end | ||
|
||
def test_associations_loaded | ||
i = Item.create!(:name => "My Cool Task") | ||
assert i.class.custom_dirty_messages[:person_id] | ||
assert_equal :belongs_to, i.class.custom_dirty_messages[:person_id][:type] | ||
end | ||
|
||
def test_display_of_associations | ||
u = Person.create!(:username => "Jeremy") | ||
u2 = Person.create!(:username => "Guy") | ||
i = Item.create!(:name => "My Task", :description => "super", :person => u) | ||
i.person = u2 | ||
|
||
assert_equal "Person has changed from \'Jeremy\' to \'Guy\'", i.change_message_for(:person) | ||
end | ||
|
||
def test_handling_of_nil_attrs | ||
i = Item.create!(:name => "Namae wa", :description => nil) | ||
i.description = "Japanese sentence" | ||
|
||
assert_nothing_raised do | ||
i.change_messages | ||
end | ||
end | ||
|
||
def test_formatting_attributes | ||
i = Item.create!(:name => "Task", :due_on => Date.today) | ||
i.due_on = Date.tomorrow | ||
|
||
today = Date.today.strftime("%d/%m/%Y") | ||
tomorrow = Date.tomorrow.strftime("%d/%m/%Y") | ||
assert_equal "Due Date has been rescheduled from '#{today}' to '#{tomorrow}'", i.change_message_for(:due_on) | ||
end | ||
|
||
def test_full_sentence_changes | ||
p = Person.create!(:username => "Jeremy") | ||
p2 = Person.create!(:username => "Optimus") | ||
i = Item.create!(:name => "My Task", :description => "Nice and easy", :person => p, :due_on => Date.today) | ||
i.attributes = {:person => p2, :description => "This task is now rather long and arduous", :due_on => Date.tomorrow } | ||
|
||
today = Date.today.strftime("%d/%m/%Y") | ||
tomorrow = Date.tomorrow.strftime("%d/%m/%Y") | ||
|
||
assert_equal "Description has changed from 'Nice and easy' to 'This task is now rather long and arduous', Person has changed from 'Jeremy' to 'Optimus', and Due Date has been rescheduled from '#{today}' to '#{tomorrow}'", \ | ||
i.change_messages.to_sentence | ||
end | ||
|
||
end |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
require File.dirname(__FILE__) + '/test_helper.rb' | ||
|
||
class CustomChangeMessagesTest < Test::Unit::TestCase | ||
|
||
class Item < ActiveRecord::Base | ||
end | ||
|
||
def setup | ||
# schema needs to be loaded in the other test, so don't load it here a second time, unless this is run first or isolated | ||
unless Item.connected? | ||
load_schema | ||
end | ||
end | ||
|
||
def test_schema_has_loaded_correctly | ||
assert_nothing_raised do | ||
Item.all | ||
end | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
sqlite3: | ||
adapter: sqlite3 | ||
dbfile: vendor/plugins/custom_change_messages/test/custom_change_messages.sqlite3.db |
Oops, something went wrong.