activerecord 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,47 +1,36 @@
|
|
1
1
|
require 'active_support/core_ext/array'
|
2
2
|
require 'active_support/core_ext/hash/except'
|
3
3
|
require 'active_support/core_ext/kernel/singleton_class'
|
4
|
-
require 'active_support/core_ext/object/blank'
|
5
|
-
require 'active_support/core_ext/class/attribute'
|
6
4
|
|
7
5
|
module ActiveRecord
|
8
|
-
# = Active Record Named \Scopes
|
6
|
+
# = Active Record \Named \Scopes
|
9
7
|
module Scoping
|
10
8
|
module Named
|
11
9
|
extend ActiveSupport::Concern
|
12
10
|
|
13
11
|
module ClassMethods
|
14
|
-
# Returns an
|
12
|
+
# Returns an <tt>ActiveRecord::Relation</tt> scope object.
|
15
13
|
#
|
16
|
-
# posts = Post.
|
14
|
+
# posts = Post.all
|
17
15
|
# posts.size # Fires "select count(*) from posts" and returns the count
|
18
16
|
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
|
19
17
|
#
|
20
|
-
# fruits = Fruit.
|
21
|
-
# fruits = fruits.where(:
|
18
|
+
# fruits = Fruit.all
|
19
|
+
# fruits = fruits.where(color: 'red') if options[:red_only]
|
22
20
|
# fruits = fruits.limit(10) if limited?
|
23
21
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# ActiveRecord::Base.default_scope.
|
30
|
-
def scoped(options = nil)
|
31
|
-
if options
|
32
|
-
scoped.apply_finder_options(options)
|
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
|
33
27
|
else
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
scope = relation
|
38
|
-
scope.default_scoped = true
|
39
|
-
scope
|
40
|
-
end
|
28
|
+
scope = relation
|
29
|
+
scope.default_scoped = true
|
30
|
+
scope
|
41
31
|
end
|
42
32
|
end
|
43
33
|
|
44
|
-
##
|
45
34
|
# Collects attributes from scopes that should be applied when creating
|
46
35
|
# an AR instance for the particular class this is called on.
|
47
36
|
def scope_attributes # :nodoc:
|
@@ -54,86 +43,70 @@ module ActiveRecord
|
|
54
43
|
end
|
55
44
|
end
|
56
45
|
|
57
|
-
##
|
58
46
|
# Are there default attributes associated with this scope?
|
59
47
|
def scope_attributes? # :nodoc:
|
60
48
|
current_scope || default_scopes.any?
|
61
49
|
end
|
62
50
|
|
63
|
-
# Adds a class method for retrieving and querying objects. A \scope
|
64
|
-
#
|
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>.
|
65
54
|
#
|
66
55
|
# class Shirt < ActiveRecord::Base
|
67
|
-
# scope :red, where(:
|
68
|
-
# scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
|
56
|
+
# scope :red, -> { where(color: 'red') }
|
57
|
+
# scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
|
69
58
|
# end
|
70
59
|
#
|
71
|
-
# The above calls to
|
72
|
-
#
|
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>.
|
73
63
|
#
|
74
|
-
#
|
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:
|
75
70
|
#
|
76
71
|
# class Shirt < ActiveRecord::Base
|
77
72
|
# def self.red
|
78
|
-
# where(:
|
73
|
+
# where(color: 'red')
|
79
74
|
# end
|
80
75
|
# end
|
81
76
|
#
|
82
|
-
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
# <tt>Shirt.red.
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# <tt>Shirt.red.dry_clean_only
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# the
|
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
97
|
#
|
98
98
|
# class Person < ActiveRecord::Base
|
99
99
|
# has_many :shirts
|
100
100
|
# end
|
101
101
|
#
|
102
|
-
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
|
103
|
-
# only shirts.
|
104
|
-
#
|
105
|
-
# Named \scopes can also be procedural:
|
106
|
-
#
|
107
|
-
# class Shirt < ActiveRecord::Base
|
108
|
-
# scope :colored, lambda { |color| where(:color => color) }
|
109
|
-
# end
|
110
|
-
#
|
111
|
-
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
112
|
-
#
|
113
|
-
# On Ruby 1.9 you can use the 'stabby lambda' syntax:
|
114
|
-
#
|
115
|
-
# scope :colored, ->(color) { where(:color => color) }
|
116
|
-
#
|
117
|
-
# Note that scopes defined with \scope will be evaluated when they are defined, rather than
|
118
|
-
# when they are used. For example, the following would be incorrect:
|
119
|
-
#
|
120
|
-
# class Post < ActiveRecord::Base
|
121
|
-
# scope :recent, where('published_at >= ?', Time.current - 1.week)
|
122
|
-
# end
|
123
|
-
#
|
124
|
-
# The example above would be 'frozen' to the <tt>Time.current</tt> value when the <tt>Post</tt>
|
125
|
-
# class was defined, and so the resultant SQL query would always be the same. The correct
|
126
|
-
# way to do this would be via a lambda, which will re-evaluate the scope each time
|
127
|
-
# it is called:
|
128
|
-
#
|
129
|
-
# class Post < ActiveRecord::Base
|
130
|
-
# scope :recent, lambda { where('published_at >= ?', Time.current - 1.week) }
|
131
|
-
# end
|
102
|
+
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
|
103
|
+
# Elton's red, dry clean only shirts.
|
132
104
|
#
|
133
|
-
# Named
|
105
|
+
# \Named scopes can also have extensions, just as with +has_many+
|
106
|
+
# declarations:
|
134
107
|
#
|
135
108
|
# class Shirt < ActiveRecord::Base
|
136
|
-
# scope :red, where(:
|
109
|
+
# scope :red, -> { where(color: 'red') } do
|
137
110
|
# def dom_id
|
138
111
|
# 'red_shirts'
|
139
112
|
# end
|
@@ -143,18 +116,18 @@ module ActiveRecord
|
|
143
116
|
# Scopes can also be used while creating/building a record.
|
144
117
|
#
|
145
118
|
# class Article < ActiveRecord::Base
|
146
|
-
# scope :published, where(:
|
119
|
+
# scope :published, -> { where(published: true) }
|
147
120
|
# end
|
148
121
|
#
|
149
122
|
# Article.published.new.published # => true
|
150
123
|
# Article.published.create.published # => true
|
151
124
|
#
|
152
|
-
# Class methods on your model are automatically available
|
125
|
+
# \Class methods on your model are automatically available
|
153
126
|
# on scopes. Assuming the following setup:
|
154
127
|
#
|
155
128
|
# class Article < ActiveRecord::Base
|
156
|
-
# scope :published, where(:
|
157
|
-
# scope :featured, where(:
|
129
|
+
# scope :published, -> { where(published: true) }
|
130
|
+
# scope :featured, -> { where(featured: true) }
|
158
131
|
#
|
159
132
|
# def self.latest_article
|
160
133
|
# order('published_at desc').first
|
@@ -169,29 +142,27 @@ module ActiveRecord
|
|
169
142
|
#
|
170
143
|
# Article.published.featured.latest_article
|
171
144
|
# Article.featured.titles
|
172
|
-
def scope(name,
|
173
|
-
|
174
|
-
valid_scope_name?(name)
|
175
|
-
extension = Module.new(&Proc.new) if block_given?
|
145
|
+
def scope(name, body, &block)
|
146
|
+
extension = Module.new(&block) if block
|
176
147
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
+
)
|
184
159
|
end
|
185
160
|
|
186
|
-
singleton_class.send(:
|
187
|
-
|
188
|
-
|
189
|
-
protected
|
161
|
+
singleton_class.send(:define_method, name) do |*args|
|
162
|
+
options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
|
163
|
+
relation = all.merge(options)
|
190
164
|
|
191
|
-
|
192
|
-
if logger && respond_to?(name, true)
|
193
|
-
logger.warn "Creating scope :#{name}. " \
|
194
|
-
"Overwriting existing method #{self.name}.#{name}."
|
165
|
+
extension ? relation.extending(extension) : relation
|
195
166
|
end
|
196
167
|
end
|
197
168
|
end
|
@@ -4,11 +4,15 @@ module ActiveRecord #:nodoc:
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
include ActiveModel::Serializers::JSON
|
6
6
|
|
7
|
+
included do
|
8
|
+
self.include_root_in_json = true
|
9
|
+
end
|
10
|
+
|
7
11
|
def serializable_hash(options = nil)
|
8
12
|
options = options.try(:clone) || {}
|
9
13
|
|
10
|
-
options[:except] = Array
|
11
|
-
options[:except] |= Array
|
14
|
+
options[:except] = Array(options[:except]).map { |n| n.to_s }
|
15
|
+
options[:except] |= Array(self.class.inheritance_column)
|
12
16
|
|
13
17
|
super(options)
|
14
18
|
end
|
@@ -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="
|
23
|
-
# <written-on type="
|
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(:
|
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 :
|
53
|
+
# firm.to_xml include: [ :account, :clients ]
|
55
54
|
#
|
56
55
|
# <?xml version="1.0" encoding="UTF-8"?>
|
57
56
|
# <firm>
|
@@ -82,7 +81,7 @@ module ActiveRecord #:nodoc:
|
|
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 :
|
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 :
|
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 :
|
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 :
|
135
|
+
# firm.to_xml procs: [ proc ]
|
137
136
|
#
|
138
137
|
# <firm>
|
139
138
|
# # ... normal attributes as shown above ...
|
@@ -165,7 +164,7 @@ module ActiveRecord #:nodoc:
|
|
165
164
|
# def to_xml(options = {})
|
166
165
|
# require 'builder'
|
167
166
|
# options[:indent] ||= 2
|
168
|
-
# xml = options[:builder] ||= ::Builder::XmlMarkup.new(:
|
167
|
+
# xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
|
169
168
|
# xml.instruct! unless options[:skip_instruct]
|
170
169
|
# xml.level_one do
|
171
170
|
# xml.tag!(:second_level, 'content')
|
@@ -178,11 +177,6 @@ module ActiveRecord #:nodoc:
|
|
178
177
|
end
|
179
178
|
|
180
179
|
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
|
181
|
-
def initialize(*args)
|
182
|
-
super
|
183
|
-
options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column)
|
184
|
-
end
|
185
|
-
|
186
180
|
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
|
187
181
|
def compute_type
|
188
182
|
klass = @serializable.class
|
data/lib/active_record/store.rb
CHANGED
@@ -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
|
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,145 @@ 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
|
+
#
|
13
18
|
# Examples:
|
14
19
|
#
|
15
20
|
# class User < ActiveRecord::Base
|
16
|
-
# store :settings, accessors: [ :color, :homepage ]
|
21
|
+
# store :settings, accessors: [ :color, :homepage ], coder: JSON
|
17
22
|
# end
|
18
|
-
#
|
23
|
+
#
|
19
24
|
# u = User.new(color: 'black', homepage: '37signals.com')
|
20
25
|
# u.color # Accessor stored attribute
|
21
26
|
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
|
22
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
|
+
#
|
23
32
|
# # Add additional accessors to an existing store through store_accessor
|
24
33
|
# class SuperUser < User
|
25
34
|
# store_accessor :settings, :privileges, :servants
|
26
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
|
27
60
|
module Store
|
28
61
|
extend ActiveSupport::Concern
|
29
|
-
|
62
|
+
|
63
|
+
included do
|
64
|
+
class_attribute :stored_attributes, instance_accessor: false
|
65
|
+
self.stored_attributes = {}
|
66
|
+
end
|
67
|
+
|
30
68
|
module ClassMethods
|
31
69
|
def store(store_attribute, options = {})
|
32
|
-
serialize store_attribute,
|
70
|
+
serialize store_attribute, IndifferentCoder.new(options[:coder])
|
33
71
|
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
|
34
72
|
end
|
35
73
|
|
36
74
|
def store_accessor(store_attribute, *keys)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
42
86
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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)
|
47
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
|
48
152
|
end
|
49
153
|
end
|
50
154
|
end
|
51
155
|
end
|
52
|
-
end
|
156
|
+
end
|