aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG40
-rwxr-xr-xactiverecord/lib/active_record.rb9
-rw-r--r--activerecord/lib/active_record/mixins/list.rb221
-rw-r--r--activerecord/lib/active_record/mixins/touch.rb34
-rw-r--r--activerecord/lib/active_record/mixins/tree.rb18
-rw-r--r--activerecord/test/fixtures/mixin.rb16
-rw-r--r--activerecord/test/mixin_test.rb111
7 files changed, 220 insertions, 229 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 844dc20b9b..bbbac02af1 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,18 @@
*CVS*
+* Added acts_as_list that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. Example:
+
+ class TodoItem < ActiveRecord::Base
+ acts_as_list :scope => :todo_list_id
+ belongs_to :todo_list
+ end
+
+* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
+ categories and the likes.
+
+* Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
+ created_at/created_on or updated_at/updated_on are present. [Tobias Luetke]
+
* Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place [Tobias Luetke]
* Added automatic transaction block around AssociationCollection.<<, AssociationCollection.delete, and AssociationCollection.destroy_all
@@ -98,32 +111,6 @@
end
end
-* Added ActiveRecord::Mixins::Touch that will record creation and update times of objects [xal]. Example:
-
- class Bill < ActiveRecord::Base
- include ActiveRecord::Mixins::Touch
- end
-
- bill = Bill.create("amount" => 100)
- bill.created_at # => Time.now at the moment of Bill.create
- bill.updated_at # => Time.now at the moment of Bill.create
-
- bill.update_attribute("amount", 150)
- bill.created_at # => Time.now at the moment of Bill.create
- bill.updated_at # => Time.now at the moment of bill.update_attribute
-
-* Added ActiveRecord::Mixins::List that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. Example:
-
- class TodoItem < ActiveRecord::Base
- include ActiveRecord::Mixins::List
- belongs_to :todo_list
-
- private
- def scope_condition
- "todo_list_id = #{todo_list_id}"
- end
- end
-
* Added the option for sanitizing find_by_sql and the offset parts in regular finds [Sam Stephenson]. Examples:
Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20]
@@ -244,6 +231,7 @@
* All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements.
+
*1.1.0* (34)
* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 2ed18b3313..229983503e 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -35,6 +35,9 @@ require 'active_record/associations'
require 'active_record/aggregations'
require 'active_record/transactions'
require 'active_record/reflection'
+require 'active_record/timestamp'
+require 'active_record/mixins/list'
+require 'active_record/mixins/tree'
ActiveRecord::Base.class_eval do
include ActiveRecord::Validations
@@ -43,11 +46,11 @@ ActiveRecord::Base.class_eval do
include ActiveRecord::Aggregations
include ActiveRecord::Transactions
include ActiveRecord::Reflection
+ include ActiveRecord::Timestamp
+ include ActiveRecord::Mixins::Tree
+ include ActiveRecord::Mixins::List
end
-require 'active_record/mixins/list'
-require 'active_record/mixins/touch'
-
require 'active_record/connection_adapters/mysql_adapter'
require 'active_record/connection_adapters/postgresql_adapter'
require 'active_record/connection_adapters/sqlite_adapter'
diff --git a/activerecord/lib/active_record/mixins/list.rb b/activerecord/lib/active_record/mixins/list.rb
index bec4b33651..16a60d7b6c 100644
--- a/activerecord/lib/active_record/mixins/list.rb
+++ b/activerecord/lib/active_record/mixins/list.rb
@@ -28,141 +28,156 @@ module ActiveRecord
# todo_list.last.move_higher
module List
def self.append_features(base)
- super
- base.before_destroy :remove_from_list
- base.after_create :add_to_list_bottom
+ super
+ base.extend(ClassMethods)
end
- # can be overriden
-
- def position_column
- "position"
- end
+ module ClassMethods
+ def acts_as_list(options = {})
+ configuration = { :column => "position", :scope => "1" }
+ configuration.update(options) if options.is_a?(Hash)
+
+ class_eval <<-EOV
+ include InstanceMethods
- # Moving around on the list
+ def position_column
+ '#{configuration[:column]}'
+ end
- def move_lower
- return unless lower_item
-
- self.class.transaction do
- lower_item.decrement_position
- increment_position
+ def scope_condition
+ "#{configuration[:scope]} = \#{#{configuration[:scope]}}"
+ end
+
+ before_destroy :remove_from_list
+ after_create :add_to_list_bottom
+ EOV
end
- end
+
+ module InstanceMethods
+ def move_lower
+ return unless lower_item
- def move_higher
- return unless higher_item
+ self.class.transaction do
+ lower_item.decrement_position
+ increment_position
+ end
+ end
- self.class.transaction do
- higher_item.increment_position
- decrement_position
- end
- end
+ def move_higher
+ return unless higher_item
+
+ self.class.transaction do
+ higher_item.increment_position
+ decrement_position
+ end
+ end
- def move_to_bottom
- self.class.transaction do
- decrement_positions_on_lower_items
- assume_bottom_position
- end
- end
+ def move_to_bottom
+ self.class.transaction do
+ decrement_positions_on_lower_items
+ assume_bottom_position
+ end
+ end
- def move_to_top
- self.class.transaction do
- increment_positions_on_higher_items
- assume_top_position
- end
- end
+ def move_to_top
+ self.class.transaction do
+ increment_positions_on_higher_items
+ assume_top_position
+ end
+ end
- # Entering or existing the list
+ # Entering or existing the list
- def add_to_list_top
- increment_positions_on_all_items
- end
+ def add_to_list_top
+ increment_positions_on_all_items
+ end
- def add_to_list_bottom
- assume_bottom_position
- end
+ def add_to_list_bottom
+ assume_bottom_position
+ end
- def remove_from_list
- decrement_positions_on_lower_items
- end
+ def remove_from_list
+ decrement_positions_on_lower_items
+ end
- # Changing the position
+ # Changing the position
- def increment_position
- update_attribute position_column, self.send(position_column).to_i + 1
- end
+ def increment_position
+ update_attribute position_column, self.send(position_column).to_i + 1
+ end
- def decrement_position
- update_attribute position_column, self.send(position_column).to_i - 1
- end
+ def decrement_position
+ update_attribute position_column, self.send(position_column).to_i - 1
+ end
- # Querying the position
+ # Querying the position
- def first?
- self.send(position_column) == 1
- end
+ def first?
+ self.send(position_column) == 1
+ end
- def last?
- self.send(position_column) == bottom_position_in_list
- end
+ def last?
+ self.send(position_column) == bottom_position_in_list
+ end
- private
- # Overwrite this method to define the scope of the list changes
- def scope_condition() "1" end
+ private
+ # Overwrite this method to define the scope of the list changes
+ def scope_condition() "1" end
- def higher_item
- self.class.find_first(
- "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
- )
- end
+ def higher_item
+ self.class.find_first(
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
+ )
+ end
- def lower_item
- self.class.find_first(
- "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
- )
- end
+ def lower_item
+ self.class.find_first(
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
+ )
+ end
- def bottom_position_in_list
- item = bottom_item
- item ? item.send(position_column) : 0
- end
+ def bottom_position_in_list
+ item = bottom_item
+ item ? item.send(position_column) : 0
+ end
- def bottom_item
- self.class.find_first(
- "#{scope_condition} ",
- "#{position_column} DESC"
- )
- end
+ def bottom_item
+ self.class.find_first(
+ "#{scope_condition} ",
+ "#{position_column} DESC"
+ )
+ end
- def assume_bottom_position
- update_attribute position_column, bottom_position_in_list.to_i + 1
- end
+ def assume_bottom_position
+ update_attribute position_column, bottom_position_in_list.to_i + 1
+ end
- def assume_top_position
- update_attribute position_column, 1
- end
+ def assume_top_position
+ update_attribute position_column, 1
+ end
- def decrement_positions_on_lower_items
- self.class.update_all(
- "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
- )
- end
+ def decrement_positions_on_lower_items
+ self.class.update_all(
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
+ )
+ end
- def increment_positions_on_higher_items
- self.class.update_all(
- "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column)}"
- )
- end
+ def increment_positions_on_higher_items
+ self.class.update_all(
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column)}"
+ )
+ end
- def increment_positions_on_all_items
- self.class.update_all(
- "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
- )
- end
+ def increment_positions_on_all_items
+ self.class.update_all(
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
+ )
+ end
+ end
+ end
end
end
end \ No newline at end of file
diff --git a/activerecord/lib/active_record/mixins/touch.rb b/activerecord/lib/active_record/mixins/touch.rb
deleted file mode 100644
index baf217542a..0000000000
--- a/activerecord/lib/active_record/mixins/touch.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module ActiveRecord
- module Mixins
- # Including this mixins will record when objects of the class are created in a datetime column called "created_at"
- # and when its updated in another datetime column called "updated_at".
- #
- # class Bill < ActiveRecord::Base
- # include ActiveRecord::Mixins::Touch
- # end
- #
- # bill = Bill.create("amount" => 100)
- # bill.created_at # => Time.now at the moment of Bill.create
- # bill.updated_at # => Time.now at the moment of Bill.create
- #
- # bill.update_attribute("amount", 150)
- # bill.created_at # => Time.now at the moment of Bill.create
- # bill.updated_at # => Time.now at the moment of bill.update_attribute
- module Touch
- def self.append_features(base)
- super
-
- base.before_create :touch_on_create
- base.before_update :touch_on_update
- end
-
- def touch_on_create
- self.updated_at = (self.created_at ||= Time.now)
- end
-
- def touch_on_update
- self.updated_at = Time.now
- end
- end
- end
-end \ No newline at end of file
diff --git a/activerecord/lib/active_record/mixins/tree.rb b/activerecord/lib/active_record/mixins/tree.rb
index 582aa09b4b..141bbb1806 100644
--- a/activerecord/lib/active_record/mixins/tree.rb
+++ b/activerecord/lib/active_record/mixins/tree.rb
@@ -21,16 +21,20 @@ module ActiveRecord
# root.children # => [child1]
# root.children.first.children.first # => subchild1
module Tree
-
def self.append_features(base)
super
-
- base.module_eval <<-associations
- belongs_to :parent, :class_name => name, :foreign_key => "parent_id"
- has_many :children, :class_name => name, :foreign_key => "parent_id", :order => "id", :dependent => true
- associations
-
+ base.extend(ClassMethods)
end
end
+
+ module ClassMethods
+ def acts_as_tree(options = {})
+ configuration = { :foreign_key => "parent_id", :order => nil }
+ configuration.update(options) if options.is_a?(Hash)
+
+ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key]
+ has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => true
+ end
+ end
end
end \ No newline at end of file
diff --git a/activerecord/test/fixtures/mixin.rb b/activerecord/test/fixtures/mixin.rb
index 589c1a8c23..8cae604140 100644
--- a/activerecord/test/fixtures/mixin.rb
+++ b/activerecord/test/fixtures/mixin.rb
@@ -1,21 +1,13 @@
class Mixin < ActiveRecord::Base
- include ActiveRecord::Mixins::Touch
- include ActiveRecord::Mixins::Tree
+ acts_as_tree :foreign_key => "parent_id", :order => "id"
+
end
class ListMixin < ActiveRecord::Base
- include ActiveRecord::Mixins::List
-
+ acts_as_list :column => "pos", :scope => :parent_id
+
def self.table_name
"mixins"
end
- def scope_condition
- "parent_id = #{self.parent_id}"
- end
-
- def position_column
- "pos"
- end
-
end \ No newline at end of file
diff --git a/activerecord/test/mixin_test.rb b/activerecord/test/mixin_test.rb
index cc56bc7cec..31e71f5d6c 100644
--- a/activerecord/test/mixin_test.rb
+++ b/activerecord/test/mixin_test.rb
@@ -4,6 +4,73 @@ require 'active_record/mixins/list'
require 'active_record/mixins/touch'
require 'fixtures/mixin'
+
+class ListTest < Test::Unit::TestCase
+ fixtures :mixins
+
+ def test_injection
+ l = ListMixin.new("parent_id"=>1)
+ assert_equal "parent_id = 1", l.scope_condition
+ assert_equal "pos", l.position_column
+
+ end
+
+ def test_reordering
+
+ assert_equal [ListMixin.find(2), ListMixin.find(3)], ListMixin.find_all("parent_id=1", "pos")
+
+ ListMixin.find(2).move_lower
+
+ assert_equal [ListMixin.find(3), ListMixin.find(2)], ListMixin.find_all("parent_id=1", "pos")
+
+ ListMixin.find(2).move_higher
+
+ assert_equal [ListMixin.find(2), ListMixin.find(3)], ListMixin.find_all("parent_id=1", "pos")
+
+ end
+
+ def test_insert
+ new = ListMixin.create("parent_id"=>5)
+ assert_equal 1, new.pos
+ assert new.first?
+ assert new.last?
+
+ new = ListMixin.create("parent_id"=>5)
+ assert_equal 2, new.pos
+ assert !new.first?
+ assert new.last?
+
+ new = ListMixin.create("parent_id"=>5)
+ assert_equal 3, new.pos
+ assert !new.first?
+ assert new.last?
+
+ new = ListMixin.create("parent_id"=>10)
+ assert_equal 1, new.pos
+ assert new.first?
+ assert new.last?
+ end
+
+
+ def test_delete_middle
+ first = ListMixin.create("parent_id"=>5)
+ assert_equal 1, first.pos
+
+ second = ListMixin.create("parent_id"=>5)
+ assert_equal 2, second.pos
+
+ third = ListMixin.create("parent_id"=>5)
+ assert_equal 3, third.pos
+
+ second.destroy
+
+ assert_equal 1, @mixins["first"].find.pos
+ assert_equal 2, @mixins["third"].find.pos
+
+ end
+
+end
+
class TreeTest < Test::Unit::TestCase
fixtures :mixins
@@ -54,48 +121,4 @@ class TouchTest < Test::Unit::TestCase
assert_not_nil @obj.updated_at
assert_not_nil @obj.created_at
end
-
-
end
-
-
-class ListTest < Test::Unit::TestCase
- fixtures :mixins
-
- def test_reordering
-
- assert_equal [ListMixin.find(2), ListMixin.find(3)], ListMixin.find_all("parent_id=1", "pos")
-
- ListMixin.find(2).move_lower
-
- assert_equal [ListMixin.find(3), ListMixin.find(2)], ListMixin.find_all("parent_id=1", "pos")
-
- ListMixin.find(2).move_higher
-
- assert_equal [ListMixin.find(2), ListMixin.find(3)], ListMixin.find_all("parent_id=1", "pos")
-
- end
-
- def test_insert
- new = ListMixin.create("parent_id"=>3)
- assert_equal 1, new.pos
- assert new.first?
- assert new.last?
-
- new = ListMixin.create("parent_id"=>3)
- assert_equal 2, new.pos
- assert !new.first?
- assert new.last?
-
- new = ListMixin.create("parent_id"=>3)
- assert_equal 3, new.pos
- assert !new.first?
- assert new.last?
-
- new = ListMixin.create("parent_id"=>2)
- assert_equal 1, new.pos
- assert new.first?
- assert new.last?
-
- end
-end \ No newline at end of file