activerecord 3.2.22.4 → 4.0.13
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 +2799 -617
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -32
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations/alias_tracker.rb +4 -2
- data/lib/active_record/associations/association.rb +60 -46
- data/lib/active_record/associations/association_scope.rb +46 -40
- data/lib/active_record/associations/belongs_to_association.rb +17 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +73 -56
- 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 +130 -96
- data/lib/active_record/associations/collection_proxy.rb +916 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
- data/lib/active_record/associations/has_many_association.rb +35 -8
- data/lib/active_record/associations/has_many_through_association.rb +37 -17
- data/lib/active_record/associations/has_one_association.rb +42 -19
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
- data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
- data/lib/active_record/associations/join_dependency.rb +30 -9
- data/lib/active_record/associations/join_helper.rb +1 -11
- 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 +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
- 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/preloader.rb +20 -43
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +223 -282
- data/lib/active_record/attribute_assignment.rb +134 -154
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +36 -29
- data/lib/active_record/attribute_methods/primary_key.rb +45 -31
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +67 -90
- data/lib/active_record/attribute_methods/serialization.rb +133 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
- data/lib/active_record/attribute_methods/write.rb +34 -39
- data/lib/active_record/attribute_methods.rb +268 -108
- data/lib/active_record/autosave_association.rb +80 -73
- data/lib/active_record/base.rb +54 -451
- data/lib/active_record/callbacks.rb +60 -22
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
- data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
- data/lib/active_record/connection_adapters/column.rb +67 -36
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
- data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
- data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +472 -0
- data/lib/active_record/counter_cache.rb +107 -108
- data/lib/active_record/dynamic_matchers.rb +115 -63
- data/lib/active_record/errors.rb +36 -18
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +8 -4
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +159 -155
- data/lib/active_record/inheritance.rb +93 -59
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +39 -43
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration/command_recorder.rb +102 -33
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +411 -173
- data/lib/active_record/model_schema.rb +81 -94
- data/lib/active_record/nested_attributes.rb +173 -131
- data/lib/active_record/null_relation.rb +67 -0
- data/lib/active_record/persistence.rb +254 -106
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +113 -38
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +115 -368
- 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 +110 -61
- data/lib/active_record/relation/batches.rb +29 -29
- data/lib/active_record/relation/calculations.rb +155 -125
- data/lib/active_record/relation/delegation.rb +94 -18
- data/lib/active_record/relation/finder_methods.rb +151 -203
- data/lib/active_record/relation/merger.rb +188 -0
- data/lib/active_record/relation/predicate_builder.rb +85 -42
- data/lib/active_record/relation/query_methods.rb +793 -146
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/relation.rb +293 -173
- data/lib/active_record/result.rb +48 -7
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +41 -54
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +41 -41
- data/lib/active_record/schema_migration.rb +46 -0
- data/lib/active_record/scoping/default.rb +56 -52
- data/lib/active_record/scoping/named.rb +78 -103
- data/lib/active_record/scoping.rb +54 -124
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +131 -15
- data/lib/active_record/tasks/database_tasks.rb +204 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +144 -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 +67 -38
- data/lib/active_record/timestamp.rb +16 -11
- data/lib/active_record/transactions.rb +73 -51
- data/lib/active_record/validations/associated.rb +19 -13
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +110 -57
- data/lib/active_record/validations.rb +18 -17
- data/lib/active_record/version.rb +7 -6
- data/lib/active_record.rb +63 -45
- data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -5
- metadata +43 -29
- data/examples/associations.png +0 -0
- 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,22 +1,29 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module Scoping
|
5
3
|
module Default
|
6
4
|
extend ActiveSupport::Concern
|
7
5
|
|
8
6
|
included do
|
9
|
-
# Stores the default scope for the class
|
10
|
-
class_attribute :default_scopes, :
|
7
|
+
# Stores the default scope for the class.
|
8
|
+
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
|
9
|
+
|
11
10
|
self.default_scopes = []
|
11
|
+
|
12
|
+
def self.default_scopes?
|
13
|
+
ActiveSupport::Deprecation.warn(
|
14
|
+
"#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
|
15
|
+
)
|
16
|
+
|
17
|
+
!!self.default_scopes
|
18
|
+
end
|
12
19
|
end
|
13
20
|
|
14
21
|
module ClassMethods
|
15
|
-
# Returns a scope for the model without the default_scope
|
22
|
+
# Returns a scope for the model without the +default_scope+.
|
16
23
|
#
|
17
24
|
# class Post < ActiveRecord::Base
|
18
25
|
# def self.default_scope
|
19
|
-
# where :
|
26
|
+
# where published: true
|
20
27
|
# end
|
21
28
|
# end
|
22
29
|
#
|
@@ -24,20 +31,12 @@ module ActiveRecord
|
|
24
31
|
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
25
32
|
#
|
26
33
|
# This method also accepts a block. All queries inside the block will
|
27
|
-
# not use the default_scope
|
34
|
+
# not use the +default_scope+:
|
28
35
|
#
|
29
36
|
# Post.unscoped {
|
30
37
|
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
31
38
|
# }
|
32
|
-
|
33
|
-
# It is recommended to use the block form of unscoped because chaining
|
34
|
-
# unscoped with <tt>scope</tt> does not work. Assuming that
|
35
|
-
# <tt>published</tt> is a <tt>scope</tt>, the following two statements
|
36
|
-
# are equal: the default_scope is applied on both.
|
37
|
-
#
|
38
|
-
# Post.unscoped.published
|
39
|
-
# Post.published
|
40
|
-
def unscoped #:nodoc:
|
39
|
+
def unscoped
|
41
40
|
block_given? ? relation.scoping { yield } : relation
|
42
41
|
end
|
43
42
|
|
@@ -51,62 +50,67 @@ module ActiveRecord
|
|
51
50
|
# the model.
|
52
51
|
#
|
53
52
|
# class Article < ActiveRecord::Base
|
54
|
-
# default_scope where(:
|
53
|
+
# default_scope { where(published: true) }
|
55
54
|
# end
|
56
55
|
#
|
57
56
|
# Article.all # => SELECT * FROM articles WHERE published = true
|
58
57
|
#
|
59
|
-
# The
|
60
|
-
# applied while updating a record.
|
58
|
+
# The +default_scope+ is also applied while creating/building a record.
|
59
|
+
# It is not applied while updating a record.
|
61
60
|
#
|
62
61
|
# Article.new.published # => true
|
63
62
|
# Article.create.published # => true
|
64
63
|
#
|
65
|
-
# You can also
|
64
|
+
# (You can also pass any object which responds to +call+ to the
|
65
|
+
# +default_scope+ macro, and it will be called when building the
|
66
|
+
# default scope.)
|
66
67
|
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# end
|
70
|
-
#
|
71
|
-
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
72
|
-
# macro, and it will be called when building the default scope.)
|
73
|
-
#
|
74
|
-
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
75
|
-
# be merged together:
|
68
|
+
# If you use multiple +default_scope+ declarations in your model then
|
69
|
+
# they will be merged together:
|
76
70
|
#
|
77
71
|
# class Article < ActiveRecord::Base
|
78
|
-
# default_scope where(:
|
79
|
-
# default_scope where(:
|
72
|
+
# default_scope { where(published: true) }
|
73
|
+
# default_scope { where(rating: 'G') }
|
80
74
|
# end
|
81
75
|
#
|
82
76
|
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
83
77
|
#
|
84
|
-
# This is also the case with inheritance and module includes where the
|
85
|
-
# defines a
|
78
|
+
# This is also the case with inheritance and module includes where the
|
79
|
+
# parent or module defines a +default_scope+ and the child or including
|
80
|
+
# class defines a second one.
|
86
81
|
#
|
87
|
-
# If you need to do more complex things with a default scope, you can
|
88
|
-
# define it as a class method:
|
82
|
+
# If you need to do more complex things with a default scope, you can
|
83
|
+
# alternatively define it as a class method:
|
89
84
|
#
|
90
85
|
# class Article < ActiveRecord::Base
|
91
86
|
# def self.default_scope
|
92
87
|
# # Should return a scope, you can call 'super' here etc.
|
93
88
|
# end
|
94
89
|
# end
|
95
|
-
def default_scope(scope =
|
90
|
+
def default_scope(scope = nil)
|
96
91
|
scope = Proc.new if block_given?
|
97
|
-
|
92
|
+
|
93
|
+
if scope.is_a?(Relation) || !scope.respond_to?(:call)
|
94
|
+
ActiveSupport::Deprecation.warn(
|
95
|
+
"Calling #default_scope without a block is deprecated. For example instead " \
|
96
|
+
"of `default_scope where(color: 'red')`, please use " \
|
97
|
+
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
|
98
|
+
"self.default_scope.)"
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
self.default_scopes += [scope]
|
98
103
|
end
|
99
104
|
|
100
|
-
def build_default_scope
|
101
|
-
if method(:default_scope).owner
|
105
|
+
def build_default_scope(base_rel = relation) # :nodoc:
|
106
|
+
if !Base.is_a?(method(:default_scope).owner)
|
107
|
+
# The user has defined their own default scope method, so call that
|
102
108
|
evaluate_default_scope { default_scope }
|
103
109
|
elsif default_scopes.any?
|
104
110
|
evaluate_default_scope do
|
105
|
-
default_scopes.inject(
|
106
|
-
if scope.is_a?(
|
107
|
-
default_scope.
|
108
|
-
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
109
|
-
default_scope.merge(scope.call)
|
111
|
+
default_scopes.inject(base_rel) do |default_scope, scope|
|
112
|
+
if !scope.is_a?(Relation) && scope.respond_to?(:call)
|
113
|
+
default_scope.merge(base_rel.scoping { scope.call })
|
110
114
|
else
|
111
115
|
default_scope.merge(scope)
|
112
116
|
end
|
@@ -115,17 +119,18 @@ module ActiveRecord
|
|
115
119
|
end
|
116
120
|
end
|
117
121
|
|
118
|
-
def ignore_default_scope?
|
119
|
-
|
122
|
+
def ignore_default_scope? # :nodoc:
|
123
|
+
ScopeRegistry.value_for(:ignore_default_scope, self)
|
120
124
|
end
|
121
125
|
|
122
|
-
def ignore_default_scope=(ignore)
|
123
|
-
|
126
|
+
def ignore_default_scope=(ignore) # :nodoc:
|
127
|
+
ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
|
124
128
|
end
|
125
129
|
|
126
|
-
# The ignore_default_scope flag is used to prevent an infinite recursion
|
127
|
-
# a default scope references a scope which has a default
|
128
|
-
|
130
|
+
# The ignore_default_scope flag is used to prevent an infinite recursion
|
131
|
+
# situation where a default scope references a scope which has a default
|
132
|
+
# scope which references a scope...
|
133
|
+
def evaluate_default_scope # :nodoc:
|
129
134
|
return if ignore_default_scope?
|
130
135
|
|
131
136
|
begin
|
@@ -135,7 +140,6 @@ module ActiveRecord
|
|
135
140
|
self.ignore_default_scope = false
|
136
141
|
end
|
137
142
|
end
|
138
|
-
|
139
143
|
end
|
140
144
|
end
|
141
145
|
end
|
@@ -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>.
|
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.
|
73
67
|
#
|
74
|
-
# Note that this is simply 'syntactic sugar' for defining an actual
|
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.
|
102
|
+
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
|
103
|
+
# Elton's red, dry clean only shirts.
|
112
104
|
#
|
113
|
-
#
|
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
|
132
|
-
#
|
133
|
-
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
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,31 @@ 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?
|
176
|
-
|
177
|
-
scope_proc = lambda do |*args|
|
178
|
-
options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options
|
179
|
-
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
|
145
|
+
def scope(name, body, &block)
|
146
|
+
extension = Module.new(&block) if block
|
180
147
|
|
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
|
-
|
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
|
190
168
|
|
191
|
-
|
192
|
-
if logger && respond_to?(name, true)
|
193
|
-
logger.warn "Creating scope :#{name}. " \
|
194
|
-
"Overwriting existing method #{self.name}.#{name}."
|
169
|
+
scope || all
|
195
170
|
end
|
196
171
|
end
|
197
172
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_support/
|
1
|
+
require 'active_support/per_thread_registry'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Scoping
|
@@ -10,134 +10,13 @@ module ActiveRecord
|
|
10
10
|
end
|
11
11
|
|
12
12
|
module ClassMethods
|
13
|
-
# with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
|
14
|
-
# <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
|
15
|
-
# <tt>:create</tt> parameters are an attributes hash.
|
16
|
-
#
|
17
|
-
# class Article < ActiveRecord::Base
|
18
|
-
# def self.create_with_scope
|
19
|
-
# with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
|
20
|
-
# find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
|
21
|
-
# a = create(1)
|
22
|
-
# a.blog_id # => 1
|
23
|
-
# end
|
24
|
-
# end
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
|
28
|
-
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
|
29
|
-
#
|
30
|
-
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
|
31
|
-
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
|
32
|
-
# array of strings format for your joins.
|
33
|
-
#
|
34
|
-
# class Article < ActiveRecord::Base
|
35
|
-
# def self.find_with_scope
|
36
|
-
# with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
|
37
|
-
# with_scope(:find => limit(10)) do
|
38
|
-
# all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
|
39
|
-
# end
|
40
|
-
# with_scope(:find => where(:author_id => 3)) do
|
41
|
-
# all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
|
42
|
-
# end
|
43
|
-
# end
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
|
48
|
-
#
|
49
|
-
# class Article < ActiveRecord::Base
|
50
|
-
# def self.find_with_exclusive_scope
|
51
|
-
# with_scope(:find => where(:blog_id => 1).limit(1)) do
|
52
|
-
# with_exclusive_scope(:find => limit(10)) do
|
53
|
-
# all # => SELECT * from articles LIMIT 10
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
# end
|
57
|
-
# end
|
58
|
-
#
|
59
|
-
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
|
60
|
-
def with_scope(scope = {}, action = :merge, &block)
|
61
|
-
# If another Active Record class has been passed in, get its current scope
|
62
|
-
scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
|
63
|
-
|
64
|
-
previous_scope = self.current_scope
|
65
|
-
|
66
|
-
if scope.is_a?(Hash)
|
67
|
-
# Dup first and second level of hash (method and params).
|
68
|
-
scope = scope.dup
|
69
|
-
scope.each do |method, params|
|
70
|
-
scope[method] = params.dup unless params == true
|
71
|
-
end
|
72
|
-
|
73
|
-
scope.assert_valid_keys([ :find, :create ])
|
74
|
-
relation = construct_finder_arel(scope[:find] || {})
|
75
|
-
relation.default_scoped = true unless action == :overwrite
|
76
|
-
|
77
|
-
if previous_scope && previous_scope.create_with_value && scope[:create]
|
78
|
-
scope_for_create = if action == :merge
|
79
|
-
previous_scope.create_with_value.merge(scope[:create])
|
80
|
-
else
|
81
|
-
scope[:create]
|
82
|
-
end
|
83
|
-
|
84
|
-
relation = relation.create_with(scope_for_create)
|
85
|
-
else
|
86
|
-
scope_for_create = scope[:create]
|
87
|
-
scope_for_create ||= previous_scope.create_with_value if previous_scope
|
88
|
-
relation = relation.create_with(scope_for_create) if scope_for_create
|
89
|
-
end
|
90
|
-
|
91
|
-
scope = relation
|
92
|
-
end
|
93
|
-
|
94
|
-
scope = previous_scope.merge(scope) if previous_scope && action == :merge
|
95
|
-
|
96
|
-
self.current_scope = scope
|
97
|
-
begin
|
98
|
-
yield
|
99
|
-
ensure
|
100
|
-
self.current_scope = previous_scope
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
protected
|
105
|
-
|
106
|
-
# Works like with_scope, but discards any nested properties.
|
107
|
-
def with_exclusive_scope(method_scoping = {}, &block)
|
108
|
-
if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
|
109
|
-
raise ArgumentError, <<-MSG
|
110
|
-
New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
|
111
|
-
|
112
|
-
User.unscoped.where(:active => true)
|
113
|
-
|
114
|
-
Or call unscoped with a block:
|
115
|
-
|
116
|
-
User.unscoped do
|
117
|
-
User.where(:active => true).all
|
118
|
-
end
|
119
|
-
|
120
|
-
MSG
|
121
|
-
end
|
122
|
-
with_scope(method_scoping, :overwrite, &block)
|
123
|
-
end
|
124
|
-
|
125
13
|
def current_scope #:nodoc:
|
126
|
-
|
14
|
+
ScopeRegistry.value_for(:current_scope, base_class.to_s)
|
127
15
|
end
|
128
16
|
|
129
17
|
def current_scope=(scope) #:nodoc:
|
130
|
-
|
18
|
+
ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
131
19
|
end
|
132
|
-
|
133
|
-
private
|
134
|
-
|
135
|
-
def construct_finder_arel(options = {}, scope = nil)
|
136
|
-
relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
|
137
|
-
relation = scope.merge(relation) if scope
|
138
|
-
relation
|
139
|
-
end
|
140
|
-
|
141
20
|
end
|
142
21
|
|
143
22
|
def populate_with_current_scope_attributes
|
@@ -148,5 +27,56 @@ module ActiveRecord
|
|
148
27
|
end
|
149
28
|
end
|
150
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
|
151
81
|
end
|
152
82
|
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 = false
|
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
|