activerecord 3.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,175 @@
1
+ require 'active_support/core_ext/array'
2
+ require 'active_support/core_ext/hash/except'
3
+ require 'active_support/core_ext/kernel/singleton_class'
4
+
5
+ module ActiveRecord
6
+ # = Active Record \Named \Scopes
7
+ module Scoping
8
+ module Named
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods
12
+ # Returns an <tt>ActiveRecord::Relation</tt> scope object.
13
+ #
14
+ # posts = Post.all
15
+ # posts.size # Fires "select count(*) from posts" and returns the count
16
+ # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
17
+ #
18
+ # fruits = Fruit.all
19
+ # fruits = fruits.where(color: 'red') if options[:red_only]
20
+ # fruits = fruits.limit(10) if limited?
21
+ #
22
+ # You can define a scope that applies to all finders using
23
+ # <tt>ActiveRecord::Base.default_scope</tt>.
24
+ def all
25
+ if current_scope
26
+ current_scope.clone
27
+ else
28
+ scope = relation
29
+ scope.default_scoped = true
30
+ scope
31
+ end
32
+ end
33
+
34
+ # Collects attributes from scopes that should be applied when creating
35
+ # an AR instance for the particular class this is called on.
36
+ def scope_attributes # :nodoc:
37
+ if current_scope
38
+ current_scope.scope_for_create
39
+ else
40
+ scope = relation
41
+ scope.default_scoped = true
42
+ scope.scope_for_create
43
+ end
44
+ end
45
+
46
+ # Are there default attributes associated with this scope?
47
+ def scope_attributes? # :nodoc:
48
+ current_scope || default_scopes.any?
49
+ end
50
+
51
+ # Adds a class method for retrieving and querying objects. A \scope
52
+ # represents a narrowing of a database query, such as
53
+ # <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
54
+ #
55
+ # class Shirt < ActiveRecord::Base
56
+ # scope :red, -> { where(color: 'red') }
57
+ # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
58
+ # end
59
+ #
60
+ # The above calls to +scope+ define class methods <tt>Shirt.red</tt> and
61
+ # <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
62
+ # represents the query <tt>Shirt.where(color: 'red')</tt>.
63
+ #
64
+ # You should always pass a callable object to the scopes defined
65
+ # with +scope+. This ensures that the scope is re-evaluated each
66
+ # time it is called.
67
+ #
68
+ # Note that this is simply 'syntactic sugar' for defining an actual
69
+ # class method:
70
+ #
71
+ # class Shirt < ActiveRecord::Base
72
+ # def self.red
73
+ # where(color: 'red')
74
+ # end
75
+ # end
76
+ #
77
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
78
+ # <tt>Shirt.red</tt> is not an Array; it resembles the association object
79
+ # constructed by a +has_many+ declaration. For instance, you can invoke
80
+ # <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
81
+ # <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
82
+ # association objects, named \scopes act like an Array, implementing
83
+ # Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
84
+ # and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
85
+ # <tt>Shirt.red</tt> really was an Array.
86
+ #
87
+ # These named \scopes are composable. For instance,
88
+ # <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
89
+ # both red and dry clean only. Nested finds and calculations also work
90
+ # with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
91
+ # returns the number of garments for which these criteria obtain.
92
+ # Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
93
+ #
94
+ # All scopes are available as class methods on the ActiveRecord::Base
95
+ # descendant upon which the \scopes were defined. But they are also
96
+ # available to +has_many+ associations. If,
97
+ #
98
+ # class Person < ActiveRecord::Base
99
+ # has_many :shirts
100
+ # end
101
+ #
102
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
103
+ # Elton's red, dry clean only shirts.
104
+ #
105
+ # \Named scopes can also have extensions, just as with +has_many+
106
+ # declarations:
107
+ #
108
+ # class Shirt < ActiveRecord::Base
109
+ # scope :red, -> { where(color: 'red') } do
110
+ # def dom_id
111
+ # 'red_shirts'
112
+ # end
113
+ # end
114
+ # end
115
+ #
116
+ # Scopes can also be used while creating/building a record.
117
+ #
118
+ # class Article < ActiveRecord::Base
119
+ # scope :published, -> { where(published: true) }
120
+ # end
121
+ #
122
+ # Article.published.new.published # => true
123
+ # Article.published.create.published # => true
124
+ #
125
+ # \Class methods on your model are automatically available
126
+ # on scopes. Assuming the following setup:
127
+ #
128
+ # class Article < ActiveRecord::Base
129
+ # scope :published, -> { where(published: true) }
130
+ # scope :featured, -> { where(featured: true) }
131
+ #
132
+ # def self.latest_article
133
+ # order('published_at desc').first
134
+ # end
135
+ #
136
+ # def self.titles
137
+ # pluck(:title)
138
+ # end
139
+ # end
140
+ #
141
+ # We are able to call the methods like this:
142
+ #
143
+ # Article.published.featured.latest_article
144
+ # Article.featured.titles
145
+ def scope(name, body, &block)
146
+ extension = Module.new(&block) if block
147
+
148
+ # Check body.is_a?(Relation) to prevent the relation actually being
149
+ # loaded by respond_to?
150
+ if body.is_a?(Relation) || !body.respond_to?(:call)
151
+ ActiveSupport::Deprecation.warn(
152
+ "Using #scope without passing a callable object is deprecated. For " \
153
+ "example `scope :red, where(color: 'red')` should be changed to " \
154
+ "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \
155
+ "in the former usage and it makes the implementation more complicated " \
156
+ "and buggy. (If you prefer, you can just define a class method named " \
157
+ "`self.red`.)"
158
+ )
159
+ end
160
+
161
+ singleton_class.send(:define_method, name) do |*args|
162
+ if body.respond_to?(:call)
163
+ scope = all.scoping { body.call(*args) }
164
+ scope = scope.extending(extension) if extension
165
+ else
166
+ scope = body
167
+ end
168
+
169
+ scope || all
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,82 @@
1
+ require 'active_support/per_thread_registry'
2
+
3
+ module ActiveRecord
4
+ module Scoping
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include Default
9
+ include Named
10
+ end
11
+
12
+ module ClassMethods
13
+ def current_scope #:nodoc:
14
+ ScopeRegistry.value_for(:current_scope, base_class.to_s)
15
+ end
16
+
17
+ def current_scope=(scope) #:nodoc:
18
+ ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
19
+ end
20
+ end
21
+
22
+ def populate_with_current_scope_attributes
23
+ return unless self.class.scope_attributes?
24
+
25
+ self.class.scope_attributes.each do |att,value|
26
+ send("#{att}=", value) if respond_to?("#{att}=")
27
+ end
28
+ end
29
+
30
+ # This class stores the +:current_scope+ and +:ignore_default_scope+ values
31
+ # for different classes. The registry is stored as a thread local, which is
32
+ # accessed through +ScopeRegistry.current+.
33
+ #
34
+ # This class allows you to store and get the scope values on different
35
+ # classes and different types of scopes. For example, if you are attempting
36
+ # to get the current_scope for the +Board+ model, then you would use the
37
+ # following code:
38
+ #
39
+ # registry = ActiveRecord::Scoping::ScopeRegistry
40
+ # registry.set_value_for(:current_scope, "Board", some_new_scope)
41
+ #
42
+ # Now when you run:
43
+ #
44
+ # registry.value_for(:current_scope, "Board")
45
+ #
46
+ # You will obtain whatever was defined in +some_new_scope+. The +value_for+
47
+ # and +set_value_for+ methods are delegated to the current +ScopeRegistry+
48
+ # object, so the above example code can also be called as:
49
+ #
50
+ # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
51
+ # "Board", some_new_scope)
52
+ class ScopeRegistry # :nodoc:
53
+ extend ActiveSupport::PerThreadRegistry
54
+
55
+ VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
56
+
57
+ def initialize
58
+ @registry = Hash.new { |hash, key| hash[key] = {} }
59
+ end
60
+
61
+ # Obtains the value for a given +scope_name+ and +variable_name+.
62
+ def value_for(scope_type, variable_name)
63
+ raise_invalid_scope_type!(scope_type)
64
+ @registry[scope_type][variable_name]
65
+ end
66
+
67
+ # Sets the +value+ for a given +scope_type+ and +variable_name+.
68
+ def set_value_for(scope_type, variable_name, value)
69
+ raise_invalid_scope_type!(scope_type)
70
+ @registry[scope_type][variable_name] = value
71
+ end
72
+
73
+ private
74
+
75
+ def raise_invalid_scope_type!(scope_type)
76
+ if !VALID_SCOPE_TYPES.include?(scope_type)
77
+ raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -4,56 +4,18 @@ module ActiveRecord #:nodoc:
4
4
  extend ActiveSupport::Concern
5
5
  include ActiveModel::Serializers::JSON
6
6
 
7
- def serializable_hash(options = nil)
8
- options ||= {}
9
-
10
- options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
11
- options[:except] |= Array.wrap(self.class.inheritance_column)
12
-
13
- hash = super(options)
14
-
15
- serializable_add_includes(options) do |association, records, opts|
16
- hash[association] = records.is_a?(Enumerable) ?
17
- records.map { |r| r.serializable_hash(opts) } :
18
- records.serializable_hash(opts)
19
- end
20
-
21
- hash
7
+ included do
8
+ self.include_root_in_json = false
22
9
  end
23
10
 
24
- private
25
- # Add associations specified via the <tt>:includes</tt> option.
26
- #
27
- # Expects a block that takes as arguments:
28
- # +association+ - name of the association
29
- # +records+ - the association record(s) to be serialized
30
- # +opts+ - options for the association records
31
- def serializable_add_includes(options = {})
32
- return unless include_associations = options.delete(:include)
33
-
34
- base_only_or_except = { :except => options[:except],
35
- :only => options[:only] }
36
-
37
- include_has_options = include_associations.is_a?(Hash)
38
- associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
39
-
40
- for association in associations
41
- records = case self.class.reflect_on_association(association).macro
42
- when :has_many, :has_and_belongs_to_many
43
- send(association).to_a
44
- when :has_one, :belongs_to
45
- send(association)
46
- end
11
+ def serializable_hash(options = nil)
12
+ options = options.try(:clone) || {}
47
13
 
48
- unless records.nil?
49
- association_options = include_has_options ? include_associations[association] : base_only_or_except
50
- opts = options.merge(association_options)
51
- yield(association, records, opts)
52
- end
53
- end
14
+ options[:except] = Array(options[:except]).map { |n| n.to_s }
15
+ options[:except] |= Array(self.class.inheritance_column)
54
16
 
55
- options[:include] = include_associations
56
- end
17
+ super(options)
18
+ end
57
19
  end
58
20
  end
59
21
 
@@ -1,4 +1,3 @@
1
- require 'active_support/core_ext/array/wrap'
2
1
  require 'active_support/core_ext/hash/conversions'
3
2
 
4
3
  module ActiveRecord #:nodoc:
@@ -19,8 +18,8 @@ module ActiveRecord #:nodoc:
19
18
  # <id type="integer">1</id>
20
19
  # <approved type="boolean">false</approved>
21
20
  # <replies-count type="integer">0</replies-count>
22
- # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
23
- # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
21
+ # <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
22
+ # <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
24
23
  # <content>Have a nice day</content>
25
24
  # <author-email-address>david@loudthinking.com</author-email-address>
26
25
  # <parent-id></parent-id>
@@ -37,7 +36,7 @@ module ActiveRecord #:nodoc:
37
36
  #
38
37
  # For instance:
39
38
  #
40
- # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
39
+ # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
41
40
  #
42
41
  # <topic>
43
42
  # <title>The First Topic</title>
@@ -51,7 +50,7 @@ module ActiveRecord #:nodoc:
51
50
  #
52
51
  # To include first level associations use <tt>:include</tt>:
53
52
  #
54
- # firm.to_xml :include => [ :account, :clients ]
53
+ # firm.to_xml include: [ :account, :clients ]
55
54
  #
56
55
  # <?xml version="1.0" encoding="UTF-8"?>
57
56
  # <firm>
@@ -75,14 +74,14 @@ module ActiveRecord #:nodoc:
75
74
  # </firm>
76
75
  #
77
76
  # Additionally, the record being serialized will be passed to a Proc's second
78
- # parameter. This allows for ad hoc additions to the resultant document that
77
+ # parameter. This allows for ad hoc additions to the resultant document that
79
78
  # incorporate the context of the record being serialized. And by leveraging the
80
79
  # closure created by a Proc, to_xml can be used to add elements that normally fall
81
80
  # outside of the scope of the model -- for example, generating and appending URLs
82
81
  # associated with models.
83
82
  #
84
83
  # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
85
- # firm.to_xml :procs => [ proc ]
84
+ # firm.to_xml procs: [ proc ]
86
85
  #
87
86
  # <firm>
88
87
  # # ... normal attributes as shown above ...
@@ -91,7 +90,7 @@ module ActiveRecord #:nodoc:
91
90
  #
92
91
  # To include deeper levels of associations pass a hash like this:
93
92
  #
94
- # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
93
+ # firm.to_xml include: {account: {}, clients: {include: :address}}
95
94
  # <?xml version="1.0" encoding="UTF-8"?>
96
95
  # <firm>
97
96
  # <id type="integer">1</id>
@@ -121,7 +120,7 @@ module ActiveRecord #:nodoc:
121
120
  #
122
121
  # To include any methods on the model being called use <tt>:methods</tt>:
123
122
  #
124
- # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
123
+ # firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
125
124
  #
126
125
  # <firm>
127
126
  # # ... normal attributes as shown above ...
@@ -133,7 +132,7 @@ module ActiveRecord #:nodoc:
133
132
  # modified version of the options hash that was given to +to_xml+:
134
133
  #
135
134
  # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
136
- # firm.to_xml :procs => [ proc ]
135
+ # firm.to_xml procs: [ proc ]
137
136
  #
138
137
  # <firm>
139
138
  # # ... normal attributes as shown above ...
@@ -163,8 +162,9 @@ module ActiveRecord #:nodoc:
163
162
  #
164
163
  # class IHaveMyOwnXML < ActiveRecord::Base
165
164
  # def to_xml(options = {})
165
+ # require 'builder'
166
166
  # options[:indent] ||= 2
167
- # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
167
+ # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
168
168
  # xml.instruct! unless options[:skip_instruct]
169
169
  # xml.level_one do
170
170
  # xml.tag!(:second_level, 'content')
@@ -177,66 +177,19 @@ module ActiveRecord #:nodoc:
177
177
  end
178
178
 
179
179
  class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
180
- def initialize(*args)
181
- super
182
- options[:except] |= Array.wrap(@serializable.class.inheritance_column)
183
- end
184
-
185
- def add_extra_behavior
186
- add_includes
187
- end
188
-
189
- def add_includes
190
- procs = options.delete(:procs)
191
- @serializable.send(:serializable_add_includes, options) do |association, records, opts|
192
- add_associations(association, records, opts)
193
- end
194
- options[:procs] = procs
195
- end
196
-
197
- # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
198
- def add_associations(association, records, opts)
199
- association_name = association.to_s.singularize
200
- merged_options = options.merge(opts).merge!(:root => association_name, :skip_instruct => true)
201
-
202
- if records.is_a?(Enumerable)
203
- tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
204
- type = options[:skip_types] ? { } : {:type => "array"}
205
-
206
- if records.empty?
207
- @builder.tag!(tag, type)
208
- else
209
- @builder.tag!(tag, type) do
210
- records.each do |record|
211
- if options[:skip_types]
212
- record_type = {}
213
- else
214
- record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
215
- record_type = {:type => record_class}
216
- end
217
-
218
- record.to_xml merged_options.merge(record_type)
219
- end
220
- end
221
- end
222
- elsif record = @serializable.send(association)
223
- record.to_xml(merged_options)
224
- end
225
- end
226
-
227
180
  class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
228
181
  def compute_type
229
- type = @serializable.class.serialized_attributes.has_key?(name) ?
230
- super : @serializable.class.columns_hash[name].type
182
+ klass = @serializable.class
183
+ type = if klass.serialized_attributes.key?(name)
184
+ super
185
+ elsif klass.columns_hash.key?(name)
186
+ klass.columns_hash[name].type
187
+ else
188
+ NilClass
189
+ end
231
190
 
232
- case type
233
- when :text
234
- :string
235
- when :time
236
- :datetime
237
- else
238
- type
239
- end
191
+ { :text => :string,
192
+ :time => :datetime }[type] || type
240
193
  end
241
194
  protected :compute_type
242
195
  end
@@ -0,0 +1,26 @@
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 initialization block:
5
+ #
6
+ # cache = ActiveRecord::StatementCache.new do
7
+ # Book.where(name: "my book").limit(100)
8
+ # end
9
+ #
10
+ # The cached statement is executed by using the +execute+ method:
11
+ #
12
+ # cache.execute
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
+ class StatementCache
17
+ def initialize
18
+ @relation = yield
19
+ raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
20
+ end
21
+
22
+ def execute
23
+ @relation.dup.to_a
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,156 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module ActiveRecord
4
+ # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
5
+ # It's like a simple key/value store baked into your record when you don't care about being able to
6
+ # query that store outside the context of a single record.
7
+ #
8
+ # You can then declare accessors to this store that are then accessible just like any other attribute
9
+ # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
10
+ # already built around just accessing attributes on the model.
11
+ #
12
+ # Make sure that you declare the database column used for the serialized store as a text, so there's
13
+ # plenty of room.
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
+ # Examples:
19
+ #
20
+ # class User < ActiveRecord::Base
21
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
22
+ # end
23
+ #
24
+ # u = User.new(color: 'black', homepage: '37signals.com')
25
+ # u.color # Accessor stored attribute
26
+ # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
27
+ #
28
+ # # There is no difference between strings and symbols for accessing custom attributes
29
+ # u.settings[:country] # => 'Denmark'
30
+ # u.settings['country'] # => 'Denmark'
31
+ #
32
+ # # Add additional accessors to an existing store through store_accessor
33
+ # class SuperUser < User
34
+ # store_accessor :settings, :privileges, :servants
35
+ # end
36
+ #
37
+ # The stored attribute names can be retrieved using +stored_attributes+.
38
+ #
39
+ # User.stored_attributes[:settings] # [:color, :homepage]
40
+ #
41
+ # == Overwriting default accessors
42
+ #
43
+ # All stored values are automatically available through accessors on the Active Record
44
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
45
+ # the default accessors (using the same name as the attribute) and calling <tt>super</tt>
46
+ # to actually change things.
47
+ #
48
+ # class Song < ActiveRecord::Base
49
+ # # Uses a stored integer to hold the volume adjustment of the song
50
+ # store :settings, accessors: [:volume_adjustment]
51
+ #
52
+ # def volume_adjustment=(decibels)
53
+ # super(decibels.to_i)
54
+ # end
55
+ #
56
+ # def volume_adjustment
57
+ # super.to_i
58
+ # end
59
+ # end
60
+ module Store
61
+ extend ActiveSupport::Concern
62
+
63
+ included do
64
+ class_attribute :stored_attributes, instance_accessor: false
65
+ self.stored_attributes = {}
66
+ end
67
+
68
+ module ClassMethods
69
+ def store(store_attribute, options = {})
70
+ serialize store_attribute, IndifferentCoder.new(options[:coder])
71
+ store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
72
+ end
73
+
74
+ def store_accessor(store_attribute, *keys)
75
+ keys = keys.flatten
76
+
77
+ _store_accessors_module.module_eval do
78
+ keys.each do |key|
79
+ define_method("#{key}=") do |value|
80
+ write_store_attribute(store_attribute, key, value)
81
+ end
82
+
83
+ define_method(key) do
84
+ read_store_attribute(store_attribute, key)
85
+ end
86
+ end
87
+ end
88
+
89
+ self.stored_attributes[store_attribute] ||= []
90
+ self.stored_attributes[store_attribute] |= keys
91
+ end
92
+
93
+ def _store_accessors_module
94
+ @_store_accessors_module ||= begin
95
+ mod = Module.new
96
+ include mod
97
+ mod
98
+ end
99
+ end
100
+ end
101
+
102
+ protected
103
+ def read_store_attribute(store_attribute, key)
104
+ attribute = initialize_store_attribute(store_attribute)
105
+ attribute[key]
106
+ end
107
+
108
+ def write_store_attribute(store_attribute, key, value)
109
+ attribute = initialize_store_attribute(store_attribute)
110
+ if value != attribute[key]
111
+ send :"#{store_attribute}_will_change!"
112
+ attribute[key] = value
113
+ end
114
+ end
115
+
116
+ private
117
+ def initialize_store_attribute(store_attribute)
118
+ attribute = send(store_attribute)
119
+ unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
120
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
121
+ send :"#{store_attribute}=", attribute
122
+ end
123
+ attribute
124
+ end
125
+
126
+ class IndifferentCoder # :nodoc:
127
+ def initialize(coder_or_class_name)
128
+ @coder =
129
+ if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
130
+ coder_or_class_name
131
+ else
132
+ ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
133
+ end
134
+ end
135
+
136
+ def dump(obj)
137
+ @coder.dump self.class.as_indifferent_hash(obj)
138
+ end
139
+
140
+ def load(yaml)
141
+ self.class.as_indifferent_hash @coder.load(yaml)
142
+ end
143
+
144
+ def self.as_indifferent_hash(obj)
145
+ case obj
146
+ when ActiveSupport::HashWithIndifferentAccess
147
+ obj
148
+ when Hash
149
+ obj.with_indifferent_access
150
+ else
151
+ ActiveSupport::HashWithIndifferentAccess.new
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end