activerecord 5.0.7.2 → 5.1.0.beta1

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 (216) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +389 -2252
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record.rb +20 -20
  8. data/lib/active_record/aggregations.rb +244 -244
  9. data/lib/active_record/association_relation.rb +5 -5
  10. data/lib/active_record/associations.rb +1579 -1569
  11. data/lib/active_record/associations/alias_tracker.rb +1 -1
  12. data/lib/active_record/associations/association.rb +23 -15
  13. data/lib/active_record/associations/association_scope.rb +83 -81
  14. data/lib/active_record/associations/belongs_to_association.rb +0 -1
  15. data/lib/active_record/associations/builder/belongs_to.rb +16 -14
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  18. data/lib/active_record/associations/collection_association.rb +74 -241
  19. data/lib/active_record/associations/collection_proxy.rb +144 -70
  20. data/lib/active_record/associations/has_many_association.rb +15 -19
  21. data/lib/active_record/associations/has_many_through_association.rb +12 -5
  22. data/lib/active_record/associations/has_one_association.rb +22 -28
  23. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  24. data/lib/active_record/associations/join_dependency.rb +117 -115
  25. data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
  26. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  28. data/lib/active_record/associations/preloader.rb +94 -94
  29. data/lib/active_record/associations/preloader/association.rb +87 -64
  30. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  31. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  32. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  33. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +34 -41
  35. data/lib/active_record/associations/singular_association.rb +8 -25
  36. data/lib/active_record/associations/through_association.rb +3 -6
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  39. data/lib/active_record/attribute_assignment.rb +61 -61
  40. data/lib/active_record/attribute_decorators.rb +35 -13
  41. data/lib/active_record/attribute_methods.rb +56 -65
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  43. data/lib/active_record/attribute_methods/dirty.rb +216 -34
  44. data/lib/active_record/attribute_methods/primary_key.rb +78 -73
  45. data/lib/active_record/attribute_methods/read.rb +39 -35
  46. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  48. data/lib/active_record/attribute_methods/write.rb +36 -30
  49. data/lib/active_record/attribute_mutation_tracker.rb +53 -10
  50. data/lib/active_record/attribute_set.rb +9 -6
  51. data/lib/active_record/attribute_set/builder.rb +41 -49
  52. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  53. data/lib/active_record/attributes.rb +21 -21
  54. data/lib/active_record/autosave_association.rb +13 -13
  55. data/lib/active_record/base.rb +24 -22
  56. data/lib/active_record/callbacks.rb +52 -14
  57. data/lib/active_record/coders/yaml_column.rb +9 -11
  58. data/lib/active_record/collection_cache_key.rb +6 -17
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
  71. data/lib/active_record/connection_adapters/column.rb +27 -5
  72. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  73. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  94. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  97. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
  98. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
  116. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  117. data/lib/active_record/connection_handling.rb +14 -26
  118. data/lib/active_record/core.rb +110 -93
  119. data/lib/active_record/counter_cache.rb +62 -13
  120. data/lib/active_record/define_callbacks.rb +20 -0
  121. data/lib/active_record/dynamic_matchers.rb +80 -79
  122. data/lib/active_record/enum.rb +8 -6
  123. data/lib/active_record/errors.rb +58 -15
  124. data/lib/active_record/explain.rb +1 -2
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +7 -4
  127. data/lib/active_record/fixture_set/file.rb +11 -8
  128. data/lib/active_record/fixtures.rb +66 -53
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +93 -79
  131. data/lib/active_record/integration.rb +7 -7
  132. data/lib/active_record/internal_metadata.rb +3 -16
  133. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  134. data/lib/active_record/locking/optimistic.rb +64 -56
  135. data/lib/active_record/locking/pessimistic.rb +10 -1
  136. data/lib/active_record/log_subscriber.rb +29 -29
  137. data/lib/active_record/migration.rb +155 -172
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +76 -37
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/model_schema.rb +85 -119
  142. data/lib/active_record/nested_attributes.rb +200 -199
  143. data/lib/active_record/null_relation.rb +10 -33
  144. data/lib/active_record/persistence.rb +45 -38
  145. data/lib/active_record/query_cache.rb +4 -8
  146. data/lib/active_record/querying.rb +2 -3
  147. data/lib/active_record/railtie.rb +16 -17
  148. data/lib/active_record/railties/controller_runtime.rb +6 -2
  149. data/lib/active_record/railties/databases.rake +125 -140
  150. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  151. data/lib/active_record/readonly_attributes.rb +2 -2
  152. data/lib/active_record/reflection.rb +79 -96
  153. data/lib/active_record/relation.rb +72 -115
  154. data/lib/active_record/relation/batches.rb +87 -58
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  156. data/lib/active_record/relation/calculations.rb +154 -160
  157. data/lib/active_record/relation/delegation.rb +30 -29
  158. data/lib/active_record/relation/finder_methods.rb +195 -226
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder.rb +92 -89
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  165. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  166. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +247 -295
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +79 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/result.rb +29 -31
  174. data/lib/active_record/runtime_registry.rb +3 -3
  175. data/lib/active_record/sanitization.rb +182 -197
  176. data/lib/active_record/schema.rb +3 -3
  177. data/lib/active_record/schema_dumper.rb +14 -37
  178. data/lib/active_record/schema_migration.rb +3 -3
  179. data/lib/active_record/scoping.rb +9 -10
  180. data/lib/active_record/scoping/default.rb +87 -91
  181. data/lib/active_record/scoping/named.rb +16 -28
  182. data/lib/active_record/secure_token.rb +2 -2
  183. data/lib/active_record/statement_cache.rb +13 -15
  184. data/lib/active_record/store.rb +31 -32
  185. data/lib/active_record/suppressor.rb +2 -1
  186. data/lib/active_record/table_metadata.rb +9 -5
  187. data/lib/active_record/tasks/database_tasks.rb +72 -65
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  191. data/lib/active_record/timestamp.rb +39 -25
  192. data/lib/active_record/touch_later.rb +1 -2
  193. data/lib/active_record/transactions.rb +98 -110
  194. data/lib/active_record/type.rb +17 -13
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +9 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/serialized.rb +8 -8
  199. data/lib/active_record/type/text.rb +9 -0
  200. data/lib/active_record/type/time.rb +0 -1
  201. data/lib/active_record/type/type_map.rb +11 -15
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type_caster.rb +2 -2
  204. data/lib/active_record/type_caster/connection.rb +8 -6
  205. data/lib/active_record/type_caster/map.rb +3 -1
  206. data/lib/active_record/validations.rb +4 -4
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/presence.rb +2 -2
  209. data/lib/active_record/validations/uniqueness.rb +8 -39
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/rails/generators/active_record.rb +4 -4
  212. data/lib/rails/generators/active_record/migration.rb +2 -2
  213. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  215. metadata +22 -13
  216. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2016 David Heinemeier Hansson
1
+ Copyright (c) 2004-2017 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -192,7 +192,7 @@ The latest version of Active Record can be installed with RubyGems:
192
192
 
193
193
  Source code can be downloaded as part of the Rails project on GitHub:
194
194
 
195
- * https://github.com/rails/rails/tree/5-0-stable/activerecord
195
+ * https://github.com/rails/rails/tree/master/activerecord
196
196
 
197
197
 
198
198
  == License
@@ -1,10 +1,10 @@
1
1
  require "active_record"
2
- require 'benchmark/ips'
2
+ require "benchmark/ips"
3
3
 
4
- TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
5
- RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
4
+ TIME = (ENV["BENCHMARK_TIME"] || 20).to_i
5
+ RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME * 1000).to_i
6
6
 
7
- conn = { adapter: 'sqlite3', database: ':memory:' }
7
+ conn = { adapter: "sqlite3", database: ":memory:" }
8
8
 
9
9
  ActiveRecord::Base.establish_connection(conn)
10
10
 
@@ -42,26 +42,26 @@ class Exhibit < ActiveRecord::Base
42
42
  def self.feel(exhibits) exhibits.each(&:feel) end
43
43
  end
44
44
 
45
- def progress_bar(int); print "." if (int%100).zero? ; end
45
+ def progress_bar(int); print "." if (int % 100).zero? ; end
46
46
 
47
- puts 'Generating data...'
47
+ puts "Generating data..."
48
48
 
49
49
  module ActiveRecord
50
50
  class Faker
51
- LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
51
+ LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
52
52
  Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem.
53
53
  Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim,
54
54
  tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae,
55
55
  varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum
56
56
  tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero.
57
- Praesent varius tincidunt commodo}.split
57
+ Praesent varius tincidunt commodo".split
58
58
 
59
59
  def self.name
60
- LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
60
+ LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join " "
61
61
  end
62
62
 
63
63
  def self.email
64
- LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join('@') + ".com"
64
+ LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join("@") + ".com"
65
65
  end
66
66
  end
67
67
  end
@@ -72,7 +72,7 @@ end
72
72
 
73
73
  # Using the same paragraph for all exhibits because it is very slow
74
74
  # to generate unique paragraphs for all exhibits.
75
- notes = ActiveRecord::Faker::LOREM.join ' '
75
+ notes = ActiveRecord::Faker::LOREM.join " "
76
76
  today = Date.today
77
77
 
78
78
  puts "Inserting #{RECORDS} users and exhibits..."
@@ -95,9 +95,9 @@ puts "Done!\n"
95
95
 
96
96
  Benchmark.ips(TIME) do |x|
97
97
  ar_obj = Exhibit.find(1)
98
- attrs = { name: 'sam' }
99
- attrs_first = { name: 'sam' }
100
- attrs_second = { name: 'tom' }
98
+ attrs = { name: "sam" }
99
+ attrs_first = { name: "sam" }
100
+ attrs_second = { name: "tom" }
101
101
  exhibit = {
102
102
  name: ActiveRecord::Faker.name,
103
103
  notes: notes,
@@ -108,22 +108,22 @@ Benchmark.ips(TIME) do |x|
108
108
  ar_obj.id
109
109
  end
110
110
 
111
- x.report 'Model.new (instantiation)' do
111
+ x.report "Model.new (instantiation)" do
112
112
  Exhibit.new
113
113
  end
114
114
 
115
- x.report 'Model.new (setting attributes)' do
115
+ x.report "Model.new (setting attributes)" do
116
116
  Exhibit.new(attrs)
117
117
  end
118
118
 
119
- x.report 'Model.first' do
119
+ x.report "Model.first" do
120
120
  Exhibit.first.look
121
121
  end
122
122
 
123
- x.report 'Model.take' do
123
+ x.report "Model.take" do
124
124
  Exhibit.take
125
125
  end
126
-
126
+
127
127
  x.report("Model.all limit(100)") do
128
128
  Exhibit.look Exhibit.limit(100)
129
129
  end
@@ -140,36 +140,36 @@ Benchmark.ips(TIME) do |x|
140
140
  Exhibit.look Exhibit.limit(10000)
141
141
  end
142
142
 
143
- x.report 'Model.named_scope' do
143
+ x.report "Model.named_scope" do
144
144
  Exhibit.limit(10).with_name.with_notes
145
145
  end
146
146
 
147
- x.report 'Model.create' do
147
+ x.report "Model.create" do
148
148
  Exhibit.create(exhibit)
149
149
  end
150
150
 
151
- x.report 'Resource#attributes=' do
151
+ x.report "Resource#attributes=" do
152
152
  e = Exhibit.new(attrs_first)
153
153
  e.attributes = attrs_second
154
154
  end
155
155
 
156
- x.report 'Resource#update' do
157
- Exhibit.first.update(name: 'bob')
156
+ x.report "Resource#update" do
157
+ Exhibit.first.update(name: "bob")
158
158
  end
159
159
 
160
- x.report 'Resource#destroy' do
160
+ x.report "Resource#destroy" do
161
161
  Exhibit.first.destroy
162
162
  end
163
163
 
164
- x.report 'Model.transaction' do
164
+ x.report "Model.transaction" do
165
165
  Exhibit.transaction { Exhibit.new }
166
166
  end
167
167
 
168
- x.report 'Model.find(id)' do
168
+ x.report "Model.find(id)" do
169
169
  User.find(1)
170
170
  end
171
171
 
172
- x.report 'Model.find_by_sql' do
172
+ x.report "Model.find_by_sql" do
173
173
  Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
174
174
  end
175
175
 
@@ -1,13 +1,13 @@
1
- require 'active_record'
1
+ require "active_record"
2
2
 
3
3
  class Person < ActiveRecord::Base
4
- establish_connection adapter: 'sqlite3', database: 'foobar.db'
4
+ establish_connection adapter: "sqlite3", database: "foobar.db"
5
5
  connection.create_table table_name, force: true do |t|
6
6
  t.string :name
7
7
  end
8
8
  end
9
9
 
10
- bob = Person.create!(name: 'bob')
10
+ bob = Person.create!(name: "bob")
11
11
  puts Person.all.inspect
12
12
  bob.destroy
13
13
  puts Person.all.inspect
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2016 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2017 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
@@ -21,13 +21,13 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- require 'active_support'
25
- require 'active_support/rails'
26
- require 'active_model'
27
- require 'arel'
24
+ require "active_support"
25
+ require "active_support/rails"
26
+ require "active_model"
27
+ require "arel"
28
28
 
29
- require 'active_record/version'
30
- require 'active_record/attribute_set'
29
+ require "active_record/version"
30
+ require "active_record/attribute_set"
31
31
 
32
32
  module ActiveRecord
33
33
  extend ActiveSupport::Autoload
@@ -45,7 +45,7 @@ module ActiveRecord
45
45
  autoload :Inheritance
46
46
  autoload :Integration
47
47
  autoload :Migration
48
- autoload :Migrator, 'active_record/migration'
48
+ autoload :Migrator, "active_record/migration"
49
49
  autoload :ModelSchema
50
50
  autoload :NestedAttributes
51
51
  autoload :NoTouching
@@ -55,7 +55,7 @@ module ActiveRecord
55
55
  autoload :Querying
56
56
  autoload :CollectionCacheKey
57
57
  autoload :ReadonlyAttributes
58
- autoload :RecordInvalid, 'active_record/validations'
58
+ autoload :RecordInvalid, "active_record/validations"
59
59
  autoload :Reflection
60
60
  autoload :RuntimeRegistry
61
61
  autoload :Sanitization
@@ -74,9 +74,9 @@ module ActiveRecord
74
74
  autoload :SecureToken
75
75
 
76
76
  eager_autoload do
77
- autoload :ActiveRecordError, 'active_record/errors'
78
- autoload :ConnectionNotEstablished, 'active_record/errors'
79
- autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
77
+ autoload :ActiveRecordError, "active_record/errors"
78
+ autoload :ConnectionNotEstablished, "active_record/errors"
79
+ autoload :ConnectionAdapters, "active_record/connection_adapters/abstract_adapter"
80
80
 
81
81
  autoload :Aggregations
82
82
  autoload :Associations
@@ -90,7 +90,7 @@ module ActiveRecord
90
90
  autoload :AssociationRelation
91
91
  autoload :NullRelation
92
92
 
93
- autoload_under 'relation' do
93
+ autoload_under "relation" do
94
94
  autoload :QueryMethods
95
95
  autoload :FinderMethods
96
96
  autoload :Calculations
@@ -105,8 +105,8 @@ module ActiveRecord
105
105
  end
106
106
 
107
107
  module Coders
108
- autoload :YAMLColumn, 'active_record/coders/yaml_column'
109
- autoload :JSON, 'active_record/coders/json'
108
+ autoload :YAMLColumn, "active_record/coders/yaml_column"
109
+ autoload :JSON, "active_record/coders/json"
110
110
  end
111
111
 
112
112
  module AttributeMethods
@@ -154,13 +154,13 @@ module ActiveRecord
154
154
  extend ActiveSupport::Autoload
155
155
 
156
156
  autoload :DatabaseTasks
157
- autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
158
- autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
157
+ autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
158
+ autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
159
159
  autoload :PostgreSQLDatabaseTasks,
160
- 'active_record/tasks/postgresql_database_tasks'
160
+ "active_record/tasks/postgresql_database_tasks"
161
161
  end
162
162
 
163
- autoload :TestFixtures, 'active_record/fixtures'
163
+ autoload :TestFixtures, "active_record/fixtures"
164
164
 
165
165
  def self.eager_load!
166
166
  super
@@ -177,5 +177,5 @@ ActiveSupport.on_load(:active_record) do
177
177
  end
178
178
 
179
179
  ActiveSupport.on_load(:i18n) do
180
- I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
180
+ I18n.load_path << File.dirname(__FILE__) + "/active_record/locale/en.yml"
181
181
  end
@@ -15,268 +15,268 @@ module ActiveRecord
15
15
 
16
16
  private
17
17
 
18
- def clear_aggregation_cache # :nodoc:
18
+ def clear_aggregation_cache
19
19
  @aggregation_cache.clear if persisted?
20
20
  end
21
21
 
22
- def init_internals # :nodoc:
22
+ def init_internals
23
23
  @aggregation_cache = {}
24
24
  super
25
25
  end
26
26
 
27
- # Active Record implements aggregation through a macro-like class method called #composed_of
28
- # for representing attributes as value objects. It expresses relationships like "Account [is]
29
- # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
30
- # to the macro adds a description of how the value objects are created from the attributes of
31
- # the entity object (when the entity is initialized either as a new object or from finding an
32
- # existing object) and how it can be turned back into attributes (when the entity is saved to
33
- # the database).
34
- #
35
- # class Customer < ActiveRecord::Base
36
- # composed_of :balance, class_name: "Money", mapping: %w(amount currency)
37
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
38
- # end
39
- #
40
- # The customer class now has the following methods to manipulate the value objects:
41
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
42
- # * <tt>Customer#address, Customer#address=(address)</tt>
43
- #
44
- # These methods will operate with value objects like the ones described below:
45
- #
46
- # class Money
47
- # include Comparable
48
- # attr_reader :amount, :currency
49
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
50
- #
51
- # def initialize(amount, currency = "USD")
52
- # @amount, @currency = amount, currency
53
- # end
54
- #
55
- # def exchange_to(other_currency)
56
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
57
- # Money.new(exchanged_amount, other_currency)
58
- # end
59
- #
60
- # def ==(other_money)
61
- # amount == other_money.amount && currency == other_money.currency
62
- # end
63
- #
64
- # def <=>(other_money)
65
- # if currency == other_money.currency
66
- # amount <=> other_money.amount
67
- # else
68
- # amount <=> other_money.exchange_to(currency).amount
69
- # end
70
- # end
71
- # end
72
- #
73
- # class Address
74
- # attr_reader :street, :city
75
- # def initialize(street, city)
76
- # @street, @city = street, city
77
- # end
78
- #
79
- # def close_to?(other_address)
80
- # city == other_address.city
81
- # end
82
- #
83
- # def ==(other_address)
84
- # city == other_address.city && street == other_address.street
85
- # end
86
- # end
87
- #
88
- # Now it's possible to access attributes from the database through the value objects instead. If
89
- # you choose to name the composition the same as the attribute's name, it will be the only way to
90
- # access that attribute. That's the case with our +balance+ attribute. You interact with the value
91
- # objects just like you would with any other attribute:
92
- #
93
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
94
- # customer.balance # => Money value object
95
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
96
- # customer.balance > Money.new(10) # => true
97
- # customer.balance == Money.new(20) # => true
98
- # customer.balance < Money.new(5) # => false
99
- #
100
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order
101
- # of the mappings will determine the order of the parameters.
102
- #
103
- # customer.address_street = "Hyancintvej"
104
- # customer.address_city = "Copenhagen"
105
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
106
- #
107
- # customer.address = Address.new("May Street", "Chicago")
108
- # customer.address_street # => "May Street"
109
- # customer.address_city # => "Chicago"
110
- #
111
- # == Writing value objects
112
- #
113
- # Value objects are immutable and interchangeable objects that represent a given value, such as
114
- # a Money object representing $5. Two Money objects both representing $5 should be equal (through
115
- # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
116
- # unlike entity objects where equality is determined by identity. An entity class such as Customer can
117
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is
118
- # determined by object or relational unique identifiers (such as primary keys). Normal
119
- # ActiveRecord::Base classes are entity objects.
120
- #
121
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have
122
- # its amount changed after creation. Create a new Money object with the new value instead. The
123
- # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
124
- # its own values. Active Record won't persist value objects that have been changed through means
125
- # other than the writer method.
126
- #
127
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
128
- # object. Attempting to change it afterwards will result in a +RuntimeError+.
129
- #
130
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
131
- # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
132
- #
133
- # == Custom constructors and converters
134
- #
135
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
136
- # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
137
- # option, as arguments. If the value class doesn't support this convention then #composed_of allows
138
- # a custom constructor to be specified.
139
- #
140
- # When a new value is assigned to the value object, the default assumption is that the new value
141
- # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
142
- # converted to an instance of value class if necessary.
143
- #
144
- # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
145
- # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
146
- # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
147
- # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
148
- # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
149
- # these requirements:
150
- #
151
- # class NetworkResource < ActiveRecord::Base
152
- # composed_of :cidr,
153
- # class_name: 'NetAddr::CIDR',
154
- # mapping: [ %w(network_address network), %w(cidr_range bits) ],
155
- # allow_nil: true,
156
- # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
157
- # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
158
- # end
159
- #
160
- # # This calls the :constructor
161
- # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
162
- #
163
- # # These assignments will both use the :converter
164
- # network_resource.cidr = [ '192.168.2.1', 8 ]
165
- # network_resource.cidr = '192.168.0.1/24'
166
- #
167
- # # This assignment won't use the :converter as the value is already an instance of the value class
168
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
169
- #
170
- # # Saving and then reloading will use the :constructor on reload
171
- # network_resource.save
172
- # network_resource.reload
173
- #
174
- # == Finding records by a value object
175
- #
176
- # Once a #composed_of relationship is specified for a model, records can be loaded from the database
177
- # by specifying an instance of the value object in the conditions hash. The following example
178
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
179
- #
180
- # Customer.where(balance: Money.new(20, "USD"))
181
- #
182
- module ClassMethods
183
- # Adds reader and writer methods for manipulating a value object:
184
- # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
185
- #
186
- # Options are:
187
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
188
- # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
189
- # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
190
- # with this option.
191
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
192
- # object. Each mapping is represented as an array where the first item is the name of the
193
- # entity attribute and the second item is the name of the attribute in the value object. The
194
- # order in which mappings are defined determines the order in which attributes are sent to the
195
- # value class constructor.
196
- # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
197
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
198
- # mapped attributes.
199
- # This defaults to +false+.
200
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
201
- # is called to initialize the value object. The constructor is passed all of the mapped attributes,
202
- # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
203
- # to instantiate a <tt>:class_name</tt> object.
204
- # The default is <tt>:new</tt>.
205
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
206
- # or a Proc that is called when a new value is assigned to the value object. The converter is
207
- # passed the single value that is used in the assignment and is only called if the new value is
208
- # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
209
- # can return nil to skip the assignment.
210
- #
211
- # Option examples:
212
- # composed_of :temperature, mapping: %w(reading celsius)
213
- # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
214
- # converter: Proc.new { |balance| balance.to_money }
215
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
216
- # composed_of :gps_location
217
- # composed_of :gps_location, allow_nil: true
218
- # composed_of :ip_address,
219
- # class_name: 'IPAddr',
220
- # mapping: %w(ip to_i),
221
- # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
222
- # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
223
- #
224
- def composed_of(part_id, options = {})
225
- options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
27
+ # Active Record implements aggregation through a macro-like class method called #composed_of
28
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
29
+ # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
30
+ # to the macro adds a description of how the value objects are created from the attributes of
31
+ # the entity object (when the entity is initialized either as a new object or from finding an
32
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
33
+ # the database).
34
+ #
35
+ # class Customer < ActiveRecord::Base
36
+ # composed_of :balance, class_name: "Money", mapping: %w(amount currency)
37
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
38
+ # end
39
+ #
40
+ # The customer class now has the following methods to manipulate the value objects:
41
+ # * <tt>Customer#balance, Customer#balance=(money)</tt>
42
+ # * <tt>Customer#address, Customer#address=(address)</tt>
43
+ #
44
+ # These methods will operate with value objects like the ones described below:
45
+ #
46
+ # class Money
47
+ # include Comparable
48
+ # attr_reader :amount, :currency
49
+ # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
50
+ #
51
+ # def initialize(amount, currency = "USD")
52
+ # @amount, @currency = amount, currency
53
+ # end
54
+ #
55
+ # def exchange_to(other_currency)
56
+ # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
57
+ # Money.new(exchanged_amount, other_currency)
58
+ # end
59
+ #
60
+ # def ==(other_money)
61
+ # amount == other_money.amount && currency == other_money.currency
62
+ # end
63
+ #
64
+ # def <=>(other_money)
65
+ # if currency == other_money.currency
66
+ # amount <=> other_money.amount
67
+ # else
68
+ # amount <=> other_money.exchange_to(currency).amount
69
+ # end
70
+ # end
71
+ # end
72
+ #
73
+ # class Address
74
+ # attr_reader :street, :city
75
+ # def initialize(street, city)
76
+ # @street, @city = street, city
77
+ # end
78
+ #
79
+ # def close_to?(other_address)
80
+ # city == other_address.city
81
+ # end
82
+ #
83
+ # def ==(other_address)
84
+ # city == other_address.city && street == other_address.street
85
+ # end
86
+ # end
87
+ #
88
+ # Now it's possible to access attributes from the database through the value objects instead. If
89
+ # you choose to name the composition the same as the attribute's name, it will be the only way to
90
+ # access that attribute. That's the case with our +balance+ attribute. You interact with the value
91
+ # objects just like you would with any other attribute:
92
+ #
93
+ # customer.balance = Money.new(20) # sets the Money value object and the attribute
94
+ # customer.balance # => Money value object
95
+ # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
96
+ # customer.balance > Money.new(10) # => true
97
+ # customer.balance == Money.new(20) # => true
98
+ # customer.balance < Money.new(5) # => false
99
+ #
100
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order
101
+ # of the mappings will determine the order of the parameters.
102
+ #
103
+ # customer.address_street = "Hyancintvej"
104
+ # customer.address_city = "Copenhagen"
105
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
106
+ #
107
+ # customer.address = Address.new("May Street", "Chicago")
108
+ # customer.address_street # => "May Street"
109
+ # customer.address_city # => "Chicago"
110
+ #
111
+ # == Writing value objects
112
+ #
113
+ # Value objects are immutable and interchangeable objects that represent a given value, such as
114
+ # a Money object representing $5. Two Money objects both representing $5 should be equal (through
115
+ # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
116
+ # unlike entity objects where equality is determined by identity. An entity class such as Customer can
117
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is
118
+ # determined by object or relational unique identifiers (such as primary keys). Normal
119
+ # ActiveRecord::Base classes are entity objects.
120
+ #
121
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have
122
+ # its amount changed after creation. Create a new Money object with the new value instead. The
123
+ # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
124
+ # its own values. Active Record won't persist value objects that have been changed through means
125
+ # other than the writer method.
126
+ #
127
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
128
+ # object. Attempting to change it afterwards will result in a +RuntimeError+.
129
+ #
130
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
131
+ # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
132
+ #
133
+ # == Custom constructors and converters
134
+ #
135
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
136
+ # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
137
+ # option, as arguments. If the value class doesn't support this convention then #composed_of allows
138
+ # a custom constructor to be specified.
139
+ #
140
+ # When a new value is assigned to the value object, the default assumption is that the new value
141
+ # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
142
+ # converted to an instance of value class if necessary.
143
+ #
144
+ # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
145
+ # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
146
+ # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
147
+ # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
148
+ # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
149
+ # these requirements:
150
+ #
151
+ # class NetworkResource < ActiveRecord::Base
152
+ # composed_of :cidr,
153
+ # class_name: 'NetAddr::CIDR',
154
+ # mapping: [ %w(network_address network), %w(cidr_range bits) ],
155
+ # allow_nil: true,
156
+ # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
157
+ # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
158
+ # end
159
+ #
160
+ # # This calls the :constructor
161
+ # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
162
+ #
163
+ # # These assignments will both use the :converter
164
+ # network_resource.cidr = [ '192.168.2.1', 8 ]
165
+ # network_resource.cidr = '192.168.0.1/24'
166
+ #
167
+ # # This assignment won't use the :converter as the value is already an instance of the value class
168
+ # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
169
+ #
170
+ # # Saving and then reloading will use the :constructor on reload
171
+ # network_resource.save
172
+ # network_resource.reload
173
+ #
174
+ # == Finding records by a value object
175
+ #
176
+ # Once a #composed_of relationship is specified for a model, records can be loaded from the database
177
+ # by specifying an instance of the value object in the conditions hash. The following example
178
+ # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
179
+ #
180
+ # Customer.where(balance: Money.new(20, "USD"))
181
+ #
182
+ module ClassMethods
183
+ # Adds reader and writer methods for manipulating a value object:
184
+ # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
185
+ #
186
+ # Options are:
187
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
188
+ # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
189
+ # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
190
+ # with this option.
191
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
192
+ # object. Each mapping is represented as an array where the first item is the name of the
193
+ # entity attribute and the second item is the name of the attribute in the value object. The
194
+ # order in which mappings are defined determines the order in which attributes are sent to the
195
+ # value class constructor.
196
+ # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
197
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
198
+ # mapped attributes.
199
+ # This defaults to +false+.
200
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
201
+ # is called to initialize the value object. The constructor is passed all of the mapped attributes,
202
+ # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
203
+ # to instantiate a <tt>:class_name</tt> object.
204
+ # The default is <tt>:new</tt>.
205
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
206
+ # or a Proc that is called when a new value is assigned to the value object. The converter is
207
+ # passed the single value that is used in the assignment and is only called if the new value is
208
+ # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
209
+ # can return +nil+ to skip the assignment.
210
+ #
211
+ # Option examples:
212
+ # composed_of :temperature, mapping: %w(reading celsius)
213
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
214
+ # converter: Proc.new { |balance| balance.to_money }
215
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
216
+ # composed_of :gps_location
217
+ # composed_of :gps_location, allow_nil: true
218
+ # composed_of :ip_address,
219
+ # class_name: 'IPAddr',
220
+ # mapping: %w(ip to_i),
221
+ # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
222
+ # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
223
+ #
224
+ def composed_of(part_id, options = {})
225
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
226
226
 
227
- name = part_id.id2name
228
- class_name = options[:class_name] || name.camelize
229
- mapping = options[:mapping] || [ name, name ]
230
- mapping = [ mapping ] unless mapping.first.is_a?(Array)
231
- allow_nil = options[:allow_nil] || false
232
- constructor = options[:constructor] || :new
233
- converter = options[:converter]
227
+ name = part_id.id2name
228
+ class_name = options[:class_name] || name.camelize
229
+ mapping = options[:mapping] || [ name, name ]
230
+ mapping = [ mapping ] unless mapping.first.is_a?(Array)
231
+ allow_nil = options[:allow_nil] || false
232
+ constructor = options[:constructor] || :new
233
+ converter = options[:converter]
234
234
 
235
- reader_method(name, class_name, mapping, allow_nil, constructor)
236
- writer_method(name, class_name, mapping, allow_nil, converter)
235
+ reader_method(name, class_name, mapping, allow_nil, constructor)
236
+ writer_method(name, class_name, mapping, allow_nil, converter)
237
237
 
238
- reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
239
- Reflection.add_aggregate_reflection self, part_id, reflection
240
- end
238
+ reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
239
+ Reflection.add_aggregate_reflection self, part_id, reflection
240
+ end
241
241
 
242
- private
243
- def reader_method(name, class_name, mapping, allow_nil, constructor)
244
- define_method(name) do
245
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
246
- attrs = mapping.collect {|key, _| _read_attribute(key)}
247
- object = constructor.respond_to?(:call) ?
248
- constructor.call(*attrs) :
249
- class_name.constantize.send(constructor, *attrs)
250
- @aggregation_cache[name] = object
242
+ private
243
+ def reader_method(name, class_name, mapping, allow_nil, constructor)
244
+ define_method(name) do
245
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? })
246
+ attrs = mapping.collect { |key, _| _read_attribute(key) }
247
+ object = constructor.respond_to?(:call) ?
248
+ constructor.call(*attrs) :
249
+ class_name.constantize.send(constructor, *attrs)
250
+ @aggregation_cache[name] = object
251
+ end
252
+ @aggregation_cache[name]
251
253
  end
252
- @aggregation_cache[name]
253
254
  end
254
- end
255
255
 
256
- def writer_method(name, class_name, mapping, allow_nil, converter)
257
- define_method("#{name}=") do |part|
258
- klass = class_name.constantize
256
+ def writer_method(name, class_name, mapping, allow_nil, converter)
257
+ define_method("#{name}=") do |part|
258
+ klass = class_name.constantize
259
259
 
260
- unless part.is_a?(klass) || converter.nil? || part.nil?
261
- part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
262
- end
260
+ unless part.is_a?(klass) || converter.nil? || part.nil?
261
+ part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
262
+ end
263
263
 
264
- hash_from_multiparameter_assignment = part.is_a?(Hash) &&
265
- part.each_key.all? { |k| k.is_a?(Integer) }
266
- if hash_from_multiparameter_assignment
267
- raise ArgumentError unless part.size == part.each_key.max
268
- part = klass.new(*part.sort.map(&:last))
269
- end
264
+ hash_from_multiparameter_assignment = part.is_a?(Hash) &&
265
+ part.each_key.all? { |k| k.is_a?(Integer) }
266
+ if hash_from_multiparameter_assignment
267
+ raise ArgumentError unless part.size == part.each_key.max
268
+ part = klass.new(*part.sort.map(&:last))
269
+ end
270
270
 
271
- if part.nil? && allow_nil
272
- mapping.each { |key, _| self[key] = nil }
273
- @aggregation_cache[name] = nil
274
- else
275
- mapping.each { |key, value| self[key] = part.send(value) }
276
- @aggregation_cache[name] = part.freeze
271
+ if part.nil? && allow_nil
272
+ mapping.each { |key, _| self[key] = nil }
273
+ @aggregation_cache[name] = nil
274
+ else
275
+ mapping.each { |key, value| self[key] = part.send(value) }
276
+ @aggregation_cache[name] = part.freeze
277
+ end
277
278
  end
278
279
  end
279
- end
280
- end
280
+ end
281
281
  end
282
282
  end