activerecord 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -2,16 +2,16 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
class Association #:nodoc:
|
5
|
-
attr_reader :owners, :reflection, :
|
6
|
-
|
7
|
-
def initialize(klass, owners, reflection,
|
8
|
-
@klass
|
9
|
-
@owners
|
10
|
-
@reflection
|
11
|
-
@
|
12
|
-
@model
|
13
|
-
@
|
14
|
-
@owners_by_key
|
5
|
+
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
6
|
+
|
7
|
+
def initialize(klass, owners, reflection, preload_scope)
|
8
|
+
@klass = klass
|
9
|
+
@owners = owners
|
10
|
+
@reflection = reflection
|
11
|
+
@preload_scope = preload_scope
|
12
|
+
@model = owners.first && owners.first.class
|
13
|
+
@scope = nil
|
14
|
+
@owners_by_key = nil
|
15
15
|
end
|
16
16
|
|
17
17
|
def run
|
@@ -24,12 +24,12 @@ module ActiveRecord
|
|
24
24
|
raise NotImplementedError
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
@
|
27
|
+
def scope
|
28
|
+
@scope ||= build_scope
|
29
29
|
end
|
30
30
|
|
31
31
|
def records_for(ids)
|
32
|
-
|
32
|
+
scope.where(association_key.in(ids))
|
33
33
|
end
|
34
34
|
|
35
35
|
def table
|
@@ -76,8 +76,8 @@ module ActiveRecord
|
|
76
76
|
else
|
77
77
|
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
78
78
|
# Make several smaller queries if necessary or make one query if the adapter supports it
|
79
|
-
sliced = owner_keys.each_slice(
|
80
|
-
records = sliced.map { |slice| records_for(slice) }.flatten
|
79
|
+
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
81
|
end
|
82
82
|
|
83
83
|
# Each record may have multiple owners, and vice-versa
|
@@ -92,33 +92,29 @@ module ActiveRecord
|
|
92
92
|
records_by_owner
|
93
93
|
end
|
94
94
|
|
95
|
+
def reflection_scope
|
96
|
+
@reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
|
97
|
+
end
|
98
|
+
|
95
99
|
def build_scope
|
96
|
-
scope = klass.
|
100
|
+
scope = klass.unscoped
|
101
|
+
scope.default_scoped = true
|
97
102
|
|
98
|
-
|
99
|
-
|
103
|
+
values = reflection_scope.values
|
104
|
+
preload_values = preload_scope.values
|
100
105
|
|
101
|
-
scope
|
102
|
-
scope =
|
106
|
+
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
107
|
+
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
108
|
+
|
109
|
+
scope.select! preload_values[:select] || values[:select] || table[Arel.star]
|
110
|
+
scope.includes! preload_values[:includes] || values[:includes]
|
103
111
|
|
104
112
|
if options[:as]
|
105
|
-
scope
|
106
|
-
klass.table_name => {
|
107
|
-
reflection.type => model.base_class.sti_name
|
108
|
-
}
|
109
|
-
)
|
113
|
+
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
110
114
|
end
|
111
115
|
|
112
116
|
scope
|
113
117
|
end
|
114
|
-
|
115
|
-
def process_conditions(conditions)
|
116
|
-
if conditions.respond_to?(:to_proc) && !conditions.is_a?(Hash)
|
117
|
-
conditions = klass.send(:instance_eval, &conditions)
|
118
|
-
end
|
119
|
-
|
120
|
-
conditions
|
121
|
-
end
|
122
118
|
end
|
123
119
|
end
|
124
120
|
end
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
|
7
7
|
def initialize(klass, records, reflection, preload_options)
|
8
8
|
super
|
9
|
-
@join_table = Arel::Table.new(
|
9
|
+
@join_table = Arel::Table.new(reflection.join_table).alias('t0')
|
10
10
|
end
|
11
11
|
|
12
12
|
# Unlike the other associations, we want to get a raw array of rows so that we can
|
@@ -14,10 +14,7 @@ module ActiveRecord
|
|
14
14
|
def associated_records_by_owner
|
15
15
|
through_records = through_records_by_owner
|
16
16
|
|
17
|
-
|
18
|
-
through_records.values.flatten,
|
19
|
-
source_reflection.name, options
|
20
|
-
).run
|
17
|
+
Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
|
21
18
|
|
22
19
|
through_records.each do |owner, records|
|
23
20
|
records.map! { |r| r.send(source_reflection.name) }.flatten!
|
@@ -28,16 +25,13 @@ module ActiveRecord
|
|
28
25
|
private
|
29
26
|
|
30
27
|
def through_records_by_owner
|
31
|
-
|
32
|
-
owners, through_reflection.name,
|
33
|
-
through_options
|
34
|
-
).run
|
28
|
+
Preloader.new(owners, through_reflection.name, through_scope).run
|
35
29
|
|
36
30
|
Hash[owners.map do |owner|
|
37
31
|
through_records = Array.wrap(owner.send(through_reflection.name))
|
38
32
|
|
39
33
|
# Dont cache the association - we would only be caching a subset
|
40
|
-
if (
|
34
|
+
if (through_scope != through_reflection.klass.unscoped) ||
|
41
35
|
(reflection.options[:source_type] && through_reflection.collection?)
|
42
36
|
owner.association(through_reflection.name).reset
|
43
37
|
end
|
@@ -46,20 +40,22 @@ module ActiveRecord
|
|
46
40
|
end]
|
47
41
|
end
|
48
42
|
|
49
|
-
def
|
50
|
-
|
43
|
+
def through_scope
|
44
|
+
through_scope = through_reflection.klass.unscoped
|
51
45
|
|
52
46
|
if options[:source_type]
|
53
|
-
|
47
|
+
through_scope.where! reflection.foreign_type => options[:source_type]
|
54
48
|
else
|
55
|
-
|
56
|
-
|
57
|
-
|
49
|
+
unless reflection_scope.where_values.empty?
|
50
|
+
through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
51
|
+
through_scope.where_values = reflection_scope.values[:where]
|
58
52
|
end
|
59
|
-
|
53
|
+
|
54
|
+
through_scope.references! reflection_scope.values[:references]
|
55
|
+
through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading?
|
60
56
|
end
|
61
57
|
|
62
|
-
|
58
|
+
through_scope
|
63
59
|
end
|
64
60
|
end
|
65
61
|
end
|
@@ -12,21 +12,21 @@ module ActiveRecord
|
|
12
12
|
target
|
13
13
|
end
|
14
14
|
|
15
|
-
# Implements the writer method, e.g. foo.
|
15
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
16
16
|
def writer(record)
|
17
17
|
replace(record)
|
18
18
|
end
|
19
19
|
|
20
|
-
def create(attributes = {},
|
21
|
-
create_record(attributes,
|
20
|
+
def create(attributes = {}, &block)
|
21
|
+
create_record(attributes, &block)
|
22
22
|
end
|
23
23
|
|
24
|
-
def create!(attributes = {},
|
25
|
-
create_record(attributes,
|
24
|
+
def create!(attributes = {}, &block)
|
25
|
+
create_record(attributes, true, &block)
|
26
26
|
end
|
27
27
|
|
28
|
-
def build(attributes = {}
|
29
|
-
record = build_record(attributes
|
28
|
+
def build(attributes = {})
|
29
|
+
record = build_record(attributes)
|
30
30
|
yield(record) if block_given?
|
31
31
|
set_new_record(record)
|
32
32
|
record
|
@@ -35,11 +35,11 @@ module ActiveRecord
|
|
35
35
|
private
|
36
36
|
|
37
37
|
def create_scope
|
38
|
-
|
38
|
+
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
39
39
|
end
|
40
40
|
|
41
41
|
def find_target
|
42
|
-
|
42
|
+
scope.first.tap { |record| set_inverse_instance(record) }
|
43
43
|
end
|
44
44
|
|
45
45
|
# Implemented by subclasses
|
@@ -51,8 +51,8 @@ module ActiveRecord
|
|
51
51
|
replace(record)
|
52
52
|
end
|
53
53
|
|
54
|
-
def create_record(attributes,
|
55
|
-
record = build_record(attributes
|
54
|
+
def create_record(attributes, raise_error = false)
|
55
|
+
record = build_record(attributes)
|
56
56
|
yield(record) if block_given?
|
57
57
|
saved = record.save
|
58
58
|
set_new_record(record)
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
scope = super
|
16
16
|
chain[1..-1].each do |reflection|
|
17
17
|
scope = scope.merge(
|
18
|
-
reflection.klass.
|
18
|
+
reflection.klass.all.with_default_scope.
|
19
19
|
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
20
20
|
)
|
21
21
|
end
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
# methods which create and delete records on the association.
|
29
29
|
#
|
30
30
|
# We only support indirectly modifying through associations which has a belongs_to source.
|
31
|
-
# This is the "has_many :tags, :
|
31
|
+
# This is the "has_many :tags, through: :taggings" situation, where the join model
|
32
32
|
# typically has a belongs_to on both side. In other words, associations which could also
|
33
33
|
# be represented as has_and_belongs_to_many associations.
|
34
34
|
#
|
@@ -1,206 +1,91 @@
|
|
1
|
-
require 'active_support/concern'
|
2
1
|
|
3
2
|
module ActiveRecord
|
4
3
|
module AttributeAssignment
|
5
4
|
extend ActiveSupport::Concern
|
6
|
-
include ActiveModel::
|
5
|
+
include ActiveModel::DeprecatedMassAssignmentSecurity
|
6
|
+
include ActiveModel::ForbiddenAttributesProtection
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
12
|
-
def attributes_protected_by_default
|
13
|
-
default = [ primary_key, inheritance_column ]
|
14
|
-
default << 'id' unless primary_key.eql? 'id'
|
15
|
-
default
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Allows you to set all the attributes at once by passing in a hash with keys
|
20
|
-
# matching the attribute names (which again matches the column names).
|
21
|
-
#
|
22
|
-
# If any attributes are protected by either +attr_protected+ or
|
23
|
-
# +attr_accessible+ then only settable attributes will be assigned.
|
24
|
-
#
|
25
|
-
# class User < ActiveRecord::Base
|
26
|
-
# attr_protected :is_admin
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# user = User.new
|
30
|
-
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
31
|
-
# user.username # => "Phusion"
|
32
|
-
# user.is_admin? # => false
|
33
|
-
def attributes=(new_attributes)
|
34
|
-
return unless new_attributes.is_a?(Hash)
|
35
|
-
|
36
|
-
assign_attributes(new_attributes)
|
37
|
-
end
|
38
|
-
|
39
|
-
# Allows you to set all the attributes for a particular mass-assignment
|
40
|
-
# security role by passing in a hash of attributes with keys matching
|
41
|
-
# the attribute names (which again matches the column names) and the role
|
42
|
-
# name using the :as option.
|
8
|
+
# Allows you to set all the attributes by passing in a hash of attributes with
|
9
|
+
# keys matching the attribute names (which again matches the column names).
|
43
10
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
|
48
|
-
# attr_accessible :name
|
49
|
-
# attr_accessible :name, :is_admin, :as => :admin
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# user = User.new
|
53
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true })
|
54
|
-
# user.name # => "Josh"
|
55
|
-
# user.is_admin? # => false
|
56
|
-
#
|
57
|
-
# user = User.new
|
58
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
|
59
|
-
# user.name # => "Josh"
|
60
|
-
# user.is_admin? # => true
|
61
|
-
#
|
62
|
-
# user = User.new
|
63
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
|
64
|
-
# user.name # => "Josh"
|
65
|
-
# user.is_admin? # => true
|
66
|
-
def assign_attributes(new_attributes, options = {})
|
11
|
+
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
12
|
+
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
|
+
# exception is raised.
|
14
|
+
def assign_attributes(new_attributes)
|
67
15
|
return if new_attributes.blank?
|
68
16
|
|
69
|
-
attributes
|
70
|
-
multi_parameter_attributes
|
17
|
+
attributes = new_attributes.stringify_keys
|
18
|
+
multi_parameter_attributes = []
|
71
19
|
nested_parameter_attributes = []
|
72
|
-
@mass_assignment_options = options
|
73
20
|
|
74
|
-
|
75
|
-
attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
|
76
|
-
end
|
21
|
+
attributes = sanitize_for_mass_assignment(attributes)
|
77
22
|
|
78
23
|
attributes.each do |k, v|
|
79
24
|
if k.include?("(")
|
80
25
|
multi_parameter_attributes << [ k, v ]
|
81
|
-
elsif
|
82
|
-
|
83
|
-
nested_parameter_attributes << [ k, v ]
|
84
|
-
else
|
85
|
-
send("#{k}=", v)
|
86
|
-
end
|
26
|
+
elsif v.is_a?(Hash)
|
27
|
+
nested_parameter_attributes << [ k, v ]
|
87
28
|
else
|
88
|
-
|
29
|
+
_assign_attribute(k, v)
|
89
30
|
end
|
90
31
|
end
|
91
32
|
|
92
|
-
|
93
|
-
|
94
|
-
send("#{k}=", v)
|
95
|
-
end
|
96
|
-
|
97
|
-
@mass_assignment_options = nil
|
98
|
-
assign_multiparameter_attributes(multi_parameter_attributes)
|
33
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
34
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
99
35
|
end
|
100
36
|
|
101
|
-
|
37
|
+
alias attributes= assign_attributes
|
102
38
|
|
103
|
-
|
104
|
-
@mass_assignment_options ||= {}
|
105
|
-
end
|
39
|
+
private
|
106
40
|
|
107
|
-
def
|
108
|
-
|
41
|
+
def _assign_attribute(k, v)
|
42
|
+
public_send("#{k}=", v)
|
43
|
+
rescue NoMethodError
|
44
|
+
if respond_to?("#{k}=")
|
45
|
+
raise
|
46
|
+
else
|
47
|
+
raise UnknownAttributeError, "unknown attribute: #{k}"
|
48
|
+
end
|
109
49
|
end
|
110
50
|
|
111
|
-
|
51
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
52
|
+
def assign_nested_parameter_attributes(pairs)
|
53
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
54
|
+
end
|
112
55
|
|
113
56
|
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
114
57
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
115
58
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
116
59
|
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
117
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum
|
118
|
-
# f for Float
|
119
|
-
# attribute will be set to nil.
|
60
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
|
61
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
120
62
|
def assign_multiparameter_attributes(pairs)
|
121
63
|
execute_callstack_for_multiparameter_attributes(
|
122
64
|
extract_callstack_for_multiparameter_attributes(pairs)
|
123
65
|
)
|
124
66
|
end
|
125
67
|
|
126
|
-
def instantiate_time_object(name, values)
|
127
|
-
if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
|
128
|
-
Time.zone.local(*values)
|
129
|
-
else
|
130
|
-
Time.time_with_datetime_fallback(self.class.default_timezone, *values)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
68
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
135
69
|
errors = []
|
136
70
|
callstack.each do |name, values_with_empty_parameters|
|
137
71
|
begin
|
138
|
-
send(name
|
72
|
+
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
|
139
73
|
rescue => ex
|
140
|
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
|
74
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
141
75
|
end
|
142
76
|
end
|
143
77
|
unless errors.empty?
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
def read_value_from_parameter(name, values_hash_from_param)
|
149
|
-
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
150
|
-
if values_hash_from_param.values.all?{|v|v.nil?}
|
151
|
-
nil
|
152
|
-
elsif klass == Time
|
153
|
-
read_time_parameter_value(name, values_hash_from_param)
|
154
|
-
elsif klass == Date
|
155
|
-
read_date_parameter_value(name, values_hash_from_param)
|
156
|
-
else
|
157
|
-
read_other_parameter_value(klass, name, values_hash_from_param)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def read_time_parameter_value(name, values_hash_from_param)
|
162
|
-
# If Date bits were not provided, error
|
163
|
-
raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
|
164
|
-
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
|
165
|
-
# If Date bits were provided but blank, then return nil
|
166
|
-
return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
|
167
|
-
|
168
|
-
set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
|
169
|
-
# If Time bits are not there, then default to 0
|
170
|
-
(3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
|
171
|
-
instantiate_time_object(name, set_values)
|
172
|
-
end
|
173
|
-
|
174
|
-
def read_date_parameter_value(name, values_hash_from_param)
|
175
|
-
return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
|
176
|
-
set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
|
177
|
-
begin
|
178
|
-
Date.new(*set_values)
|
179
|
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
180
|
-
instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def read_other_parameter_value(klass, name, values_hash_from_param)
|
185
|
-
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
|
186
|
-
values = (1..max_position).collect do |position|
|
187
|
-
raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
|
188
|
-
values_hash_from_param[position]
|
78
|
+
error_descriptions = errors.map { |ex| ex.message }.join(",")
|
79
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
189
80
|
end
|
190
|
-
klass.new(*values)
|
191
|
-
end
|
192
|
-
|
193
|
-
def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
|
194
|
-
[values_hash_from_param.keys.max,upper_cap].min
|
195
81
|
end
|
196
82
|
|
197
83
|
def extract_callstack_for_multiparameter_attributes(pairs)
|
198
84
|
attributes = { }
|
199
85
|
|
200
|
-
pairs.each do |
|
201
|
-
multiparameter_name, value = pair
|
86
|
+
pairs.each do |(multiparameter_name, value)|
|
202
87
|
attribute_name = multiparameter_name.split("(").first
|
203
|
-
attributes[attribute_name]
|
88
|
+
attributes[attribute_name] ||= {}
|
204
89
|
|
205
90
|
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
206
91
|
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
@@ -217,5 +102,100 @@ module ActiveRecord
|
|
217
102
|
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
218
103
|
end
|
219
104
|
|
105
|
+
class MultiparameterAttribute #:nodoc:
|
106
|
+
attr_reader :object, :name, :values, :column
|
107
|
+
|
108
|
+
def initialize(object, name, values)
|
109
|
+
@object = object
|
110
|
+
@name = name
|
111
|
+
@values = values
|
112
|
+
end
|
113
|
+
|
114
|
+
def read_value
|
115
|
+
return if values.values.compact.empty?
|
116
|
+
|
117
|
+
@column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
|
118
|
+
klass = column.klass
|
119
|
+
|
120
|
+
if klass == Time
|
121
|
+
read_time
|
122
|
+
elsif klass == Date
|
123
|
+
read_date
|
124
|
+
else
|
125
|
+
read_other(klass)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def instantiate_time_object(set_values)
|
132
|
+
if object.class.send(:create_time_zone_conversion_attribute?, name, column)
|
133
|
+
Time.zone.local(*set_values)
|
134
|
+
else
|
135
|
+
Time.send(object.class.default_timezone, *set_values)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def read_time
|
140
|
+
# If column is a :time (and not :date or :timestamp) there is no need to validate if
|
141
|
+
# there are year/month/day fields
|
142
|
+
if column.type == :time
|
143
|
+
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
|
144
|
+
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
|
145
|
+
values[key] ||= value
|
146
|
+
end
|
147
|
+
else
|
148
|
+
# else column is a timestamp, so if Date bits were not provided, error
|
149
|
+
validate_missing_parameters!([1,2,3])
|
150
|
+
|
151
|
+
# If Date bits were provided but blank, then return nil
|
152
|
+
return if blank_date_parameter?
|
153
|
+
end
|
154
|
+
|
155
|
+
max_position = extract_max_param(6)
|
156
|
+
set_values = values.values_at(*(1..max_position))
|
157
|
+
# If Time bits are not there, then default to 0
|
158
|
+
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
|
159
|
+
instantiate_time_object(set_values)
|
160
|
+
end
|
161
|
+
|
162
|
+
def read_date
|
163
|
+
return if blank_date_parameter?
|
164
|
+
set_values = values.values_at(1,2,3)
|
165
|
+
begin
|
166
|
+
Date.new(*set_values)
|
167
|
+
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
168
|
+
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def read_other(klass)
|
173
|
+
max_position = extract_max_param
|
174
|
+
positions = (1..max_position)
|
175
|
+
validate_missing_parameters!(positions)
|
176
|
+
|
177
|
+
set_values = values.values_at(*positions)
|
178
|
+
klass.new(*set_values)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Checks whether some blank date parameter exists. Note that this is different
|
182
|
+
# than the validate_missing_parameters! method, since it just checks for blank
|
183
|
+
# positions instead of missing ones, and does not raise in case one blank position
|
184
|
+
# exists. The caller is responsible to handle the case of this returning true.
|
185
|
+
def blank_date_parameter?
|
186
|
+
(1..3).any? { |position| values[position].blank? }
|
187
|
+
end
|
188
|
+
|
189
|
+
# If some position is not provided, it errors out a missing parameter exception.
|
190
|
+
def validate_missing_parameters!(positions)
|
191
|
+
if missing_parameter = positions.detect { |position| !values.key?(position) }
|
192
|
+
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def extract_max_param(upper_cap = 100)
|
197
|
+
[values.keys.max, upper_cap].min
|
198
|
+
end
|
199
|
+
end
|
220
200
|
end
|
221
201
|
end
|