activerecord 1.14.4 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
data/Rakefile CHANGED
@@ -27,10 +27,14 @@ task :default => [ :test_mysql, :test_sqlite, :test_postgresql ]
27
27
 
28
28
  # Run the unit tests
29
29
 
30
- for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase )
30
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase frontbase )
31
31
  Rake::TestTask.new("test_#{adapter}") { |t|
32
32
  t.libs << "test" << "test/connections/native_#{adapter}"
33
- t.pattern = "test/*_test{,_#{adapter}}.rb"
33
+ if adapter =~ /^sqlserver/
34
+ t.pattern = "test/**/*_test{,_sqlserver}.rb"
35
+ else
36
+ t.pattern = "test/**/*_test{,_#{adapter}}.rb"
37
+ end
34
38
  t.verbose = true
35
39
  }
36
40
  end
@@ -41,6 +45,8 @@ desc 'Build the MySQL test databases'
41
45
  task :build_mysql_databases do
42
46
  %x( mysqladmin create activerecord_unittest )
43
47
  %x( mysqladmin create activerecord_unittest2 )
48
+ %x( mysql -e "grant all on activerecord_unittest.* to rails@localhost" )
49
+ %x( mysql -e "grant all on activerecord_unittest2.* to rails@localhost" )
44
50
  %x( mysql activerecord_unittest < #{File.join(SCHEMA_PATH, 'mysql.sql')} )
45
51
  %x( mysql activerecord_unittest < #{File.join(SCHEMA_PATH, 'mysql2.sql')} )
46
52
  end
@@ -56,21 +62,60 @@ task :rebuild_mysql_databases => [:drop_mysql_databases, :build_mysql_databases]
56
62
 
57
63
  desc 'Build the PostgreSQL test databases'
58
64
  task :build_postgresql_databases do
59
- %x( createdb activerecord_unittest )
60
- %x( createdb activerecord_unittest2 )
61
- %x( psql activerecord_unittest -f #{File.join(SCHEMA_PATH, 'postgresql.sql')} )
62
- %x( psql activerecord_unittest2 -f #{File.join(SCHEMA_PATH, 'postgresql2.sql')} )
65
+ %x( createdb -U postgres activerecord_unittest )
66
+ %x( createdb -U postgres activerecord_unittest2 )
67
+ %x( psql activerecord_unittest -f #{File.join(SCHEMA_PATH, 'postgresql.sql')} postgres )
68
+ %x( psql activerecord_unittest2 -f #{File.join(SCHEMA_PATH, 'postgresql2.sql')} postgres )
63
69
  end
64
70
 
65
71
  desc 'Drop the PostgreSQL test databases'
66
72
  task :drop_postgresql_databases do
67
- %x( dropdb activerecord_unittest )
68
- %x( dropdb activerecord_unittest2 )
73
+ %x( dropdb -U postgres activerecord_unittest )
74
+ %x( dropdb -U postgres activerecord_unittest2 )
69
75
  end
70
76
 
71
77
  desc 'Rebuild the PostgreSQL test databases'
72
78
  task :rebuild_postgresql_databases => [:drop_postgresql_databases, :build_postgresql_databases]
73
79
 
80
+ desc 'Build the FrontBase test databases'
81
+ task :build_frontbase_databases => :rebuild_frontbase_databases
82
+
83
+ desc 'Rebuild the FrontBase test databases'
84
+ task :rebuild_frontbase_databases do
85
+ build_frontbase_database = Proc.new do |db_name, sql_definition_file|
86
+ %(
87
+ STOP DATABASE #{db_name};
88
+ DELETE DATABASE #{db_name};
89
+ CREATE DATABASE #{db_name};
90
+
91
+ CONNECT TO #{db_name} AS SESSION_NAME USER _SYSTEM;
92
+ SET COMMIT FALSE;
93
+
94
+ CREATE USER RAILS;
95
+ CREATE SCHEMA RAILS AUTHORIZATION RAILS;
96
+ COMMIT;
97
+
98
+ SET SESSION AUTHORIZATION RAILS;
99
+ SCRIPT '#{sql_definition_file}';
100
+
101
+ COMMIT;
102
+
103
+ DISCONNECT ALL;
104
+ )
105
+ end
106
+ create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_PATH, 'frontbase.sql')]
107
+ create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_PATH, 'frontbase2.sql')]
108
+ execute_frontbase_sql = Proc.new do |sql|
109
+ system(<<-SHELL)
110
+ /Library/FrontBase/bin/sql92 <<-SQL
111
+ #{sql}
112
+ SQL
113
+ SHELL
114
+ end
115
+ execute_frontbase_sql[create_activerecord_unittest]
116
+ execute_frontbase_sql[create_activerecord_unittest2]
117
+ end
118
+
74
119
  # Generate the RDoc documentation
75
120
 
76
121
  Rake::RDocTask.new { |rdoc|
@@ -106,7 +151,7 @@ spec = Gem::Specification.new do |s|
106
151
  s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
107
152
  end
108
153
 
109
- s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD)
154
+ s.add_dependency('activesupport', '= 1.4.0' + PKG_BUILD)
110
155
 
111
156
  s.files.delete "test/fixtures/fixture_database.sqlite"
112
157
  s.files.delete "test/fixtures/fixture_database_2.sqlite"
@@ -178,4 +223,4 @@ task :release => [ :package ] do
178
223
  puts release_command
179
224
  system(release_command)
180
225
  end
181
- end
226
+ end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2006 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -46,14 +46,18 @@ require 'active_record/timestamp'
46
46
  require 'active_record/acts/list'
47
47
  require 'active_record/acts/tree'
48
48
  require 'active_record/acts/nested_set'
49
- require 'active_record/locking'
49
+ require 'active_record/locking/optimistic'
50
+ require 'active_record/locking/pessimistic'
50
51
  require 'active_record/migration'
51
52
  require 'active_record/schema'
52
53
  require 'active_record/calculations'
54
+ require 'active_record/xml_serialization'
55
+ require 'active_record/attribute_methods'
53
56
 
54
57
  ActiveRecord::Base.class_eval do
55
58
  include ActiveRecord::Validations
56
- include ActiveRecord::Locking
59
+ include ActiveRecord::Locking::Optimistic
60
+ include ActiveRecord::Locking::Pessimistic
57
61
  include ActiveRecord::Callbacks
58
62
  include ActiveRecord::Observing
59
63
  include ActiveRecord::Timestamp
@@ -65,10 +69,12 @@ ActiveRecord::Base.class_eval do
65
69
  include ActiveRecord::Acts::List
66
70
  include ActiveRecord::Acts::NestedSet
67
71
  include ActiveRecord::Calculations
72
+ include ActiveRecord::XmlSerialization
73
+ include ActiveRecord::AttributeMethods
68
74
  end
69
75
 
70
76
  unless defined?(RAILS_CONNECTION_ADAPTERS)
71
- RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase )
77
+ RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase )
72
78
  end
73
79
 
74
80
  RAILS_CONNECTION_ADAPTERS.each do |adapter|
@@ -1,8 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts #:nodoc:
3
3
  module List #:nodoc:
4
- def self.append_features(base)
5
- super
4
+ def self.included(base)
6
5
  base.extend(ClassMethods)
7
6
  end
8
7
 
@@ -78,7 +77,8 @@ module ActiveRecord
78
77
  def insert_at(position = 1)
79
78
  insert_at_position(position)
80
79
  end
81
-
80
+
81
+ # Swap positions with the next lower item, if one exists.
82
82
  def move_lower
83
83
  return unless lower_item
84
84
 
@@ -88,6 +88,7 @@ module ActiveRecord
88
88
  end
89
89
  end
90
90
 
91
+ # Swap positions with the next higher item, if one exists.
91
92
  def move_higher
92
93
  return unless higher_item
93
94
 
@@ -97,6 +98,8 @@ module ActiveRecord
97
98
  end
98
99
  end
99
100
 
101
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
102
+ # position adjusted accordingly.
100
103
  def move_to_bottom
101
104
  return unless in_list?
102
105
  acts_as_list_class.transaction do
@@ -105,6 +108,8 @@ module ActiveRecord
105
108
  end
106
109
  end
107
110
 
111
+ # Move to the top of the list. If the item is already in the list, the items above it have their
112
+ # position adjusted accordingly.
108
113
  def move_to_top
109
114
  return unless in_list?
110
115
  acts_as_list_class.transaction do
@@ -112,31 +117,36 @@ module ActiveRecord
112
117
  assume_top_position
113
118
  end
114
119
  end
115
-
120
+
116
121
  def remove_from_list
117
122
  decrement_positions_on_lower_items if in_list?
118
123
  end
119
124
 
125
+ # Increase the position of this item without adjusting the rest of the list.
120
126
  def increment_position
121
127
  return unless in_list?
122
128
  update_attribute position_column, self.send(position_column).to_i + 1
123
129
  end
124
130
 
131
+ # Decrease the position of this item without adjusting the rest of the list.
125
132
  def decrement_position
126
133
  return unless in_list?
127
134
  update_attribute position_column, self.send(position_column).to_i - 1
128
135
  end
129
136
 
137
+ # Return true if this object is the first in the list.
130
138
  def first?
131
139
  return false unless in_list?
132
140
  self.send(position_column) == 1
133
141
  end
134
142
 
143
+ # Return true if this object is the last in the list.
135
144
  def last?
136
145
  return false unless in_list?
137
146
  self.send(position_column) == bottom_position_in_list
138
147
  end
139
148
 
149
+ # Return the next higher item in the list.
140
150
  def higher_item
141
151
  return nil unless in_list?
142
152
  acts_as_list_class.find(:first, :conditions =>
@@ -144,6 +154,7 @@ module ActiveRecord
144
154
  )
145
155
  end
146
156
 
157
+ # Return the next lower item in the list.
147
158
  def lower_item
148
159
  return nil unless in_list?
149
160
  acts_as_list_class.find(:first, :conditions =>
@@ -1,8 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Acts #:nodoc:
3
3
  module NestedSet #:nodoc:
4
- def self.append_features(base)
5
- super
4
+ def self.included(base)
6
5
  base.extend(ClassMethods)
7
6
  end
8
7
 
@@ -164,9 +163,9 @@ module ActiveRecord
164
163
  child[left_col_name] = right_bound
165
164
  child[right_col_name] = right_bound + 1
166
165
  self[right_col_name] += 2
167
- self.class.transaction {
168
- self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
169
- self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
166
+ self.class.base_class.transaction {
167
+ self.class.base_class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
168
+ self.class.base_class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
170
169
  self.save
171
170
  child.save
172
171
  }
@@ -181,17 +180,17 @@ module ActiveRecord
181
180
 
182
181
  # Returns a set of itself and all of its nested children
183
182
  def full_set
184
- self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
183
+ self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
185
184
  end
186
185
 
187
186
  # Returns a set of all of its children and nested children
188
187
  def all_children
189
- self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
188
+ self.class.base_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
190
189
  end
191
190
 
192
191
  # Returns a set of only this entry's immediate children
193
192
  def direct_children
194
- self.class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}")
193
+ self.class.base_class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}")
195
194
  end
196
195
 
197
196
  # Prunes a branch off of the tree, shifting all of the elements on the right
@@ -200,10 +199,10 @@ module ActiveRecord
200
199
  return if self[right_col_name].nil? || self[left_col_name].nil?
201
200
  dif = self[right_col_name] - self[left_col_name] + 1
202
201
 
203
- self.class.transaction {
204
- self.class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
205
- self.class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})", "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
206
- self.class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )", "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
202
+ self.class.base_class.transaction {
203
+ self.class.base_class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
204
+ self.class.base_class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})", "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
205
+ self.class.base_class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )", "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
207
206
  }
208
207
  end
209
208
  end
@@ -1,21 +1,20 @@
1
1
  module ActiveRecord
2
2
  module Acts #:nodoc:
3
3
  module Tree #:nodoc:
4
- def self.append_features(base)
5
- super
6
- base.extend(ClassMethods)
7
- end
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
8
7
 
9
- # Specify this act if you want to model a tree structure by providing a parent association and a children
8
+ # Specify this act if you want to model a tree structure by providing a parent association and a children
10
9
  # association. This act requires that you have a foreign key column, which by default is called parent_id.
11
- #
10
+ #
12
11
  # class Category < ActiveRecord::Base
13
12
  # acts_as_tree :order => "name"
14
13
  # end
15
- #
16
- # Example :
14
+ #
15
+ # Example:
17
16
  # root
18
- # \_ child1
17
+ # \_ child1
19
18
  # \_ subchild1
20
19
  # \_ subchild2
21
20
  #
@@ -28,7 +27,7 @@ module ActiveRecord
28
27
  # root.children # => [child1]
29
28
  # root.children.first.children.first # => subchild1
30
29
  #
31
- # In addition to the parent and children associations, the following instance methods are added to the class
30
+ # In addition to the parent and children associations, the following instance methods are added to the class
32
31
  # after specifying the act:
33
32
  # * siblings : Returns all the children of the parent, excluding the current node ([ subchild2 ] when called from subchild1)
34
33
  # * self_and_siblings : Returns all the children of the parent, including the current node ([ subchild1, subchild2 ] when called from subchild1)
@@ -49,7 +48,7 @@ module ActiveRecord
49
48
 
50
49
  class_eval <<-EOV
51
50
  include ActiveRecord::Acts::Tree::InstanceMethods
52
-
51
+
53
52
  def self.roots
54
53
  find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
55
54
  end
@@ -67,13 +66,13 @@ module ActiveRecord
67
66
  # subchild1.ancestors # => [child1, root]
68
67
  def ancestors
69
68
  node, nodes = self, []
70
- nodes << node = node.parent until not node.has_parent?
69
+ nodes << node = node.parent while node.parent
71
70
  nodes
72
71
  end
73
72
 
74
73
  def root
75
74
  node = self
76
- node = node.parent until not node.has_parent?
75
+ node = node.parent while node.parent
77
76
  node
78
77
  end
79
78
 
@@ -82,7 +81,7 @@ module ActiveRecord
82
81
  end
83
82
 
84
83
  def self_and_siblings
85
- has_parent? ? parent.children : self.class.roots
84
+ parent ? parent.children : self.class.roots
86
85
  end
87
86
  end
88
87
  end
@@ -109,8 +109,8 @@ module ActiveRecord
109
109
  # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
110
110
  # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
111
111
  module ClassMethods
112
- # Adds the a reader and writer method for manipulating a value object, so
113
- # <tt>composed_of :address</tt> would add <tt>address</tt> and <tt>address=(new_address)</tt>.
112
+ # Adds reader and writer methods for manipulating a value object:
113
+ # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
114
114
  #
115
115
  # Options are:
116
116
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
@@ -118,49 +118,73 @@ module ActiveRecord
118
118
  # if the real class name is +CompanyAddress+, you'll have to specify it with this option.
119
119
  # * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
120
120
  # to a constructor parameter on the value class.
121
+ # * <tt>:allow_nil</tt> - specifies that the aggregate object will not be instantiated when all mapped
122
+ # attributes are nil. Setting the aggregate class to nil has the effect of writing nil to all mapped attributes.
123
+ # This defaults to false.
121
124
  #
122
125
  # Option examples:
123
126
  # composed_of :temperature, :mapping => %w(reading celsius)
124
127
  # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
125
128
  # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
126
129
  # composed_of :gps_location
130
+ # composed_of :gps_location, :allow_nil => true
131
+ #
127
132
  def composed_of(part_id, options = {})
128
- options.assert_valid_keys(:class_name, :mapping)
133
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil)
129
134
 
130
135
  name = part_id.id2name
131
- class_name = options[:class_name] || name_to_class_name(name)
132
- mapping = options[:mapping] || [ name, name ]
136
+ class_name = options[:class_name] || name.camelize
137
+ mapping = options[:mapping] || [ name, name ]
138
+ allow_nil = options[:allow_nil] || false
133
139
 
134
- reader_method(name, class_name, mapping)
135
- writer_method(name, class_name, mapping)
140
+ reader_method(name, class_name, mapping, allow_nil)
141
+ writer_method(name, class_name, mapping, allow_nil)
136
142
 
137
143
  create_reflection(:composed_of, part_id, options, self)
138
144
  end
139
145
 
140
146
  private
141
- def name_to_class_name(name)
142
- name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
143
- end
144
-
145
- def reader_method(name, class_name, mapping)
147
+ def reader_method(name, class_name, mapping, allow_nil)
148
+ mapping = (Array === mapping.first ? mapping : [ mapping ])
149
+
150
+ allow_nil_condition = if allow_nil
151
+ mapping.collect { |pair| "!read_attribute(\"#{pair.first}\").nil?"}.join(" && ")
152
+ else
153
+ "true"
154
+ end
155
+
146
156
  module_eval <<-end_eval
147
157
  def #{name}(force_reload = false)
148
- if @#{name}.nil? || force_reload
149
- @#{name} = #{class_name}.new(#{(Array === mapping.first ? mapping : [ mapping ]).collect{ |pair| "read_attribute(\"#{pair.first}\")"}.join(", ")})
158
+ if (@#{name}.nil? || force_reload) && #{allow_nil_condition}
159
+ @#{name} = #{class_name}.new(#{mapping.collect { |pair| "read_attribute(\"#{pair.first}\")"}.join(", ")})
150
160
  end
151
-
152
161
  return @#{name}
153
162
  end
154
163
  end_eval
155
164
  end
156
165
 
157
- def writer_method(name, class_name, mapping)
158
- module_eval <<-end_eval
159
- def #{name}=(part)
160
- @#{name} = part.freeze
161
- #{(Array === mapping.first ? mapping : [ mapping ]).collect{ |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")}
162
- end
163
- end_eval
166
+ def writer_method(name, class_name, mapping, allow_nil)
167
+ mapping = (Array === mapping.first ? mapping : [ mapping ])
168
+
169
+ if allow_nil
170
+ module_eval <<-end_eval
171
+ def #{name}=(part)
172
+ if part.nil?
173
+ #{mapping.collect { |pair| "@attributes[\"#{pair.first}\"] = nil" }.join("\n")}
174
+ else
175
+ @#{name} = part.freeze
176
+ #{mapping.collect { |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")}
177
+ end
178
+ end
179
+ end_eval
180
+ else
181
+ module_eval <<-end_eval
182
+ def #{name}=(part)
183
+ @#{name} = part.freeze
184
+ #{mapping.collect{ |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")}
185
+ end
186
+ end_eval
187
+ end
164
188
  end
165
189
  end
166
190
  end