activerecord 3.2.22.5 → 4.2.11.3

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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,111 @@
1
+ module ActiveRecord
2
+
3
+ # Statement cache is used to cache a single statement in order to avoid creating the AST again.
4
+ # Initializing the cache is done by passing the statement in the create block:
5
+ #
6
+ # cache = StatementCache.create(Book.connection) do |params|
7
+ # Book.where(name: "my book").where("author_id > 3")
8
+ # end
9
+ #
10
+ # The cached statement is executed by using the +execute+ method:
11
+ #
12
+ # cache.execute([], Book, Book.connection)
13
+ #
14
+ # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
15
+ # Database is queried when +to_a+ is called on the relation.
16
+ #
17
+ # If you want to cache the statement without the values you can use the +bind+ method of the
18
+ # block parameter.
19
+ #
20
+ # cache = StatementCache.create(Book.connection) do |params|
21
+ # Book.where(name: params.bind)
22
+ # end
23
+ #
24
+ # And pass the bind values as the first argument of +execute+ call.
25
+ #
26
+ # cache.execute(["my book"], Book, Book.connection)
27
+ class StatementCache # :nodoc:
28
+ class Substitute; end # :nodoc:
29
+
30
+ class Query # :nodoc:
31
+ def initialize(sql)
32
+ @sql = sql
33
+ end
34
+
35
+ def sql_for(binds, connection)
36
+ @sql
37
+ end
38
+ end
39
+
40
+ class PartialQuery < Query # :nodoc:
41
+ def initialize values
42
+ @values = values
43
+ @indexes = values.each_with_index.find_all { |thing,i|
44
+ Arel::Nodes::BindParam === thing
45
+ }.map(&:last)
46
+ end
47
+
48
+ def sql_for(binds, connection)
49
+ val = @values.dup
50
+ binds = binds.dup
51
+ @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
52
+ val.join
53
+ end
54
+ end
55
+
56
+ def self.query(visitor, ast)
57
+ Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
58
+ end
59
+
60
+ def self.partial_query(visitor, ast, collector)
61
+ collected = visitor.accept(ast, collector).value
62
+ PartialQuery.new collected
63
+ end
64
+
65
+ class Params # :nodoc:
66
+ def bind; Substitute.new; end
67
+ end
68
+
69
+ class BindMap # :nodoc:
70
+ def initialize(bind_values)
71
+ @indexes = []
72
+ @bind_values = bind_values
73
+
74
+ bind_values.each_with_index do |(_, value), i|
75
+ if Substitute === value
76
+ @indexes << i
77
+ end
78
+ end
79
+ end
80
+
81
+ def bind(values)
82
+ bvs = @bind_values.map { |pair| pair.dup }
83
+ @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
84
+ bvs
85
+ end
86
+ end
87
+
88
+ attr_reader :bind_map, :query_builder
89
+
90
+ def self.create(connection, block = Proc.new)
91
+ relation = block.call Params.new
92
+ bind_map = BindMap.new relation.bind_values
93
+ query_builder = connection.cacheable_query relation.arel
94
+ new query_builder, bind_map
95
+ end
96
+
97
+ def initialize(query_builder, bind_map)
98
+ @query_builder = query_builder
99
+ @bind_map = bind_map
100
+ end
101
+
102
+ def execute(params, klass, connection)
103
+ bind_values = bind_map.bind params
104
+
105
+ sql = query_builder.sql_for bind_values, connection
106
+
107
+ klass.find_by_sql sql, bind_values
108
+ end
109
+ alias :call :execute
110
+ end
111
+ end
@@ -1,6 +1,8 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
1
3
  module ActiveRecord
2
4
  # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
3
- # It's like a simple key/value store backed into your record when you don't care about being able to
5
+ # It's like a simple key/value store baked into your record when you don't care about being able to
4
6
  # query that store outside the context of a single record.
5
7
  #
6
8
  # You can then declare accessors to this store that are then accessible just like any other attribute
@@ -10,43 +12,194 @@ module ActiveRecord
10
12
  # Make sure that you declare the database column used for the serialized store as a text, so there's
11
13
  # plenty of room.
12
14
  #
15
+ # You can set custom coder to encode/decode your serialized attributes to/from different formats.
16
+ # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
17
+ #
18
+ # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
19
+ # the serialization provided by +store+. Simply use +store_accessor+ instead to generate
20
+ # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
21
+ # using a symbol.
22
+ #
13
23
  # Examples:
14
24
  #
15
25
  # class User < ActiveRecord::Base
16
- # store :settings, accessors: [ :color, :homepage ]
26
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
17
27
  # end
18
- #
28
+ #
19
29
  # u = User.new(color: 'black', homepage: '37signals.com')
20
30
  # u.color # Accessor stored attribute
21
31
  # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
22
32
  #
33
+ # # There is no difference between strings and symbols for accessing custom attributes
34
+ # u.settings[:country] # => 'Denmark'
35
+ # u.settings['country'] # => 'Denmark'
36
+ #
23
37
  # # Add additional accessors to an existing store through store_accessor
24
38
  # class SuperUser < User
25
39
  # store_accessor :settings, :privileges, :servants
26
40
  # end
41
+ #
42
+ # The stored attribute names can be retrieved using +stored_attributes+.
43
+ #
44
+ # User.stored_attributes[:settings] # [:color, :homepage]
45
+ #
46
+ # == Overwriting default accessors
47
+ #
48
+ # All stored values are automatically available through accessors on the Active Record
49
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
50
+ # the default accessors (using the same name as the attribute) and calling <tt>super</tt>
51
+ # to actually change things.
52
+ #
53
+ # class Song < ActiveRecord::Base
54
+ # # Uses a stored integer to hold the volume adjustment of the song
55
+ # store :settings, accessors: [:volume_adjustment]
56
+ #
57
+ # def volume_adjustment=(decibels)
58
+ # super(decibels.to_i)
59
+ # end
60
+ #
61
+ # def volume_adjustment
62
+ # super.to_i
63
+ # end
64
+ # end
27
65
  module Store
28
66
  extend ActiveSupport::Concern
29
-
67
+
68
+ included do
69
+ class << self
70
+ attr_accessor :local_stored_attributes
71
+ end
72
+ end
73
+
30
74
  module ClassMethods
31
75
  def store(store_attribute, options = {})
32
- serialize store_attribute, Hash
76
+ serialize store_attribute, IndifferentCoder.new(options[:coder])
33
77
  store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
34
78
  end
35
79
 
36
80
  def store_accessor(store_attribute, *keys)
37
- Array(keys).flatten.each do |key|
38
- define_method("#{key}=") do |value|
39
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
40
- send("#{store_attribute}_will_change!")
41
- send(store_attribute)[key] = value
81
+ keys = keys.flatten
82
+
83
+ _store_accessors_module.module_eval do
84
+ keys.each do |key|
85
+ define_method("#{key}=") do |value|
86
+ write_store_attribute(store_attribute, key, value)
87
+ end
88
+
89
+ define_method(key) do
90
+ read_store_attribute(store_attribute, key)
91
+ end
42
92
  end
43
-
44
- define_method(key) do
45
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
46
- send(store_attribute)[key]
93
+ end
94
+
95
+ # assign new store attribute and create new hash to ensure that each class in the hierarchy
96
+ # has its own hash of stored attributes.
97
+ self.local_stored_attributes ||= {}
98
+ self.local_stored_attributes[store_attribute] ||= []
99
+ self.local_stored_attributes[store_attribute] |= keys
100
+ end
101
+
102
+ def _store_accessors_module # :nodoc:
103
+ @_store_accessors_module ||= begin
104
+ mod = Module.new
105
+ include mod
106
+ mod
107
+ end
108
+ end
109
+
110
+ def stored_attributes
111
+ parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
112
+ if self.local_stored_attributes
113
+ parent.merge!(self.local_stored_attributes) { |k, a, b| a | b }
114
+ end
115
+ parent
116
+ end
117
+ end
118
+
119
+ protected
120
+ def read_store_attribute(store_attribute, key)
121
+ accessor = store_accessor_for(store_attribute)
122
+ accessor.read(self, store_attribute, key)
123
+ end
124
+
125
+ def write_store_attribute(store_attribute, key, value)
126
+ accessor = store_accessor_for(store_attribute)
127
+ accessor.write(self, store_attribute, key, value)
128
+ end
129
+
130
+ private
131
+ def store_accessor_for(store_attribute)
132
+ type_for_attribute(store_attribute.to_s).accessor
133
+ end
134
+
135
+ class HashAccessor # :nodoc:
136
+ def self.read(object, attribute, key)
137
+ prepare(object, attribute)
138
+ object.public_send(attribute)[key]
139
+ end
140
+
141
+ def self.write(object, attribute, key, value)
142
+ prepare(object, attribute)
143
+ if value != read(object, attribute, key)
144
+ object.public_send :"#{attribute}_will_change!"
145
+ object.public_send(attribute)[key] = value
47
146
  end
48
147
  end
148
+
149
+ def self.prepare(object, attribute)
150
+ object.public_send :"#{attribute}=", {} unless object.send(attribute)
151
+ end
152
+ end
153
+
154
+ class StringKeyedHashAccessor < HashAccessor # :nodoc:
155
+ def self.read(object, attribute, key)
156
+ super object, attribute, key.to_s
157
+ end
158
+
159
+ def self.write(object, attribute, key, value)
160
+ super object, attribute, key.to_s, value
161
+ end
162
+ end
163
+
164
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
165
+ def self.prepare(object, store_attribute)
166
+ attribute = object.send(store_attribute)
167
+ unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
168
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
169
+ object.send :"#{store_attribute}=", attribute
170
+ end
171
+ attribute
172
+ end
173
+ end
174
+
175
+ class IndifferentCoder # :nodoc:
176
+ def initialize(coder_or_class_name)
177
+ @coder =
178
+ if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
179
+ coder_or_class_name
180
+ else
181
+ ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
182
+ end
183
+ end
184
+
185
+ def dump(obj)
186
+ @coder.dump self.class.as_indifferent_hash(obj)
187
+ end
188
+
189
+ def load(yaml)
190
+ self.class.as_indifferent_hash(@coder.load(yaml || ''))
191
+ end
192
+
193
+ def self.as_indifferent_hash(obj)
194
+ case obj
195
+ when ActiveSupport::HashWithIndifferentAccess
196
+ obj
197
+ when Hash
198
+ obj.with_indifferent_access
199
+ else
200
+ ActiveSupport::HashWithIndifferentAccess.new
201
+ end
49
202
  end
50
203
  end
51
204
  end
52
- end
205
+ end
@@ -0,0 +1,299 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
3
+ module ActiveRecord
4
+ module Tasks # :nodoc:
5
+ class DatabaseAlreadyExists < StandardError; end # :nodoc:
6
+ class DatabaseNotSupported < StandardError; end # :nodoc:
7
+
8
+ # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
9
+ # logic behind common tasks used to manage database and migrations.
10
+ #
11
+ # The tasks defined here are used with Rake tasks provided by Active Record.
12
+ #
13
+ # In order to use DatabaseTasks, a few config values need to be set. All the needed
14
+ # config values are set by Rails already, so it's necessary to do it only if you
15
+ # want to change the defaults or when you want to use Active Record outside of Rails
16
+ # (in such case after configuring the database tasks, you can also use the rake tasks
17
+ # defined in Active Record).
18
+ #
19
+ # The possible config values are:
20
+ #
21
+ # * +env+: current environment (like Rails.env).
22
+ # * +database_configuration+: configuration of your databases (as in +config/database.yml+).
23
+ # * +db_dir+: your +db+ directory.
24
+ # * +fixtures_path+: a path to fixtures directory.
25
+ # * +migrations_paths+: a list of paths to directories with migrations.
26
+ # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
27
+ # * +root+: a path to the root of the application.
28
+ #
29
+ # Example usage of +DatabaseTasks+ outside Rails could look as such:
30
+ #
31
+ # include ActiveRecord::Tasks
32
+ # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
33
+ # DatabaseTasks.db_dir = 'db'
34
+ # # other settings...
35
+ #
36
+ # DatabaseTasks.create_current('production')
37
+ module DatabaseTasks
38
+ extend self
39
+
40
+ attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
41
+ attr_accessor :database_configuration
42
+
43
+ LOCAL_HOSTS = ['127.0.0.1', 'localhost']
44
+
45
+ def register_task(pattern, task)
46
+ @tasks ||= {}
47
+ @tasks[pattern] = task
48
+ end
49
+
50
+ register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
51
+ register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
52
+ register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
53
+
54
+ def db_dir
55
+ @db_dir ||= Rails.application.config.paths["db"].first
56
+ end
57
+
58
+ def migrations_paths
59
+ @migrations_paths ||= Rails.application.paths['db/migrate'].to_a
60
+ end
61
+
62
+ def fixtures_path
63
+ @fixtures_path ||= if ENV['FIXTURES_PATH']
64
+ File.join(root, ENV['FIXTURES_PATH'])
65
+ else
66
+ File.join(root, 'test', 'fixtures')
67
+ end
68
+ end
69
+
70
+ def root
71
+ @root ||= Rails.root
72
+ end
73
+
74
+ def env
75
+ @env ||= Rails.env
76
+ end
77
+
78
+ def seed_loader
79
+ @seed_loader ||= Rails.application
80
+ end
81
+
82
+ def current_config(options = {})
83
+ options.reverse_merge! :env => env
84
+ if options.has_key?(:config)
85
+ @current_config = options[:config]
86
+ else
87
+ @current_config ||= ActiveRecord::Base.configurations[options[:env]]
88
+ end
89
+ end
90
+
91
+ def create(*arguments)
92
+ configuration = arguments.first
93
+ class_for_adapter(configuration['adapter']).new(*arguments).create
94
+ rescue DatabaseAlreadyExists
95
+ $stderr.puts "#{configuration['database']} already exists"
96
+ rescue Exception => error
97
+ $stderr.puts error, *(error.backtrace)
98
+ $stderr.puts "Couldn't create database for #{configuration.inspect}"
99
+ end
100
+
101
+ def create_all
102
+ each_local_configuration { |configuration| create configuration }
103
+ end
104
+
105
+ def create_current(environment = env)
106
+ each_current_configuration(environment) { |configuration|
107
+ create configuration
108
+ }
109
+ ActiveRecord::Base.establish_connection(environment.to_sym)
110
+ end
111
+
112
+ def drop(*arguments)
113
+ configuration = arguments.first
114
+ class_for_adapter(configuration['adapter']).new(*arguments).drop
115
+ rescue ActiveRecord::NoDatabaseError
116
+ $stderr.puts "Database '#{configuration['database']}' does not exist"
117
+ rescue Exception => error
118
+ $stderr.puts error, *(error.backtrace)
119
+ $stderr.puts "Couldn't drop #{configuration['database']}"
120
+ end
121
+
122
+ def drop_all
123
+ each_local_configuration { |configuration| drop configuration }
124
+ end
125
+
126
+ def drop_current(environment = env)
127
+ each_current_configuration(environment) { |configuration|
128
+ drop configuration
129
+ }
130
+ end
131
+
132
+ def migrate
133
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
134
+
135
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
136
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
137
+ scope = ENV['SCOPE']
138
+ verbose_was, Migration.verbose = Migration.verbose, verbose
139
+ Migrator.migrate(migrations_paths, version) do |migration|
140
+ scope.blank? || scope == migration.scope
141
+ end
142
+ ActiveRecord::Base.clear_cache!
143
+ ensure
144
+ Migration.verbose = verbose_was
145
+ end
146
+
147
+ def charset_current(environment = env)
148
+ charset ActiveRecord::Base.configurations[environment]
149
+ end
150
+
151
+ def charset(*arguments)
152
+ configuration = arguments.first
153
+ class_for_adapter(configuration['adapter']).new(*arguments).charset
154
+ end
155
+
156
+ def collation_current(environment = env)
157
+ collation ActiveRecord::Base.configurations[environment]
158
+ end
159
+
160
+ def collation(*arguments)
161
+ configuration = arguments.first
162
+ class_for_adapter(configuration['adapter']).new(*arguments).collation
163
+ end
164
+
165
+ def purge(configuration)
166
+ class_for_adapter(configuration['adapter']).new(configuration).purge
167
+ end
168
+
169
+ def purge_all
170
+ each_local_configuration { |configuration|
171
+ purge configuration
172
+ }
173
+ end
174
+
175
+ def purge_current(environment = env)
176
+ each_current_configuration(environment) { |configuration|
177
+ purge configuration
178
+ }
179
+ ActiveRecord::Base.establish_connection(environment.to_sym)
180
+ end
181
+
182
+ def structure_dump(*arguments)
183
+ configuration = arguments.first
184
+ filename = arguments.delete_at 1
185
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_dump(filename)
186
+ end
187
+
188
+ def structure_load(*arguments)
189
+ configuration = arguments.first
190
+ filename = arguments.delete_at 1
191
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
192
+ end
193
+
194
+ def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
195
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
196
+ This method will act on a specific connection in the future.
197
+ To act on the current connection, use `load_schema_current` instead.
198
+ MSG
199
+
200
+ load_schema_current(format, file)
201
+ end
202
+
203
+ def schema_file(format = ActiveRecord::Base.schema_format)
204
+ case format
205
+ when :ruby
206
+ File.join(db_dir, "schema.rb")
207
+ when :sql
208
+ File.join(db_dir, "structure.sql")
209
+ end
210
+ end
211
+
212
+ # This method is the successor of +load_schema+. We should rename it
213
+ # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
214
+ def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
215
+ file ||= schema_file(format)
216
+
217
+ case format
218
+ when :ruby
219
+ check_schema_file(file)
220
+ ActiveRecord::Base.establish_connection(configuration)
221
+ load(file)
222
+ when :sql
223
+ check_schema_file(file)
224
+ structure_load(configuration, file)
225
+ else
226
+ raise ArgumentError, "unknown format #{format.inspect}"
227
+ end
228
+ end
229
+
230
+ def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
231
+ if File.exist?(file || schema_file(format))
232
+ load_schema_current(format, file, environment)
233
+ end
234
+ end
235
+
236
+ def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
237
+ each_current_configuration(environment) { |configuration|
238
+ load_schema_for configuration, format, file
239
+ }
240
+ ActiveRecord::Base.establish_connection(environment.to_sym)
241
+ end
242
+
243
+ def check_schema_file(filename)
244
+ unless File.exist?(filename)
245
+ message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
246
+ message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails)
247
+ Kernel.abort message
248
+ end
249
+ end
250
+
251
+ def load_seed
252
+ if seed_loader
253
+ seed_loader.load_seed
254
+ else
255
+ raise "You tried to load seed data, but no seed loader is specified. Please specify seed " +
256
+ "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" +
257
+ "Seed loader should respond to load_seed method"
258
+ end
259
+ end
260
+
261
+ private
262
+
263
+ def class_for_adapter(adapter)
264
+ key = @tasks.keys.detect { |pattern| adapter[pattern] }
265
+ unless key
266
+ raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter"
267
+ end
268
+ @tasks[key]
269
+ end
270
+
271
+ def each_current_configuration(environment)
272
+ environments = [environment]
273
+ # add test environment only if no RAILS_ENV was specified.
274
+ environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
275
+
276
+ configurations = ActiveRecord::Base.configurations.values_at(*environments)
277
+ configurations.compact.each do |configuration|
278
+ yield configuration unless configuration['database'].blank?
279
+ end
280
+ end
281
+
282
+ def each_local_configuration
283
+ ActiveRecord::Base.configurations.each_value do |configuration|
284
+ next unless configuration['database']
285
+
286
+ if local_database?(configuration)
287
+ yield configuration
288
+ else
289
+ $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host."
290
+ end
291
+ end
292
+ end
293
+
294
+ def local_database?(configuration)
295
+ configuration['host'].blank? || LOCAL_HOSTS.include?(configuration['host'])
296
+ end
297
+ end
298
+ end
299
+ end