activerecord 4.0.13 → 4.1.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 +745 -2700
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record.rb +2 -6
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +0 -4
- data/lib/active_record/associations.rb +87 -43
- data/lib/active_record/associations/alias_tracker.rb +1 -3
- data/lib/active_record/associations/association.rb +8 -16
- data/lib/active_record/associations/association_scope.rb +5 -16
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +78 -54
- data/lib/active_record/associations/builder/belongs_to.rb +91 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
- data/lib/active_record/associations/builder/has_many.rb +2 -2
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +68 -105
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/has_many_association.rb +11 -9
- data/lib/active_record/associations/has_many_through_association.rb +16 -12
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +204 -165
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_helper.rb +2 -11
- data/lib/active_record/associations/preloader.rb +89 -34
- data/lib/active_record/associations/preloader/association.rb +43 -25
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +5 -2
- data/lib/active_record/attribute_methods.rb +45 -40
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +8 -22
- data/lib/active_record/attribute_methods/primary_key.rb +1 -7
- data/lib/active_record/attribute_methods/read.rb +55 -28
- data/lib/active_record/attribute_methods/serialization.rb +12 -33
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
- data/lib/active_record/attribute_methods/write.rb +37 -12
- data/lib/active_record/autosave_association.rb +207 -207
- data/lib/active_record/base.rb +5 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
- data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
- data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
- data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +22 -43
- data/lib/active_record/counter_cache.rb +7 -7
- data/lib/active_record/enum.rb +100 -0
- data/lib/active_record/errors.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +171 -74
- data/lib/active_record/inheritance.rb +16 -22
- data/lib/active_record/integration.rb +52 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -12
- data/lib/active_record/migration.rb +62 -46
- data/lib/active_record/migration/command_recorder.rb +7 -13
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +10 -8
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +3 -3
- data/lib/active_record/persistence.rb +16 -34
- data/lib/active_record/querying.rb +14 -12
- data/lib/active_record/railtie.rb +0 -50
- data/lib/active_record/railties/databases.rake +12 -15
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +189 -75
- data/lib/active_record/relation.rb +69 -94
- data/lib/active_record/relation/batches.rb +57 -23
- data/lib/active_record/relation/calculations.rb +36 -43
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +107 -62
- data/lib/active_record/relation/merger.rb +7 -20
- data/lib/active_record/relation/predicate_builder.rb +57 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +110 -98
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +6 -8
- data/lib/active_record/schema_dumper.rb +16 -5
- data/lib/active_record/schema_migration.rb +24 -25
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +8 -29
- data/lib/active_record/store.rb +56 -28
- data/lib/active_record/tasks/database_tasks.rb +8 -4
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +8 -10
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -6
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +2 -8
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- metadata +32 -45
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -102
@@ -3,6 +3,7 @@ module ActiveRecord
|
|
3
3
|
class Preloader
|
4
4
|
class Association #:nodoc:
|
5
5
|
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
6
|
+
attr_reader :preloaded_records
|
6
7
|
|
7
8
|
def initialize(klass, owners, reflection, preload_scope)
|
8
9
|
@klass = klass
|
@@ -12,15 +13,14 @@ module ActiveRecord
|
|
12
13
|
@model = owners.first && owners.first.class
|
13
14
|
@scope = nil
|
14
15
|
@owners_by_key = nil
|
16
|
+
@preloaded_records = []
|
15
17
|
end
|
16
18
|
|
17
|
-
def run
|
18
|
-
|
19
|
-
preload
|
20
|
-
end
|
19
|
+
def run(preloader)
|
20
|
+
preload(preloader)
|
21
21
|
end
|
22
22
|
|
23
|
-
def preload
|
23
|
+
def preload(preloader)
|
24
24
|
raise NotImplementedError
|
25
25
|
end
|
26
26
|
|
@@ -29,6 +29,10 @@ module ActiveRecord
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def records_for(ids)
|
32
|
+
query_scope(ids)
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_scope(ids)
|
32
36
|
scope.where(association_key.in(ids))
|
33
37
|
end
|
34
38
|
|
@@ -52,12 +56,9 @@ module ActiveRecord
|
|
52
56
|
raise NotImplementedError
|
53
57
|
end
|
54
58
|
|
55
|
-
# We're converting to a string here because postgres will return the aliased association
|
56
|
-
# key in a habtm as a string (for whatever reason)
|
57
59
|
def owners_by_key
|
58
60
|
@owners_by_key ||= owners.group_by do |owner|
|
59
|
-
|
60
|
-
key && key.to_s
|
61
|
+
owner[owner_key_name]
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
@@ -67,38 +68,47 @@ module ActiveRecord
|
|
67
68
|
|
68
69
|
private
|
69
70
|
|
70
|
-
def associated_records_by_owner
|
71
|
+
def associated_records_by_owner(preloader)
|
71
72
|
owners_map = owners_by_key
|
72
73
|
owner_keys = owners_map.keys.compact
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
# Each record may have multiple owners, and vice-versa
|
76
|
+
records_by_owner = owners.each_with_object({}) do |owner,h|
|
77
|
+
h[owner] = []
|
78
|
+
end
|
79
|
+
|
80
|
+
if owner_keys.any?
|
77
81
|
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
78
82
|
# Make several smaller queries if necessary or make one query if the adapter supports it
|
79
83
|
sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
80
|
-
records = sliced.map { |slice| records_for(slice).to_a }.flatten
|
81
|
-
end
|
82
|
-
|
83
|
-
# Each record may have multiple owners, and vice-versa
|
84
|
-
records_by_owner = Hash[owners.map { |owner| [owner, []] }]
|
85
|
-
records.each do |record|
|
86
|
-
owner_key = record[association_key_name].to_s
|
87
84
|
|
88
|
-
|
89
|
-
|
85
|
+
records = load_slices sliced
|
86
|
+
records.each do |record, owner_key|
|
87
|
+
owners_map[owner_key].each do |owner|
|
88
|
+
records_by_owner[owner] << record
|
89
|
+
end
|
90
90
|
end
|
91
91
|
end
|
92
|
+
|
92
93
|
records_by_owner
|
93
94
|
end
|
94
95
|
|
96
|
+
def load_slices(slices)
|
97
|
+
@preloaded_records = slices.flat_map { |slice|
|
98
|
+
records_for(slice)
|
99
|
+
}
|
100
|
+
|
101
|
+
@preloaded_records.map { |record|
|
102
|
+
[record, record[association_key_name]]
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
95
106
|
def reflection_scope
|
96
107
|
@reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
|
97
108
|
end
|
98
109
|
|
99
110
|
def build_scope
|
100
111
|
scope = klass.unscoped
|
101
|
-
scope.default_scoped = true
|
102
112
|
|
103
113
|
values = reflection_scope.values
|
104
114
|
preload_values = preload_scope.values
|
@@ -106,14 +116,22 @@ module ActiveRecord
|
|
106
116
|
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
107
117
|
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
108
118
|
|
109
|
-
scope.
|
119
|
+
scope.select! preload_values[:select] || values[:select] || table[Arel.star]
|
110
120
|
scope.includes! preload_values[:includes] || values[:includes]
|
111
121
|
|
122
|
+
if preload_values.key? :order
|
123
|
+
scope.order! preload_values[:order]
|
124
|
+
else
|
125
|
+
if values.key? :order
|
126
|
+
scope.order! values[:order]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
112
130
|
if options[:as]
|
113
131
|
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
114
132
|
end
|
115
133
|
|
116
|
-
scope
|
134
|
+
klass.default_scoped.merge(scope)
|
117
135
|
end
|
118
136
|
end
|
119
137
|
end
|
@@ -9,8 +9,8 @@ module ActiveRecord
|
|
9
9
|
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
|
10
10
|
end
|
11
11
|
|
12
|
-
def preload
|
13
|
-
associated_records_by_owner.each do |owner, records|
|
12
|
+
def preload(preloader)
|
13
|
+
associated_records_by_owner(preloader).each do |owner, records|
|
14
14
|
association = owner.association(reflection.name)
|
15
15
|
association.loaded!
|
16
16
|
association.target.concat(records)
|
@@ -5,13 +5,13 @@ module ActiveRecord
|
|
5
5
|
|
6
6
|
private
|
7
7
|
|
8
|
-
def preload
|
9
|
-
associated_records_by_owner.each do |owner, associated_records|
|
8
|
+
def preload(preloader)
|
9
|
+
associated_records_by_owner(preloader).each do |owner, associated_records|
|
10
10
|
record = associated_records.first
|
11
11
|
|
12
12
|
association = owner.association(reflection.name)
|
13
13
|
association.target = record
|
14
|
-
association.set_inverse_instance(record)
|
14
|
+
association.set_inverse_instance(record) if record
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -2,7 +2,6 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
module ThroughAssociation #:nodoc:
|
5
|
-
|
6
5
|
def through_reflection
|
7
6
|
reflection.through_reflection
|
8
7
|
end
|
@@ -11,51 +10,84 @@ module ActiveRecord
|
|
11
10
|
reflection.source_reflection
|
12
11
|
end
|
13
12
|
|
14
|
-
def associated_records_by_owner
|
15
|
-
|
13
|
+
def associated_records_by_owner(preloader)
|
14
|
+
preloader.preload(owners,
|
15
|
+
through_reflection.name,
|
16
|
+
through_scope)
|
16
17
|
|
17
|
-
|
18
|
+
through_records = owners.map do |owner|
|
19
|
+
association = owner.association through_reflection.name
|
18
20
|
|
19
|
-
|
20
|
-
records.map! { |r| r.send(source_reflection.name) }.flatten!
|
21
|
-
records.compact!
|
21
|
+
[owner, Array(association.reader)]
|
22
22
|
end
|
23
|
-
end
|
24
23
|
|
25
|
-
|
24
|
+
reset_association owners, through_reflection.name
|
25
|
+
|
26
|
+
middle_records = through_records.map { |(_,rec)| rec }.flatten
|
27
|
+
|
28
|
+
preloaders = preloader.preload(middle_records,
|
29
|
+
source_reflection.name,
|
30
|
+
reflection_scope)
|
26
31
|
|
27
|
-
|
28
|
-
|
32
|
+
@preloaded_records = preloaders.flat_map(&:preloaded_records)
|
33
|
+
|
34
|
+
middle_to_pl = preloaders.each_with_object({}) do |pl,h|
|
35
|
+
pl.owners.each { |middle|
|
36
|
+
h[middle] = pl
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
record_offset = {}
|
41
|
+
@preloaded_records.each_with_index do |record,i|
|
42
|
+
record_offset[record] = i
|
43
|
+
end
|
29
44
|
|
30
|
-
|
31
|
-
|
45
|
+
through_records.each_with_object({}) { |(lhs,center),records_by_owner|
|
46
|
+
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
|
32
47
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
48
|
+
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
|
49
|
+
rhs_records = middles.flat_map { |r|
|
50
|
+
association = r.association source_reflection.name
|
51
|
+
|
52
|
+
association.reader
|
53
|
+
}.compact
|
54
|
+
|
55
|
+
rhs_records.sort_by { |rhs| record_offset[rhs] }
|
37
56
|
end
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def reset_association(owners, association_name)
|
63
|
+
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
64
|
+
(reflection.options[:source_type] && through_reflection.collection?)
|
38
65
|
|
39
|
-
|
40
|
-
|
66
|
+
# Dont cache the association - we would only be caching a subset
|
67
|
+
if should_reset
|
68
|
+
owners.each { |owner|
|
69
|
+
owner.association(association_name).reset
|
70
|
+
}
|
71
|
+
end
|
41
72
|
end
|
42
73
|
|
74
|
+
|
43
75
|
def through_scope
|
44
|
-
|
76
|
+
scope = through_reflection.klass.unscoped
|
45
77
|
|
46
78
|
if options[:source_type]
|
47
|
-
|
79
|
+
scope.where! reflection.foreign_type => options[:source_type]
|
48
80
|
else
|
49
81
|
unless reflection_scope.where_values.empty?
|
50
|
-
|
51
|
-
|
82
|
+
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
83
|
+
scope.where_values = reflection_scope.values[:where]
|
52
84
|
end
|
53
85
|
|
54
|
-
|
55
|
-
|
86
|
+
scope.references! reflection_scope.values[:references]
|
87
|
+
scope.order! reflection_scope.values[:order] if scope.eager_loading?
|
56
88
|
end
|
57
89
|
|
58
|
-
|
90
|
+
scope
|
59
91
|
end
|
60
92
|
end
|
61
93
|
end
|
@@ -18,11 +18,11 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def create(attributes = {}, &block)
|
21
|
-
|
21
|
+
create_record(attributes, &block)
|
22
22
|
end
|
23
23
|
|
24
24
|
def create!(attributes = {}, &block)
|
25
|
-
|
25
|
+
create_record(attributes, true, &block)
|
26
26
|
end
|
27
27
|
|
28
28
|
def build(attributes = {})
|
@@ -39,10 +39,11 @@ module ActiveRecord
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def find_target
|
42
|
-
|
42
|
+
if record = scope.first
|
43
|
+
set_inverse_instance record
|
44
|
+
end
|
43
45
|
end
|
44
46
|
|
45
|
-
# Implemented by subclasses
|
46
47
|
def replace(record)
|
47
48
|
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
48
49
|
end
|
@@ -51,7 +52,7 @@ module ActiveRecord
|
|
51
52
|
replace(record)
|
52
53
|
end
|
53
54
|
|
54
|
-
def
|
55
|
+
def create_record(attributes, raise_error = false)
|
55
56
|
record = build_record(attributes)
|
56
57
|
yield(record) if block_given?
|
57
58
|
saved = record.save
|
@@ -13,9 +13,9 @@ module ActiveRecord
|
|
13
13
|
# 2. To get the type conditions for any STI models in the chain
|
14
14
|
def target_scope
|
15
15
|
scope = super
|
16
|
-
chain
|
16
|
+
chain.drop(1).each do |reflection|
|
17
17
|
scope.merge!(
|
18
|
-
reflection.klass.all.
|
18
|
+
reflection.klass.all.
|
19
19
|
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
20
20
|
)
|
21
21
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
+
require 'active_model/forbidden_attributes_protection'
|
1
2
|
|
2
3
|
module ActiveRecord
|
3
4
|
module AttributeAssignment
|
4
5
|
extend ActiveSupport::Concern
|
5
|
-
include ActiveModel::DeprecatedMassAssignmentSecurity
|
6
6
|
include ActiveModel::ForbiddenAttributesProtection
|
7
7
|
|
8
8
|
# Allows you to set all the attributes by passing in a hash of attributes with
|
@@ -12,6 +12,9 @@ module ActiveRecord
|
|
12
12
|
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
13
|
# exception is raised.
|
14
14
|
def assign_attributes(new_attributes)
|
15
|
+
if !new_attributes.respond_to?(:stringify_keys)
|
16
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
17
|
+
end
|
15
18
|
return if new_attributes.blank?
|
16
19
|
|
17
20
|
attributes = new_attributes.stringify_keys
|
@@ -44,7 +47,7 @@ module ActiveRecord
|
|
44
47
|
if respond_to?("#{k}=")
|
45
48
|
raise
|
46
49
|
else
|
47
|
-
raise UnknownAttributeError,
|
50
|
+
raise UnknownAttributeError.new(self, k)
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_support/core_ext/enumerable'
|
2
2
|
require 'mutex_m'
|
3
|
+
require 'thread_safe'
|
3
4
|
|
4
5
|
module ActiveRecord
|
5
6
|
# = Active Record Attribute Methods
|
@@ -19,6 +20,35 @@ module ActiveRecord
|
|
19
20
|
include Serialization
|
20
21
|
end
|
21
22
|
|
23
|
+
AttrNames = Module.new {
|
24
|
+
def self.set_name_cache(name, value)
|
25
|
+
const_name = "ATTR_#{name}"
|
26
|
+
unless const_defined? const_name
|
27
|
+
const_set const_name, value.dup.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
}
|
31
|
+
|
32
|
+
class AttributeMethodCache
|
33
|
+
def initialize
|
34
|
+
@module = Module.new
|
35
|
+
@method_cache = ThreadSafe::Cache.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](name)
|
39
|
+
@method_cache.compute_if_absent(name) do
|
40
|
+
safe_name = name.unpack('h*').first
|
41
|
+
temp_method = "__temp__#{safe_name}"
|
42
|
+
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
43
|
+
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
|
44
|
+
@module.instance_method temp_method
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def method_body; raise NotImplementedError; end
|
50
|
+
end
|
51
|
+
|
22
52
|
module ClassMethods
|
23
53
|
def inherited(child_class) #:nodoc:
|
24
54
|
child_class.initialize_generated_modules
|
@@ -26,18 +56,7 @@ module ActiveRecord
|
|
26
56
|
end
|
27
57
|
|
28
58
|
def initialize_generated_modules # :nodoc:
|
29
|
-
@generated_attribute_methods = Module.new {
|
30
|
-
extend Mutex_m
|
31
|
-
|
32
|
-
const_set :AttrNames, Module.new {
|
33
|
-
def self.set_name_cache(name, value)
|
34
|
-
const_name = "ATTR_#{name}"
|
35
|
-
unless const_defined? const_name
|
36
|
-
const_set const_name, value.dup.freeze
|
37
|
-
end
|
38
|
-
end
|
39
|
-
}
|
40
|
-
}
|
59
|
+
@generated_attribute_methods = Module.new { extend Mutex_m }
|
41
60
|
@attribute_methods_generated = false
|
42
61
|
include @generated_attribute_methods
|
43
62
|
end
|
@@ -87,8 +106,7 @@ module ActiveRecord
|
|
87
106
|
else
|
88
107
|
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
|
89
108
|
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
|
90
|
-
|
91
|
-
defined && !base_defined || super
|
109
|
+
defined && !ActiveRecord::Base.method_defined?(method_name) || super
|
92
110
|
end
|
93
111
|
end
|
94
112
|
|
@@ -161,7 +179,6 @@ module ActiveRecord
|
|
161
179
|
# this is probably horribly slow, but should only happen at most once for a given AR class
|
162
180
|
attribute_method.bind(self).call(*args, &block)
|
163
181
|
else
|
164
|
-
return super unless respond_to_missing?(method, true)
|
165
182
|
send(method, *args, &block)
|
166
183
|
end
|
167
184
|
else
|
@@ -169,20 +186,6 @@ module ActiveRecord
|
|
169
186
|
end
|
170
187
|
end
|
171
188
|
|
172
|
-
def attribute_missing(match, *args, &block) # :nodoc:
|
173
|
-
if self.class.columns_hash[match.attr_name]
|
174
|
-
ActiveSupport::Deprecation.warn(
|
175
|
-
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
|
176
|
-
"dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
|
177
|
-
"is a column of the table. If this error has happened through normal usage of Active " \
|
178
|
-
"Record (rather than through your own code or external libraries), please report it as " \
|
179
|
-
"a bug."
|
180
|
-
)
|
181
|
-
end
|
182
|
-
|
183
|
-
super
|
184
|
-
end
|
185
|
-
|
186
189
|
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
187
190
|
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
|
188
191
|
# which will all return +true+. It also define the attribute methods if they have
|
@@ -211,7 +214,7 @@ module ActiveRecord
|
|
211
214
|
# For queries selecting a subset of columns, return false for unselected columns.
|
212
215
|
# We check defined?(@attributes) not to issue warnings if called on objects that
|
213
216
|
# have been allocated but not yet initialized.
|
214
|
-
if defined?(@attributes) && @attributes.
|
217
|
+
if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
|
215
218
|
return has_attribute?(name)
|
216
219
|
end
|
217
220
|
|
@@ -257,31 +260,33 @@ module ActiveRecord
|
|
257
260
|
}
|
258
261
|
end
|
259
262
|
|
260
|
-
# Placeholder so it can be overriden when needed by serialization
|
261
|
-
def attributes_for_coder # :nodoc:
|
262
|
-
attributes
|
263
|
-
end
|
264
|
-
|
265
263
|
# Returns an <tt>#inspect</tt>-like string for the value of the
|
266
264
|
# attribute +attr_name+. String attributes are truncated upto 50
|
267
|
-
# characters,
|
268
|
-
# <tt>:db</tt> format
|
269
|
-
# <tt>#inspect</tt> without
|
265
|
+
# characters, Date and Time attributes are returned in the
|
266
|
+
# <tt>:db</tt> format, Array attributes are truncated upto 10 values.
|
267
|
+
# Other attributes return the value of <tt>#inspect</tt> without
|
268
|
+
# modification.
|
270
269
|
#
|
271
270
|
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
|
272
271
|
#
|
273
272
|
# person.attribute_for_inspect(:name)
|
274
|
-
# # => "\"David Heinemeier Hansson David Heinemeier Hansson
|
273
|
+
# # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
|
275
274
|
#
|
276
275
|
# person.attribute_for_inspect(:created_at)
|
277
276
|
# # => "\"2012-10-22 00:15:07\""
|
277
|
+
#
|
278
|
+
# person.attribute_for_inspect(:tag_ids)
|
279
|
+
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
|
278
280
|
def attribute_for_inspect(attr_name)
|
279
281
|
value = read_attribute(attr_name)
|
280
282
|
|
281
283
|
if value.is_a?(String) && value.length > 50
|
282
|
-
"#{value[0
|
284
|
+
"#{value[0, 50]}...".inspect
|
283
285
|
elsif value.is_a?(Date) || value.is_a?(Time)
|
284
286
|
%("#{value.to_s(:db)}")
|
287
|
+
elsif value.is_a?(Array) && value.size > 10
|
288
|
+
inspected = value.first(10).inspect
|
289
|
+
%(#{inspected[0...-1]}, ...])
|
285
290
|
else
|
286
291
|
value.inspect
|
287
292
|
end
|