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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -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/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- 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
|
-
|
8
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
56
|
-
|
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="
|
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>
|
@@ -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.
|
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 :
|
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 ...
|
@@ -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(:
|
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
|
-
|
230
|
-
|
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
|
-
|
233
|
-
|
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
|