From 95314be65be197b6c38c8c93e3f8d1e8b5b0b674 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 15 Dec 2004 00:46:26 +0000 Subject: Added tree mixin and unit tests for all the mixins git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@156 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/lib/active_record/fixtures.rb | 7 +- activerecord/lib/active_record/mixins/list.rb | 32 ++++--- activerecord/lib/active_record/mixins/tree.rb | 36 ++++++++ .../test/fixtures/db_definitions/mysql.sql | 12 +++ .../test/fixtures/db_definitions/postgresql.sql | 12 +++ .../test/fixtures/db_definitions/sqlite.sql | 13 +++ .../test/fixtures/db_definitions/sqlserver.sql | 16 +++- activerecord/test/fixtures/mixin.rb | 21 +++++ activerecord/test/fixtures/mixins.yml | 14 +++ activerecord/test/fixtures/naked/csv/accounts.csv | 1 + activerecord/test/fixtures/naked/yml/accounts.yml | 1 + activerecord/test/fixtures/naked/yml/companies.yml | 1 + activerecord/test/fixtures/naked/yml/courses.yml | 1 + activerecord/test/fixtures_test.rb | 19 ++++ activerecord/test/mixin_test.rb | 101 +++++++++++++++++++++ 15 files changed, 271 insertions(+), 16 deletions(-) create mode 100644 activerecord/lib/active_record/mixins/tree.rb create mode 100644 activerecord/test/fixtures/mixin.rb create mode 100644 activerecord/test/fixtures/mixins.yml create mode 100644 activerecord/test/fixtures/naked/csv/accounts.csv create mode 100644 activerecord/test/fixtures/naked/yml/accounts.yml create mode 100644 activerecord/test/fixtures/naked/yml/companies.yml create mode 100644 activerecord/test/fixtures/naked/yml/courses.yml create mode 100644 activerecord/test/mixin_test.rb diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 8447a33c7d..09d0f46390 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -193,8 +193,11 @@ class Fixtures < Hash def read_fixture_files if File.file?(yaml_file_path) # YAML fixtures - YAML::load(erb_render(IO.read(yaml_file_path))).each do |name, data| - self[name] = Fixture.new(data, @class_name) + begin + yaml = YAML::load(erb_render(IO.read(yaml_file_path))) + yaml.each { |name, data| self[name] = Fixture.new(data, @class_name) } if yaml + rescue Exception=>boom + raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}" end elsif File.file?(csv_file_path) # CSV fixtures diff --git a/activerecord/lib/active_record/mixins/list.rb b/activerecord/lib/active_record/mixins/list.rb index 6910ac771f..bec4b33651 100644 --- a/activerecord/lib/active_record/mixins/list.rb +++ b/activerecord/lib/active_record/mixins/list.rb @@ -32,6 +32,12 @@ module ActiveRecord base.before_destroy :remove_from_list base.after_create :add_to_list_bottom end + + # can be overriden + + def position_column + "position" + end # Moving around on the list @@ -86,22 +92,22 @@ module ActiveRecord # Changing the position def increment_position - update_attribute "position", position.to_i + 1 + update_attribute position_column, self.send(position_column).to_i + 1 end def decrement_position - update_attribute "position", position.to_i - 1 + update_attribute position_column, self.send(position_column).to_i - 1 end # Querying the position def first? - self.position == 1 + self.send(position_column) == 1 end def last? - self.position == bottom_position_in_list + self.send(position_column) == bottom_position_in_list end private @@ -110,51 +116,51 @@ module ActiveRecord def higher_item self.class.find_first( - "#{scope_condition} AND position = #{(position.to_i - 1).to_s}" + "#{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 = #{(position.to_i + 1).to_s}" + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" ) end def bottom_position_in_list item = bottom_item - item ? item.position : 0 + item ? item.send(position_column) : 0 end def bottom_item self.class.find_first( "#{scope_condition} ", - "position DESC" + "#{position_column} DESC" ) end def assume_bottom_position - update_attribute "position", bottom_position_in_list.to_i + 1 + update_attribute position_column, bottom_position_in_list.to_i + 1 end def assume_top_position - update_attribute "position", 1 + update_attribute position_column, 1 end def decrement_positions_on_lower_items self.class.update_all( - "position = (position - 1)", "#{scope_condition} AND position > #{position.to_i}" + "#{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 = (position + 1)", "#{scope_condition} AND position < #{position.to_i}" + "#{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 = (position + 1)", "#{scope_condition}" + "#{position_column} = (#{position_column} + 1)", "#{scope_condition}" ) end end diff --git a/activerecord/lib/active_record/mixins/tree.rb b/activerecord/lib/active_record/mixins/tree.rb new file mode 100644 index 0000000000..582aa09b4b --- /dev/null +++ b/activerecord/lib/active_record/mixins/tree.rb @@ -0,0 +1,36 @@ +module ActiveRecord + module Mixins + # Including this mixin if you want to model a tree structure by providing a parent association and an children + # association. This mixin assumes that you have a column called parent_id + # + # class Category < ActiveRecord::Base + # include ActiveRecord::Mixins::Tree + # end + # + # Example : + # root + # \_ child1 + # \_ sub-child1 + # + # root = Category.create("name" => "root") + # child1 = root.children.create("name" => "child1") + # subchild1 = child1.children.create("name" => "subchild1") + # + # root.parent # => nil + # child1.parent # => root + # 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 + + end + end + end +end \ No newline at end of file diff --git a/activerecord/test/fixtures/db_definitions/mysql.sql b/activerecord/test/fixtures/db_definitions/mysql.sql index 0b96c49dd3..6af00eb7f0 100755 --- a/activerecord/test/fixtures/db_definitions/mysql.sql +++ b/activerecord/test/fixtures/db_definitions/mysql.sql @@ -96,3 +96,15 @@ CREATE TABLE `colnametests` ( `references` int(11) NOT NULL, PRIMARY KEY (`id`) ); + +CREATE TABLE `mixins` ( + `id` int(11) NOT NULL auto_increment, + `parent_id` int(11) default NULL, + `pos` int(11) default NULL, + `lft` int(11) default NULL, + `rgt` int(11) default NULL, + `root_id` int(11) default NULL, + `created_at` datetime default NULL, + `updated_at` datetime default NULL, + PRIMARY KEY (`id`) +); diff --git a/activerecord/test/fixtures/db_definitions/postgresql.sql b/activerecord/test/fixtures/db_definitions/postgresql.sql index 87db0b7f3f..9bf1035ea3 100644 --- a/activerecord/test/fixtures/db_definitions/postgresql.sql +++ b/activerecord/test/fixtures/db_definitions/postgresql.sql @@ -113,3 +113,15 @@ CREATE TABLE colnametests ( id serial, "references" integer NOT NULL ); + +CREATE TABLE mixins ( + id serial, + parent_id integer, + pos integer, + lft integer, + rgt integer, + root_id integer, + created_at timestamp, + updated_at timestamp, + PRIMARY KEY (`id`) +); diff --git a/activerecord/test/fixtures/db_definitions/sqlite.sql b/activerecord/test/fixtures/db_definitions/sqlite.sql index 91ca172789..9863fc779f 100644 --- a/activerecord/test/fixtures/db_definitions/sqlite.sql +++ b/activerecord/test/fixtures/db_definitions/sqlite.sql @@ -85,3 +85,16 @@ CREATE TABLE 'colnametests' ( 'id' INTEGER NOT NULL PRIMARY KEY, 'references' INTEGER NOT NULL ); + +CREATE TABLE 'mixins' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'parent_id' INTEGER DEFAULT NULL, + 'pos' INTEGER DEFAULT NULL, + 'lft' INTEGER DEFAULT NULL, + 'rgt' INTEGER DEFAULT NULL, + 'root_id' INTEGER DEFAULT NULL, + 'created_at' DATETIME DEFAULT NULL, + 'updated_at' DATETIME DEFAULT NULL +); + + diff --git a/activerecord/test/fixtures/db_definitions/sqlserver.sql b/activerecord/test/fixtures/db_definitions/sqlserver.sql index 0ae9780273..555ff00f90 100644 --- a/activerecord/test/fixtures/db_definitions/sqlserver.sql +++ b/activerecord/test/fixtures/db_definitions/sqlserver.sql @@ -93,4 +93,18 @@ CREATE TABLE colnametests ( id int NOT NULL IDENTITY(1, 1), [references] int NOT NULL, PRIMARY KEY (id) -); \ No newline at end of file +); + +CREATE TABLE mixins ( + id int NOT NULL IDENTITY(1, 1), + parent_id int default NULL, + pos int default NULL, + lft int default NULL, + rgt int default NULL, + root_id int default NULL, + created_at datetime default NULL, + updated_at datetime default NULL, + PRIMARY KEY (id) +); + + diff --git a/activerecord/test/fixtures/mixin.rb b/activerecord/test/fixtures/mixin.rb new file mode 100644 index 0000000000..589c1a8c23 --- /dev/null +++ b/activerecord/test/fixtures/mixin.rb @@ -0,0 +1,21 @@ +class Mixin < ActiveRecord::Base + include ActiveRecord::Mixins::Touch + include ActiveRecord::Mixins::Tree +end + +class ListMixin < ActiveRecord::Base + include ActiveRecord::Mixins::List + + 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/fixtures/mixins.yml b/activerecord/test/fixtures/mixins.yml new file mode 100644 index 0000000000..c74b1da51b --- /dev/null +++ b/activerecord/test/fixtures/mixins.yml @@ -0,0 +1,14 @@ +first: + id: 1 + pos: 1 + parent_id: 0 + +second: + id: 2 + pos: 1 + parent_id: 1 + +third: + id: 3 + pos: 2 + parent_id: 1 \ No newline at end of file diff --git a/activerecord/test/fixtures/naked/csv/accounts.csv b/activerecord/test/fixtures/naked/csv/accounts.csv new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/activerecord/test/fixtures/naked/csv/accounts.csv @@ -0,0 +1 @@ + diff --git a/activerecord/test/fixtures/naked/yml/accounts.yml b/activerecord/test/fixtures/naked/yml/accounts.yml new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/activerecord/test/fixtures/naked/yml/accounts.yml @@ -0,0 +1 @@ + diff --git a/activerecord/test/fixtures/naked/yml/companies.yml b/activerecord/test/fixtures/naked/yml/companies.yml new file mode 100644 index 0000000000..2c151c203b --- /dev/null +++ b/activerecord/test/fixtures/naked/yml/companies.yml @@ -0,0 +1 @@ +# i wonder what will happen here diff --git a/activerecord/test/fixtures/naked/yml/courses.yml b/activerecord/test/fixtures/naked/yml/courses.yml new file mode 100644 index 0000000000..19f0805d8d --- /dev/null +++ b/activerecord/test/fixtures/naked/yml/courses.yml @@ -0,0 +1 @@ +qwerty diff --git a/activerecord/test/fixtures_test.rb b/activerecord/test/fixtures_test.rb index a971b9f6f8..4c450eb3cf 100755 --- a/activerecord/test/fixtures_test.rb +++ b/activerecord/test/fixtures_test.rb @@ -87,4 +87,23 @@ class FixturesTest < Test::Unit::TestCase assert_equal 10, @developers.size assert_equal "fixture_5", @dev_5.name end + + def test_empty_yaml_fixture + assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/yml/accounts") + end + + def test_empty_yaml_fixture_with_a_comment_in_it + assert_not_nil Fixtures.new( Account.connection, "companies", File.dirname(__FILE__) + "/fixtures/naked/yml/companies") + end + + def test_dirty_dirty_yaml_file + assert_raises(Fixture::FormatError) do + Fixtures.new( Account.connection, "courses", File.dirname(__FILE__) + "/fixtures/naked/yml/courses") + end + end + + def test_empty_csv_fixtures + assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/csv/accounts") + end + end diff --git a/activerecord/test/mixin_test.rb b/activerecord/test/mixin_test.rb new file mode 100644 index 0000000000..cc56bc7cec --- /dev/null +++ b/activerecord/test/mixin_test.rb @@ -0,0 +1,101 @@ +require 'abstract_unit' +require 'active_record/mixins/tree' +require 'active_record/mixins/list' +require 'active_record/mixins/touch' +require 'fixtures/mixin' + +class TreeTest < Test::Unit::TestCase + fixtures :mixins + + def test_has_child + assert_equal true, @first.has_children? + assert_equal false, @second.has_children? + assert_equal false, @third.has_children? + end + + def test_children + assert_equal @first.children, [@second, @third] + assert_equal @second.children, [] + end + + def test_parent + assert_equal @second.parent, @first + assert_equal @second.parent, @third.parent + assert_nil @first.parent + end + + def test_insert + @extra = @first.children.create + + assert @extra + + assert_equal @extra.parent, @first + assert_equal [@second, @third, @extra], @first.children + end + + def test_delete + assert_equal 3, Mixin.count + @first.destroy + assert_equal 0, Mixin.count + end +end + +class TouchTest < Test::Unit::TestCase + fixtures :mixins + + def test_update + assert_nil @first.updated_at + @first.save + assert_not_nil @first.updated_at + end + + def test_create + @obj = Mixin.create({"parent" => @third}) + 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 -- cgit v1.2.3