activerecord 4.1.0.beta2 → 4.1.0.rc1
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 +622 -9
- data/MIT-LICENSE +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/active_record/associations.rb +10 -7
- data/lib/active_record/associations/alias_tracker.rb +39 -29
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/association_scope.rb +56 -31
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
- data/lib/active_record/associations/builder/association.rb +6 -0
- data/lib/active_record/associations/builder/belongs_to.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +33 -9
- data/lib/active_record/associations/collection_proxy.rb +53 -5
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +28 -5
- data/lib/active_record/attribute_methods/dirty.rb +27 -4
- data/lib/active_record/attribute_methods/read.rb +1 -1
- data/lib/active_record/attribute_methods/serialization.rb +18 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
- data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
- data/lib/active_record/connection_handling.rb +64 -3
- data/lib/active_record/core.rb +28 -24
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +111 -17
- data/lib/active_record/errors.rb +12 -0
- data/lib/active_record/fixtures.rb +13 -15
- data/lib/active_record/inheritance.rb +29 -9
- data/lib/active_record/integration.rb +4 -2
- data/lib/active_record/migration.rb +20 -7
- data/lib/active_record/migration/command_recorder.rb +18 -6
- data/lib/active_record/persistence.rb +10 -5
- data/lib/active_record/querying.rb +1 -0
- data/lib/active_record/railtie.rb +11 -8
- data/lib/active_record/railties/databases.rake +24 -38
- data/lib/active_record/relation.rb +3 -2
- data/lib/active_record/relation/batches.rb +24 -9
- data/lib/active_record/relation/finder_methods.rb +100 -11
- data/lib/active_record/relation/query_methods.rb +39 -27
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +7 -5
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/scoping/named.rb +6 -0
- data/lib/active_record/store.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +45 -23
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +7 -7
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- metadata +5 -6
- data/lib/active_record/associations/join_helper.rb +0 -36
@@ -37,6 +37,8 @@ module ActiveRecord
|
|
37
37
|
def not(opts, *rest)
|
38
38
|
where_value = @scope.send(:build_where, opts, rest).map do |rel|
|
39
39
|
case rel
|
40
|
+
when NilClass
|
41
|
+
raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
|
40
42
|
when Arel::Nodes::In
|
41
43
|
Arel::Nodes::NotIn.new(rel.left, rel.right)
|
42
44
|
when Arel::Nodes::Equality
|
@@ -118,6 +120,9 @@ module ActiveRecord
|
|
118
120
|
# Will throw an error, but this will work:
|
119
121
|
#
|
120
122
|
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
123
|
+
#
|
124
|
+
# Note that +includes+ works with association names while +references+ needs
|
125
|
+
# the actual table name.
|
121
126
|
def includes(*args)
|
122
127
|
check_if_method_has_arguments!(:includes, args)
|
123
128
|
spawn.includes!(*args)
|
@@ -161,24 +166,26 @@ module ActiveRecord
|
|
161
166
|
self
|
162
167
|
end
|
163
168
|
|
164
|
-
#
|
165
|
-
# therefore be JOINed in any query rather than loaded separately.
|
169
|
+
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
170
|
+
# and should therefore be JOINed in any query rather than loaded separately.
|
171
|
+
# This method only works in conjuction with +includes+.
|
172
|
+
# See #includes for more details.
|
166
173
|
#
|
167
174
|
# User.includes(:posts).where("posts.name = 'foo'")
|
168
175
|
# # => Doesn't JOIN the posts table, resulting in an error.
|
169
176
|
#
|
170
177
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
171
178
|
# # => Query now knows the string references posts, so adds a JOIN
|
172
|
-
def references(*
|
173
|
-
check_if_method_has_arguments!(:references,
|
174
|
-
spawn.references!(*
|
179
|
+
def references(*table_names)
|
180
|
+
check_if_method_has_arguments!(:references, table_names)
|
181
|
+
spawn.references!(*table_names)
|
175
182
|
end
|
176
183
|
|
177
|
-
def references!(*
|
178
|
-
|
179
|
-
|
184
|
+
def references!(*table_names) # :nodoc:
|
185
|
+
table_names.flatten!
|
186
|
+
table_names.map!(&:to_s)
|
180
187
|
|
181
|
-
self.references_values |=
|
188
|
+
self.references_values |= table_names
|
182
189
|
self
|
183
190
|
end
|
184
191
|
|
@@ -232,7 +239,9 @@ module ActiveRecord
|
|
232
239
|
|
233
240
|
def select!(*fields) # :nodoc:
|
234
241
|
fields.flatten!
|
235
|
-
|
242
|
+
fields.map! do |field|
|
243
|
+
klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
|
244
|
+
end
|
236
245
|
self.select_values += fields
|
237
246
|
self
|
238
247
|
end
|
@@ -631,12 +640,11 @@ module ActiveRecord
|
|
631
640
|
self
|
632
641
|
end
|
633
642
|
|
634
|
-
# Returns a chainable relation with zero records
|
635
|
-
# instance of the <tt>ActiveRecord::NullRelation</tt> class.
|
643
|
+
# Returns a chainable relation with zero records.
|
636
644
|
#
|
637
|
-
# The returned
|
638
|
-
#
|
639
|
-
#
|
645
|
+
# The returned relation implements the Null Object pattern. It is an
|
646
|
+
# object with defined null behavior and always returns an empty array of
|
647
|
+
# records without querying the database.
|
640
648
|
#
|
641
649
|
# Any subsequent condition chained to the returned relation will continue
|
642
650
|
# generating an empty relation and will not fire any query to the database.
|
@@ -816,11 +824,12 @@ module ActiveRecord
|
|
816
824
|
end
|
817
825
|
|
818
826
|
# Returns the Arel object associated with the relation.
|
819
|
-
def arel
|
827
|
+
def arel # :nodoc:
|
820
828
|
@arel ||= build_arel
|
821
829
|
end
|
822
830
|
|
823
|
-
|
831
|
+
private
|
832
|
+
|
824
833
|
def build_arel
|
825
834
|
arel = Arel::SelectManager.new(table.engine, table)
|
826
835
|
|
@@ -846,19 +855,17 @@ module ActiveRecord
|
|
846
855
|
arel
|
847
856
|
end
|
848
857
|
|
849
|
-
private
|
850
|
-
|
851
858
|
def symbol_unscoping(scope)
|
852
859
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
853
860
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
854
861
|
end
|
855
862
|
|
856
863
|
single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
|
857
|
-
unscope_code =
|
864
|
+
unscope_code = "#{scope}_value#{'s' unless single_val_method}="
|
858
865
|
|
859
866
|
case scope
|
860
867
|
when :order
|
861
|
-
self.
|
868
|
+
self.reverse_order_value = false
|
862
869
|
result = []
|
863
870
|
else
|
864
871
|
result = [] unless single_val_method
|
@@ -868,17 +875,17 @@ module ActiveRecord
|
|
868
875
|
end
|
869
876
|
|
870
877
|
def where_unscoping(target_value)
|
871
|
-
|
878
|
+
target_value = target_value.to_s
|
872
879
|
|
873
880
|
where_values.reject! do |rel|
|
874
881
|
case rel
|
875
882
|
when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
|
876
883
|
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
|
877
|
-
subrelation.name
|
878
|
-
else
|
879
|
-
raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
|
884
|
+
subrelation.name == target_value
|
880
885
|
end
|
881
886
|
end
|
887
|
+
|
888
|
+
bind_values.reject! { |col,_| col.name == target_value }
|
882
889
|
end
|
883
890
|
|
884
891
|
def custom_join_ast(table, joins)
|
@@ -983,8 +990,11 @@ module ActiveRecord
|
|
983
990
|
end
|
984
991
|
|
985
992
|
def build_select(arel, selects)
|
986
|
-
|
987
|
-
|
993
|
+
if !selects.empty?
|
994
|
+
expanded_select = selects.map do |field|
|
995
|
+
columns_hash.key?(field.to_s) ? arel_table[field] : field
|
996
|
+
end
|
997
|
+
arel.project(*expanded_select)
|
988
998
|
else
|
989
999
|
arel.project(@klass.arel_table[Arel.star])
|
990
1000
|
end
|
@@ -1040,9 +1050,11 @@ module ActiveRecord
|
|
1040
1050
|
order_args.map! do |arg|
|
1041
1051
|
case arg
|
1042
1052
|
when Symbol
|
1053
|
+
arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
|
1043
1054
|
table[arg].asc
|
1044
1055
|
when Hash
|
1045
1056
|
arg.map { |field, dir|
|
1057
|
+
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
|
1046
1058
|
table[field].send(dir)
|
1047
1059
|
}
|
1048
1060
|
else
|
data/lib/active_record/result.rb
CHANGED
@@ -29,6 +29,7 @@ module ActiveRecord
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
alias_method :sanitize_sql, :sanitize_sql_for_conditions
|
32
|
+
alias_method :sanitize_conditions, :sanitize_sql
|
32
33
|
|
33
34
|
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
34
35
|
# them into a valid SQL fragment for a SET clause.
|
@@ -100,8 +101,9 @@ module ActiveRecord
|
|
100
101
|
# { status: nil, group_id: 1 }
|
101
102
|
# # => "status = NULL , group_id = 1"
|
102
103
|
def sanitize_sql_hash_for_assignment(attrs, table)
|
104
|
+
c = connection
|
103
105
|
attrs.map do |attr, value|
|
104
|
-
"#{
|
106
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
|
105
107
|
end.join(', ')
|
106
108
|
end
|
107
109
|
|
@@ -121,8 +123,6 @@ module ActiveRecord
|
|
121
123
|
end
|
122
124
|
end
|
123
125
|
|
124
|
-
alias_method :sanitize_conditions, :sanitize_sql
|
125
|
-
|
126
126
|
def replace_bind_variables(statement, values) #:nodoc:
|
127
127
|
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
128
128
|
bound = values.dup
|
@@ -152,8 +152,10 @@ module ActiveRecord
|
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
155
|
-
def quote_bound_value(value, c = connection) #:nodoc:
|
156
|
-
if
|
155
|
+
def quote_bound_value(value, c = connection, column = nil) #:nodoc:
|
156
|
+
if column
|
157
|
+
c.quote(value, column)
|
158
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
157
159
|
if value.respond_to?(:empty?) && value.empty?
|
158
160
|
c.quote(nil)
|
159
161
|
else
|
@@ -27,6 +27,11 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
def initialize_internals_callback
|
31
|
+
super
|
32
|
+
populate_with_current_scope_attributes
|
33
|
+
end
|
34
|
+
|
30
35
|
# This class stores the +:current_scope+ and +:ignore_default_scope+ values
|
31
36
|
# for different classes. The registry is stored as a thread local, which is
|
32
37
|
# accessed through +ScopeRegistry.current+.
|
@@ -139,6 +139,12 @@ module ActiveRecord
|
|
139
139
|
# Article.published.featured.latest_article
|
140
140
|
# Article.featured.titles
|
141
141
|
def scope(name, body, &block)
|
142
|
+
if dangerous_class_method?(name)
|
143
|
+
raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
|
144
|
+
"on the model \"#{self.name}\", but Active Record already defined " \
|
145
|
+
"a class method with the same name."
|
146
|
+
end
|
147
|
+
|
142
148
|
extension = Module.new(&block) if block
|
143
149
|
|
144
150
|
singleton_class.send(:define_method, name) do |*args|
|
data/lib/active_record/store.rb
CHANGED
@@ -36,9 +36,8 @@ module ActiveRecord
|
|
36
36
|
module DatabaseTasks
|
37
37
|
extend self
|
38
38
|
|
39
|
-
attr_writer :current_config
|
40
|
-
attr_accessor :database_configuration
|
41
|
-
:fixtures_path, :env, :root
|
39
|
+
attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
|
40
|
+
attr_accessor :database_configuration
|
42
41
|
|
43
42
|
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
|
44
43
|
|
@@ -51,16 +50,36 @@ module ActiveRecord
|
|
51
50
|
register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
|
52
51
|
register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
|
53
52
|
|
53
|
+
def db_dir
|
54
|
+
@db_dir ||= Rails.application.config.paths["db"].first
|
55
|
+
end
|
56
|
+
|
57
|
+
def migrations_paths
|
58
|
+
@migrations_paths ||= Rails.application.paths['db/migrate'].to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
def fixtures_path
|
62
|
+
@fixtures_path ||= File.join(root, 'test', 'fixtures')
|
63
|
+
end
|
64
|
+
|
65
|
+
def root
|
66
|
+
@root ||= Rails.root
|
67
|
+
end
|
68
|
+
|
69
|
+
def env
|
70
|
+
@env ||= Rails.env
|
71
|
+
end
|
72
|
+
|
73
|
+
def seed_loader
|
74
|
+
@seed_loader ||= Rails.application
|
75
|
+
end
|
76
|
+
|
54
77
|
def current_config(options = {})
|
55
78
|
options.reverse_merge! :env => env
|
56
79
|
if options.has_key?(:config)
|
57
80
|
@current_config = options[:config]
|
58
81
|
else
|
59
|
-
@current_config ||=
|
60
|
-
database_url_config
|
61
|
-
else
|
62
|
-
ActiveRecord::Base.configurations[options[:env]]
|
63
|
-
end
|
82
|
+
@current_config ||= ActiveRecord::Base.configurations[options[:env]]
|
64
83
|
end
|
65
84
|
end
|
66
85
|
|
@@ -82,11 +101,7 @@ module ActiveRecord
|
|
82
101
|
each_current_configuration(environment) { |configuration|
|
83
102
|
create configuration
|
84
103
|
}
|
85
|
-
ActiveRecord::Base.establish_connection
|
86
|
-
end
|
87
|
-
|
88
|
-
def create_database_url
|
89
|
-
create database_url_config
|
104
|
+
ActiveRecord::Base.establish_connection(environment.to_sym)
|
90
105
|
end
|
91
106
|
|
92
107
|
def drop(*arguments)
|
@@ -107,10 +122,6 @@ module ActiveRecord
|
|
107
122
|
}
|
108
123
|
end
|
109
124
|
|
110
|
-
def drop_database_url
|
111
|
-
drop database_url_config
|
112
|
-
end
|
113
|
-
|
114
125
|
def charset_current(environment = env)
|
115
126
|
charset ActiveRecord::Base.configurations[environment]
|
116
127
|
end
|
@@ -145,6 +156,21 @@ module ActiveRecord
|
|
145
156
|
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
|
146
157
|
end
|
147
158
|
|
159
|
+
def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
|
160
|
+
case format
|
161
|
+
when :ruby
|
162
|
+
file ||= File.join(db_dir, "schema.rb")
|
163
|
+
check_schema_file(file)
|
164
|
+
load(file)
|
165
|
+
when :sql
|
166
|
+
file ||= File.join(db_dir, "structure.sql")
|
167
|
+
check_schema_file(file)
|
168
|
+
structure_load(current_config, file)
|
169
|
+
else
|
170
|
+
raise ArgumentError, "unknown format #{format.inspect}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
148
174
|
def check_schema_file(filename)
|
149
175
|
unless File.exist?(filename)
|
150
176
|
message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
|
@@ -165,11 +191,6 @@ module ActiveRecord
|
|
165
191
|
|
166
192
|
private
|
167
193
|
|
168
|
-
def database_url_config
|
169
|
-
@database_url_config ||=
|
170
|
-
ConnectionAdapters::ConnectionSpecification::Resolver.new(ENV["DATABASE_URL"], {}).spec.config.stringify_keys
|
171
|
-
end
|
172
|
-
|
173
194
|
def class_for_adapter(adapter)
|
174
195
|
key = @tasks.keys.detect { |pattern| adapter[pattern] }
|
175
196
|
unless key
|
@@ -180,7 +201,8 @@ module ActiveRecord
|
|
180
201
|
|
181
202
|
def each_current_configuration(environment)
|
182
203
|
environments = [environment]
|
183
|
-
|
204
|
+
# add test environment only if no RAILS_ENV was specified.
|
205
|
+
environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
|
184
206
|
|
185
207
|
configurations = ActiveRecord::Base.configurations.values_at(*environments)
|
186
208
|
configurations.compact.each do |configuration|
|
@@ -37,8 +37,8 @@ module ActiveRecord
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def initialize_dup(other) # :nodoc:
|
40
|
-
clear_timestamp_attributes
|
41
40
|
super
|
41
|
+
clear_timestamp_attributes
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -71,7 +71,7 @@ module ActiveRecord
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def should_record_timestamps?
|
74
|
-
self.record_timestamps && (!partial_writes? || changed?
|
74
|
+
self.record_timestamps && (!partial_writes? || changed?)
|
75
75
|
end
|
76
76
|
|
77
77
|
def timestamp_attributes_for_create_in_model
|
@@ -6,9 +6,6 @@ module ActiveRecord
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
ACTIONS = [:create, :destroy, :update]
|
8
8
|
|
9
|
-
class TransactionError < ActiveRecordError # :nodoc:
|
10
|
-
end
|
11
|
-
|
12
9
|
included do
|
13
10
|
define_callbacks :commit, :rollback,
|
14
11
|
terminator: ->(_, result) { result == false },
|
@@ -243,15 +240,14 @@ module ActiveRecord
|
|
243
240
|
def set_options_for_callbacks!(args)
|
244
241
|
options = args.last
|
245
242
|
if options.is_a?(Hash) && options[:on]
|
246
|
-
assert_valid_transaction_action(options[:on])
|
247
|
-
options[:if] = Array(options[:if])
|
248
243
|
fire_on = Array(options[:on])
|
244
|
+
assert_valid_transaction_action(fire_on)
|
245
|
+
options[:if] = Array(options[:if])
|
249
246
|
options[:if] << "transaction_include_any_action?(#{fire_on})"
|
250
247
|
end
|
251
248
|
end
|
252
249
|
|
253
250
|
def assert_valid_transaction_action(actions)
|
254
|
-
actions = Array(actions)
|
255
251
|
if (actions - ACTIONS).any?
|
256
252
|
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
|
257
253
|
end
|
@@ -277,6 +273,10 @@ module ActiveRecord
|
|
277
273
|
with_transaction_returning_status { super }
|
278
274
|
end
|
279
275
|
|
276
|
+
def touch(*) #:nodoc:
|
277
|
+
with_transaction_returning_status { super }
|
278
|
+
end
|
279
|
+
|
280
280
|
# Reset id and @new_record if the transaction rolls back.
|
281
281
|
def rollback_active_record_state!
|
282
282
|
remember_transaction_record_state
|
@@ -295,7 +295,7 @@ module ActiveRecord
|
|
295
295
|
def committed! #:nodoc:
|
296
296
|
run_callbacks :commit if destroyed? || persisted?
|
297
297
|
ensure
|
298
|
-
|
298
|
+
@_start_transaction_state.clear
|
299
299
|
end
|
300
300
|
|
301
301
|
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
super
|
6
6
|
attributes.each do |attribute|
|
7
7
|
next unless record.class.reflect_on_association(attribute)
|
8
|
-
associated_records = Array(record.send(attribute))
|
8
|
+
associated_records = Array.wrap(record.send(attribute))
|
9
9
|
|
10
10
|
# Superclass validates presence. Ensure present records aren't about to be destroyed.
|
11
11
|
if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
|