activerecord 1.0.0 → 4.0.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,349 @@
1
+ module ActiveRecord
2
+ module FinderMethods
3
+ # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
4
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
5
+ # is an integer, find by id coerces its arguments using +to_i+.
6
+ #
7
+ # Person.find(1) # returns the object for ID = 1
8
+ # Person.find("1") # returns the object for ID = 1
9
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
10
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
11
+ # Person.find([1]) # returns an array for the object with ID = 1
12
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
13
+ #
14
+ # Note that returned records may not be in the same order as the ids you
15
+ # provide since database rows are unordered. Give an explicit <tt>order</tt>
16
+ # to ensure the results are sorted.
17
+ #
18
+ # ==== Find with lock
19
+ #
20
+ # Example for find with a lock: Imagine two concurrent transactions:
21
+ # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
22
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
23
+ # transaction has to wait until the first is finished; we get the
24
+ # expected <tt>person.visits == 4</tt>.
25
+ #
26
+ # Person.transaction do
27
+ # person = Person.lock(true).find(1)
28
+ # person.visits += 1
29
+ # person.save!
30
+ # end
31
+ def find(*args)
32
+ if block_given?
33
+ to_a.find { |*block_args| yield(*block_args) }
34
+ else
35
+ find_with_ids(*args)
36
+ end
37
+ end
38
+
39
+ # Finds the first record matching the specified conditions. There
40
+ # is no implied ordering so if order matters, you should specify it
41
+ # yourself.
42
+ #
43
+ # If no record is found, returns <tt>nil</tt>.
44
+ #
45
+ # Post.find_by name: 'Spartacus', rating: 4
46
+ # Post.find_by "published_at < ?", 2.weeks.ago
47
+ def find_by(*args)
48
+ where(*args).take
49
+ end
50
+
51
+ # Like <tt>find_by</tt>, except that if no record is found, raises
52
+ # an <tt>ActiveRecord::RecordNotFound</tt> error.
53
+ def find_by!(*args)
54
+ where(*args).take!
55
+ end
56
+
57
+ # Gives a record (or N records if a parameter is supplied) without any implied
58
+ # order. The order will depend on the database implementation.
59
+ # If an order is supplied it will be respected.
60
+ #
61
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
62
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
63
+ # Person.where(["name LIKE '%?'", name]).take
64
+ def take(limit = nil)
65
+ limit ? limit(limit).to_a : find_take
66
+ end
67
+
68
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
69
+ # is found. Note that <tt>take!</tt> accepts no arguments.
70
+ def take!
71
+ take or raise RecordNotFound
72
+ end
73
+
74
+ # Find the first record (or first N records if a parameter is supplied).
75
+ # If no order is defined it will order by primary key.
76
+ #
77
+ # Person.first # returns the first object fetched by SELECT * FROM people
78
+ # Person.where(["user_name = ?", user_name]).first
79
+ # Person.where(["user_name = :u", { u: user_name }]).first
80
+ # Person.order("created_on DESC").offset(5).first
81
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
82
+ def first(limit = nil)
83
+ if limit
84
+ if order_values.empty? && primary_key
85
+ order(arel_table[primary_key].asc).limit(limit).to_a
86
+ else
87
+ limit(limit).to_a
88
+ end
89
+ else
90
+ find_first
91
+ end
92
+ end
93
+
94
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
95
+ # is found. Note that <tt>first!</tt> accepts no arguments.
96
+ def first!
97
+ first or raise RecordNotFound
98
+ end
99
+
100
+ # Find the last record (or last N records if a parameter is supplied).
101
+ # If no order is defined it will order by primary key.
102
+ #
103
+ # Person.last # returns the last object fetched by SELECT * FROM people
104
+ # Person.where(["user_name = ?", user_name]).last
105
+ # Person.order("created_on DESC").offset(5).last
106
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
107
+ #
108
+ # Take note that in that last case, the results are sorted in ascending order:
109
+ #
110
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
111
+ #
112
+ # and not:
113
+ #
114
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
115
+ def last(limit = nil)
116
+ if limit
117
+ if order_values.empty? && primary_key
118
+ order(arel_table[primary_key].desc).limit(limit).reverse
119
+ else
120
+ to_a.last(limit)
121
+ end
122
+ else
123
+ find_last
124
+ end
125
+ end
126
+
127
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
128
+ # is found. Note that <tt>last!</tt> accepts no arguments.
129
+ def last!
130
+ last or raise RecordNotFound
131
+ end
132
+
133
+ # Returns truthy if a record exists in the table that matches the +id+ or
134
+ # conditions given, or falsy otherwise. The argument can take six forms:
135
+ #
136
+ # * Integer - Finds the record with this primary key.
137
+ # * String - Finds the record with a primary key corresponding to this
138
+ # string (such as <tt>'5'</tt>).
139
+ # * Array - Finds the record that matches these +find+-style conditions
140
+ # (such as <tt>['color = ?', 'red']</tt>).
141
+ # * Hash - Finds the record that matches these +find+-style conditions
142
+ # (such as <tt>{color: 'red'}</tt>).
143
+ # * +false+ - Returns always +false+.
144
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
145
+ #
146
+ # For more information about specifying conditions as a Hash or Array,
147
+ # see the Conditions section in the introduction to ActiveRecord::Base.
148
+ #
149
+ # Note: You can't pass in a condition as a string (like <tt>name =
150
+ # 'Jamie'</tt>), since it would be sanitized and then queried against
151
+ # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
152
+ #
153
+ # Person.exists?(5)
154
+ # Person.exists?('5')
155
+ # Person.exists?(['name LIKE ?', "%#{query}%"])
156
+ # Person.exists?(name: 'David')
157
+ # Person.exists?(false)
158
+ # Person.exists?
159
+ def exists?(conditions = :none)
160
+ conditions = conditions.id if Base === conditions
161
+ return false if !conditions
162
+
163
+ join_dependency = construct_join_dependency_for_association_find
164
+ relation = construct_relation_for_association_find(join_dependency)
165
+ relation = relation.except(:select, :order).select("1 AS one").limit(1)
166
+
167
+ case conditions
168
+ when Array, Hash
169
+ relation = relation.where(conditions)
170
+ else
171
+ relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
172
+ end
173
+
174
+ connection.select_value(relation, "#{name} Exists", relation.bind_values)
175
+ rescue ThrowResult
176
+ false
177
+ end
178
+
179
+ # This method is called whenever no records are found with either a single
180
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
181
+ #
182
+ # The error message is different depending on whether a single id or
183
+ # multiple ids are provided. If multiple ids are provided, then the number
184
+ # of results obtained should be provided in the +result_size+ argument and
185
+ # the expected number of results should be provided in the +expected_size+
186
+ # argument.
187
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
188
+ conditions = arel.where_sql
189
+ conditions = " [#{conditions}]" if conditions
190
+
191
+ if Array(ids).size == 1
192
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
193
+ else
194
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
195
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
196
+ end
197
+
198
+ raise RecordNotFound, error
199
+ end
200
+
201
+ protected
202
+
203
+ def find_with_associations
204
+ join_dependency = construct_join_dependency_for_association_find
205
+ relation = construct_relation_for_association_find(join_dependency)
206
+ rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
207
+ join_dependency.instantiate(rows)
208
+ rescue ThrowResult
209
+ []
210
+ end
211
+
212
+ def construct_join_dependency_for_association_find
213
+ including = (eager_load_values + includes_values).uniq
214
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
215
+ end
216
+
217
+ def construct_relation_for_association_calculations
218
+ including = (eager_load_values + includes_values).uniq
219
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
220
+ relation = except(:includes, :eager_load, :preload)
221
+ apply_join_dependency(relation, join_dependency)
222
+ end
223
+
224
+ def construct_relation_for_association_find(join_dependency)
225
+ relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
226
+ apply_join_dependency(relation, join_dependency)
227
+ end
228
+
229
+ def apply_join_dependency(relation, join_dependency)
230
+ join_dependency.join_associations.each do |association|
231
+ relation = association.join_relation(relation)
232
+ end
233
+
234
+ limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
235
+
236
+ if !limitable_reflections && relation.limit_value
237
+ limited_id_condition = construct_limited_ids_condition(relation.except(:select))
238
+ relation = relation.where(limited_id_condition)
239
+ end
240
+
241
+ relation = relation.except(:limit, :offset) unless limitable_reflections
242
+
243
+ relation
244
+ end
245
+
246
+ def construct_limited_ids_condition(relation)
247
+ orders = relation.order_values.map { |val| val.presence }.compact
248
+ values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
249
+
250
+ relation = relation.dup.select(values)
251
+
252
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
253
+ ids_array = id_rows.map {|row| row[primary_key]}
254
+
255
+ ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
256
+ end
257
+
258
+ def find_with_ids(*ids)
259
+ expects_array = ids.first.kind_of?(Array)
260
+ return ids.first if expects_array && ids.first.empty?
261
+
262
+ ids = ids.flatten.compact.uniq
263
+
264
+ case ids.size
265
+ when 0
266
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
267
+ when 1
268
+ result = find_one(ids.first)
269
+ expects_array ? [ result ] : result
270
+ else
271
+ find_some(ids)
272
+ end
273
+ end
274
+
275
+ def find_one(id)
276
+ id = id.id if ActiveRecord::Base === id
277
+
278
+ column = columns_hash[primary_key]
279
+ substitute = connection.substitute_at(column, bind_values.length)
280
+ relation = where(table[primary_key].eq(substitute))
281
+ relation.bind_values += [[column, id]]
282
+ record = relation.take
283
+
284
+ raise_record_not_found_exception!(id, 0, 1) unless record
285
+
286
+ record
287
+ end
288
+
289
+ def find_some(ids)
290
+ result = where(table[primary_key].in(ids)).to_a
291
+
292
+ expected_size =
293
+ if limit_value && ids.size > limit_value
294
+ limit_value
295
+ else
296
+ ids.size
297
+ end
298
+
299
+ # 11 ids with limit 3, offset 9 should give 2 results.
300
+ if offset_value && (ids.size - offset_value < expected_size)
301
+ expected_size = ids.size - offset_value
302
+ end
303
+
304
+ if result.size == expected_size
305
+ result
306
+ else
307
+ raise_record_not_found_exception!(ids, result.size, expected_size)
308
+ end
309
+ end
310
+
311
+ def find_take
312
+ if loaded?
313
+ @records.first
314
+ else
315
+ @take ||= limit(1).to_a.first
316
+ end
317
+ end
318
+
319
+ def find_first
320
+ if loaded?
321
+ @records.first
322
+ else
323
+ @first ||=
324
+ if with_default_scope.order_values.empty? && primary_key
325
+ order(arel_table[primary_key].asc).limit(1).to_a.first
326
+ else
327
+ limit(1).to_a.first
328
+ end
329
+ end
330
+ end
331
+
332
+ def find_last
333
+ if loaded?
334
+ @records.last
335
+ else
336
+ @last ||=
337
+ if offset_value || limit_value
338
+ to_a.last
339
+ else
340
+ reverse_order.limit(1).to_a.first
341
+ end
342
+ end
343
+ end
344
+
345
+ def using_limitable_reflections?(reflections)
346
+ reflections.none? { |r| r.collection? }
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,161 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require "set"
3
+
4
+ module ActiveRecord
5
+ class Relation
6
+ class HashMerger # :nodoc:
7
+ attr_reader :relation, :hash
8
+
9
+ def initialize(relation, hash)
10
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
11
+
12
+ @relation = relation
13
+ @hash = hash
14
+ end
15
+
16
+ def merge
17
+ Merger.new(relation, other).merge
18
+ end
19
+
20
+ # Applying values to a relation has some side effects. E.g.
21
+ # interpolation might take place for where values. So we should
22
+ # build a relation to merge in rather than directly merging
23
+ # the values.
24
+ def other
25
+ other = Relation.new(relation.klass, relation.table)
26
+ hash.each { |k, v|
27
+ if k == :joins
28
+ if Hash === v
29
+ other.joins!(v)
30
+ else
31
+ other.joins!(*v)
32
+ end
33
+ else
34
+ other.send("#{k}!", v)
35
+ end
36
+ }
37
+ other
38
+ end
39
+ end
40
+
41
+ class Merger # :nodoc:
42
+ attr_reader :relation, :values, :other
43
+
44
+ def initialize(relation, other)
45
+ if other.default_scoped? && other.klass != relation.klass
46
+ other = other.with_default_scope
47
+ end
48
+
49
+ @relation = relation
50
+ @values = other.values
51
+ @other = other
52
+ end
53
+
54
+ NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
55
+ Relation::MULTI_VALUE_METHODS -
56
+ [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
57
+
58
+ def normal_values
59
+ NORMAL_VALUES
60
+ end
61
+
62
+ def merge
63
+ normal_values.each do |name|
64
+ value = values[name]
65
+ relation.send("#{name}!", *value) unless value.blank?
66
+ end
67
+
68
+ merge_multi_values
69
+ merge_single_values
70
+ merge_joins
71
+
72
+ relation
73
+ end
74
+
75
+ private
76
+
77
+ def merge_joins
78
+ return if values[:joins].blank?
79
+
80
+ if other.klass == relation.klass
81
+ relation.joins!(*values[:joins])
82
+ else
83
+ joins_dependency, rest = values[:joins].partition do |join|
84
+ case join
85
+ when Hash, Symbol, Array
86
+ true
87
+ else
88
+ false
89
+ end
90
+ end
91
+
92
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
93
+ joins_dependency,
94
+ [])
95
+ relation.joins! rest
96
+
97
+ join_dependency.join_associations.each do |association|
98
+ @relation = association.join_relation(relation)
99
+ end
100
+ end
101
+ end
102
+
103
+ def merge_multi_values
104
+ relation.where_values = merged_wheres
105
+ relation.bind_values = merged_binds
106
+
107
+ if values[:reordering]
108
+ # override any order specified in the original relation
109
+ relation.reorder! values[:order]
110
+ elsif values[:order]
111
+ # merge in order_values from r
112
+ relation.order! values[:order]
113
+ end
114
+
115
+ relation.extend(*values[:extending]) unless values[:extending].blank?
116
+ end
117
+
118
+ def merge_single_values
119
+ relation.from_value = values[:from] unless relation.from_value
120
+ relation.lock_value = values[:lock] unless relation.lock_value
121
+ relation.reverse_order_value = values[:reverse_order]
122
+
123
+ unless values[:create_with].blank?
124
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
125
+ end
126
+ end
127
+
128
+ def merged_binds
129
+ if values[:bind]
130
+ (relation.bind_values + values[:bind]).uniq(&:first)
131
+ else
132
+ relation.bind_values
133
+ end
134
+ end
135
+
136
+ def merged_wheres
137
+ values[:where] ||= []
138
+
139
+ if values[:where].empty? || relation.where_values.empty?
140
+ relation.where_values + values[:where]
141
+ else
142
+ # Remove equalities from the existing relation with a LHS which is
143
+ # present in the relation being merged in.
144
+
145
+ seen = Set.new
146
+ values[:where].each { |w|
147
+ if w.respond_to?(:operator) && w.operator == :==
148
+ seen << w.left
149
+ end
150
+ }
151
+
152
+ relation.where_values.reject { |w|
153
+ w.respond_to?(:operator) &&
154
+ w.operator == :== &&
155
+ seen.include?(w.left)
156
+ } + values[:where]
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,106 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder # :nodoc:
3
+ def self.build_from_hash(klass, attributes, default_table)
4
+ queries = []
5
+
6
+ attributes.each do |column, value|
7
+ table = default_table
8
+
9
+ if value.is_a?(Hash)
10
+ if value.empty?
11
+ queries << '1=0'
12
+ else
13
+ table = Arel::Table.new(column, default_table.engine)
14
+ association = klass.reflect_on_association(column.to_sym)
15
+
16
+ value.each do |k, v|
17
+ queries.concat expand(association && association.klass, table, k, v)
18
+ end
19
+ end
20
+ else
21
+ column = column.to_s
22
+
23
+ if column.include?('.')
24
+ table_name, column = column.split('.', 2)
25
+ table = Arel::Table.new(table_name, default_table.engine)
26
+ end
27
+
28
+ queries.concat expand(klass, table, column, value)
29
+ end
30
+ end
31
+
32
+ queries
33
+ end
34
+
35
+ def self.expand(klass, table, column, value)
36
+ queries = []
37
+
38
+ # Find the foreign key when using queries such as:
39
+ # Post.where(author: author)
40
+ #
41
+ # For polymorphic relationships, find the foreign key and type:
42
+ # PriceEstimate.where(estimate_of: treasure)
43
+ if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym)
44
+ if reflection.polymorphic?
45
+ queries << build(table[reflection.foreign_type], value.class.base_class)
46
+ end
47
+
48
+ column = reflection.foreign_key
49
+ end
50
+
51
+ queries << build(table[column], value)
52
+ queries
53
+ end
54
+
55
+ def self.references(attributes)
56
+ attributes.map do |key, value|
57
+ if value.is_a?(Hash)
58
+ key
59
+ else
60
+ key = key.to_s
61
+ key.split('.').first if key.include?('.')
62
+ end
63
+ end.compact
64
+ end
65
+
66
+ private
67
+ def self.build(attribute, value)
68
+ case value
69
+ when Array
70
+ values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
71
+ ranges, values = values.partition {|v| v.is_a?(Range)}
72
+
73
+ values_predicate = if values.include?(nil)
74
+ values = values.compact
75
+
76
+ case values.length
77
+ when 0
78
+ attribute.eq(nil)
79
+ when 1
80
+ attribute.eq(values.first).or(attribute.eq(nil))
81
+ else
82
+ attribute.in(values).or(attribute.eq(nil))
83
+ end
84
+ else
85
+ attribute.in(values)
86
+ end
87
+
88
+ array_predicates = ranges.map { |range| attribute.in(range) }
89
+ array_predicates << values_predicate
90
+ array_predicates.inject { |composite, predicate| composite.or(predicate) }
91
+ when ActiveRecord::Relation
92
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
93
+ attribute.in(value.arel.ast)
94
+ when Range
95
+ attribute.in(value)
96
+ when ActiveRecord::Base
97
+ attribute.eq(value.id)
98
+ when Class
99
+ # FIXME: I think we need to deprecate this behavior
100
+ attribute.eq(value.name)
101
+ else
102
+ attribute.eq(value)
103
+ end
104
+ end
105
+ end
106
+ end