torque-postgresql 2.0.1 → 2.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/torque/postgresql/adapter.rb +7 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
- data/lib/torque/postgresql/adapter/oid.rb +3 -1
- data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
- data/lib/torque/postgresql/adapter/quoting.rb +2 -0
- data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
- data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +11 -2
- data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
- data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
- data/lib/torque/postgresql/arel/join_source.rb +2 -0
- data/lib/torque/postgresql/arel/nodes.rb +2 -0
- data/lib/torque/postgresql/arel/operations.rb +2 -0
- data/lib/torque/postgresql/arel/select_manager.rb +2 -0
- data/lib/torque/postgresql/arel/visitors.rb +6 -3
- data/lib/torque/postgresql/associations/association.rb +14 -3
- data/lib/torque/postgresql/associations/association_scope.rb +2 -0
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +164 -47
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
- data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
- data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
- data/lib/torque/postgresql/attributes/builder.rb +3 -1
- data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
- data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
- data/lib/torque/postgresql/attributes/enum.rb +5 -10
- data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
- data/lib/torque/postgresql/attributes/lazy.rb +3 -1
- data/lib/torque/postgresql/attributes/period.rb +2 -0
- data/lib/torque/postgresql/autosave_association.rb +19 -16
- data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
- data/lib/torque/postgresql/base.rb +5 -2
- data/lib/torque/postgresql/coder.rb +5 -3
- data/lib/torque/postgresql/collector.rb +2 -0
- data/lib/torque/postgresql/config.rb +5 -0
- data/lib/torque/postgresql/geometry_builder.rb +2 -0
- data/lib/torque/postgresql/i18n.rb +2 -0
- data/lib/torque/postgresql/inheritance.rb +2 -0
- data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
- data/lib/torque/postgresql/railtie.rb +2 -0
- data/lib/torque/postgresql/reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
- data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +18 -4
- data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
- data/lib/torque/postgresql/relation.rb +15 -11
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
- data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
- data/lib/torque/postgresql/relation/inheritance.rb +2 -0
- data/lib/torque/postgresql/relation/merger.rb +2 -0
- data/lib/torque/postgresql/schema_cache.rb +2 -0
- data/lib/torque/postgresql/version.rb +3 -1
- data/spec/schema.rb +3 -2
- data/spec/tests/arel_spec.rb +3 -1
- data/spec/tests/belongs_to_many_spec.rb +104 -12
- data/spec/tests/enum_set_spec.rb +1 -1
- data/spec/tests/has_many_spec.rb +25 -1
- data/spec/tests/table_inheritance_spec.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd314ab034c3d38a227829faf2d706b13625a5e4cc6393004f6dbe2890e784a8
|
4
|
+
data.tar.gz: 035b0fc92e1ea2aedb507c18094e66025c0fe98838f8553f3abc64764a0b2f03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6eeaffde8209891fee13de7518acea135cfe5f35ecbc5aa59124faad208ed0545a24c2ffb993f10eb5e306c2ccb96036e1dbc812a11177ecdda5e3ead62e0dd7
|
7
|
+
data.tar.gz: ad55cf8b8a5e9de71575c12e8452d440439529258752ceceb95da17111991b8a9ad90101806ab328d5993d328bec69a71feb4cc6b85431c9802d0c225557dbb5
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'adapter/database_statements'
|
2
4
|
require_relative 'adapter/oid'
|
3
5
|
require_relative 'adapter/quoting'
|
@@ -19,6 +21,11 @@ module Torque
|
|
19
21
|
select_value('SELECT version()').match(/#{Adapter::ADAPTER_NAME} ([\d\.]+)/)[1]
|
20
22
|
)
|
21
23
|
end
|
24
|
+
|
25
|
+
# Add `inherits` to the list of extracted table options
|
26
|
+
def extract_table_options!(options)
|
27
|
+
super.merge(options.extract!(:inherits))
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
31
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
|
@@ -17,9 +17,11 @@ module Torque
|
|
17
17
|
ActiveRecord::Type.register(:circle, OID::Circle, adapter: :postgresql)
|
18
18
|
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
|
19
19
|
ActiveRecord::Type.register(:enum_set, OID::EnumSet, adapter: :postgresql)
|
20
|
-
ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
|
21
20
|
ActiveRecord::Type.register(:line, OID::Line, adapter: :postgresql)
|
22
21
|
ActiveRecord::Type.register(:segment, OID::Segment, adapter: :postgresql)
|
22
|
+
|
23
|
+
ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql) \
|
24
|
+
unless PostgreSQL::AR610
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -24,10 +24,17 @@ module Torque
|
|
24
24
|
end)
|
25
25
|
end
|
26
26
|
|
27
|
+
if respond_to?(:supports_check_constraints?) && supports_check_constraints?
|
28
|
+
statements.concat(o.check_constraints.map do |expression, options|
|
29
|
+
check_constraint_in_create(o.name, expression, options)
|
30
|
+
end)
|
31
|
+
end
|
32
|
+
|
27
33
|
create_sql << "(#{statements.join(', ')})" \
|
28
34
|
if statements.present? || o.inherits.present?
|
29
35
|
|
30
|
-
|
36
|
+
options = PostgreSQL::AR610 ? o : table_options(o)
|
37
|
+
add_table_options!(create_sql, options)
|
31
38
|
|
32
39
|
if o.inherits.present?
|
33
40
|
tables = o.inherits.map(&method(:quote_table_name))
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Torque
|
2
4
|
module PostgreSQL
|
3
5
|
module Adapter
|
@@ -39,7 +41,7 @@ module Torque
|
|
39
41
|
|
40
42
|
def tables(stream) # :nodoc:
|
41
43
|
inherited_tables = @connection.inherited_tables
|
42
|
-
sorted_tables = @connection.
|
44
|
+
sorted_tables = @connection.tables.sort - @connection.views
|
43
45
|
|
44
46
|
stream.puts " # These are the common tables managed"
|
45
47
|
(sorted_tables - inherited_tables.keys).each do |table_name|
|
@@ -67,12 +69,19 @@ module Torque
|
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
70
|
-
#
|
72
|
+
# Dump foreign keys at the end to make sure all dependent tables exist.
|
71
73
|
if @connection.supports_foreign_keys?
|
72
74
|
sorted_tables.each do |tbl|
|
73
75
|
foreign_keys(tbl, stream) unless ignored?(tbl)
|
74
76
|
end
|
75
77
|
end
|
78
|
+
|
79
|
+
# Scenic integration
|
80
|
+
views(stream) if defined?(::Scenic)
|
81
|
+
|
82
|
+
# FX integration
|
83
|
+
functions(stream) if defined?(::Fx::SchemaDumper::Function)
|
84
|
+
triggers(stream) if defined?(::Fx::SchemaDumper::Trigger)
|
76
85
|
end
|
77
86
|
|
78
87
|
# Dump user defined types like enum
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Torque
|
2
4
|
module PostgreSQL
|
3
5
|
module Arel
|
@@ -22,6 +24,8 @@ module Torque
|
|
22
24
|
}.freeze
|
23
25
|
|
24
26
|
INFLIX_OPERATION.each do |operator_name, operator|
|
27
|
+
next if nodes.const_defined?(operator_name)
|
28
|
+
|
25
29
|
klass = Class.new(inflix)
|
26
30
|
klass.send(:define_method, :initialize) { |*args| super(operator, *args) }
|
27
31
|
|
@@ -31,7 +35,7 @@ module Torque
|
|
31
35
|
# Don't worry about quoting here, if the right side is something that
|
32
36
|
# doesn't need quoting, it will leave it as it is
|
33
37
|
Math.send(:define_method, operator_name.underscore) do |other|
|
34
|
-
klass.new(self,
|
38
|
+
klass.new(self, other)
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Torque
|
2
4
|
module PostgreSQL
|
3
5
|
module Arel
|
4
6
|
module Visitors
|
5
7
|
# Enclose select manager with parenthesis
|
6
8
|
# :TODO: Remove when checking the new version of Arel
|
7
|
-
def visit_Arel_SelectManager
|
9
|
+
def visit_Arel_SelectManager(o, collector)
|
8
10
|
collector << '('
|
9
11
|
visit(o.ast, collector) << ')'
|
10
12
|
end
|
@@ -23,8 +25,9 @@ module Torque
|
|
23
25
|
|
24
26
|
# Allow quoted arrays to get here
|
25
27
|
def visit_Arel_Nodes_Casted(o, collector)
|
26
|
-
|
27
|
-
|
28
|
+
value = o.respond_to?(:val) ? o.val : o.value
|
29
|
+
return super unless value.is_a?(::Enumerable)
|
30
|
+
quote_array(value, collector)
|
28
31
|
end
|
29
32
|
|
30
33
|
## TORQUE VISITORS
|
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Torque
|
2
4
|
module PostgreSQL
|
3
5
|
module Associations
|
4
6
|
module Association
|
5
7
|
|
8
|
+
# There is no problem of adding temporary items on target because
|
9
|
+
# CollectionProxy will handle memory and persisted relationship
|
6
10
|
def inversed_from(record)
|
7
11
|
return super unless reflection.connected_through_array?
|
8
12
|
|
@@ -11,20 +15,27 @@ module Torque
|
|
11
15
|
@inversed = self.target.present?
|
12
16
|
end
|
13
17
|
|
18
|
+
# The binds and the cache are getting mixed and caching the wrong query
|
19
|
+
def skip_statement_cache?(*)
|
20
|
+
super || reflection.connected_through_array?
|
21
|
+
end
|
22
|
+
|
14
23
|
private
|
15
24
|
|
25
|
+
# This is mainly for the has many when connect through an array to add
|
26
|
+
# its id to the list of the inverse belongs to many association
|
16
27
|
def set_owner_attributes(record)
|
17
28
|
return super unless reflection.connected_through_array?
|
18
29
|
|
19
30
|
add_id = owner[reflection.active_record_primary_key]
|
20
|
-
|
21
|
-
|
22
|
-
record[record_fk].push(add_id) unless (record[record_fk] ||= []).include?(add_id)
|
31
|
+
list = record[reflection.foreign_key] ||= []
|
32
|
+
list.push(add_id) unless list.include?(add_id)
|
23
33
|
end
|
24
34
|
|
25
35
|
end
|
26
36
|
|
27
37
|
::ActiveRecord::Associations::Association.prepend(Association)
|
38
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(Association)
|
28
39
|
end
|
29
40
|
end
|
30
41
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_record/associations/collection_association'
|
4
|
+
|
2
5
|
# FIXME: build, create
|
3
6
|
module Torque
|
4
7
|
module PostgreSQL
|
@@ -6,10 +9,68 @@ module Torque
|
|
6
9
|
class BelongsToManyAssociation < ::ActiveRecord::Associations::CollectionAssociation
|
7
10
|
include ::ActiveRecord::Associations::ForeignAssociation
|
8
11
|
|
12
|
+
## CUSTOM
|
13
|
+
def ids_reader
|
14
|
+
if loaded?
|
15
|
+
target.pluck(reflection.association_primary_key)
|
16
|
+
elsif !target.empty?
|
17
|
+
load_target.pluck(reflection.association_primary_key)
|
18
|
+
else
|
19
|
+
stale_state
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ids_writer(ids)
|
24
|
+
owner.write_attribute(source_attr, ids.presence)
|
25
|
+
return unless owner.persisted? && owner.attribute_changed?(source_attr)
|
26
|
+
|
27
|
+
owner.update_attribute(source_attr, ids.presence)
|
28
|
+
end
|
29
|
+
|
30
|
+
def size
|
31
|
+
if loaded?
|
32
|
+
target.size
|
33
|
+
elsif !target.empty?
|
34
|
+
unsaved_records = target.select(&:new_record?)
|
35
|
+
unsaved_records.size + stale_state.size
|
36
|
+
else
|
37
|
+
stale_state&.size || 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def empty?
|
42
|
+
size.zero?
|
43
|
+
end
|
44
|
+
|
45
|
+
def include?(record)
|
46
|
+
return false unless record.is_a?(reflection.klass)
|
47
|
+
return include_in_memory?(record) if record.new_record?
|
48
|
+
|
49
|
+
(!target.empty? && target.include?(record)) ||
|
50
|
+
stale_state&.include?(record.read_attribute(klass_attr))
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_target
|
54
|
+
if stale_target? || find_target?
|
55
|
+
@target = merge_target_lists(find_target, target)
|
56
|
+
end
|
57
|
+
|
58
|
+
loaded!
|
59
|
+
target
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_changes
|
63
|
+
@_building_changes = true
|
64
|
+
yield.tap { ids_writer(ids_reader) }
|
65
|
+
ensure
|
66
|
+
@_building_changes = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
## HAS MANY
|
9
70
|
def handle_dependency
|
10
71
|
case options[:dependent]
|
11
72
|
when :restrict_with_exception
|
12
|
-
raise
|
73
|
+
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
|
13
74
|
|
14
75
|
when :restrict_with_error
|
15
76
|
unless empty?
|
@@ -19,88 +80,122 @@ module Torque
|
|
19
80
|
end
|
20
81
|
|
21
82
|
when :destroy
|
22
|
-
# No point in executing the counter update since we're going to destroy the parent anyway
|
23
83
|
load_target.each { |t| t.destroyed_by_association = reflection }
|
24
84
|
destroy_all
|
85
|
+
when :destroy_async
|
86
|
+
load_target.each do |t|
|
87
|
+
t.destroyed_by_association = reflection
|
88
|
+
end
|
89
|
+
|
90
|
+
unless target.empty?
|
91
|
+
association_class = target.first.class
|
92
|
+
primary_key_column = association_class.primary_key.to_sym
|
93
|
+
|
94
|
+
ids = target.collect do |assoc|
|
95
|
+
assoc.public_send(primary_key_column)
|
96
|
+
end
|
97
|
+
|
98
|
+
enqueue_destroy_association(
|
99
|
+
owner_model_name: owner.class.to_s,
|
100
|
+
owner_id: owner.id,
|
101
|
+
association_class: association_class.to_s,
|
102
|
+
association_ids: ids,
|
103
|
+
association_primary_key_column: primary_key_column,
|
104
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
105
|
+
)
|
106
|
+
end
|
25
107
|
else
|
26
108
|
delete_all
|
27
109
|
end
|
28
110
|
end
|
29
111
|
|
30
|
-
def ids_reader
|
31
|
-
owner[reflection.active_record_primary_key]
|
32
|
-
end
|
33
|
-
|
34
|
-
def ids_writer(new_ids)
|
35
|
-
column = reflection.active_record_primary_key
|
36
|
-
command = owner.persisted? ? :update_column : :write_attribute
|
37
|
-
owner.public_send(command, column, new_ids.presence)
|
38
|
-
@association_scope = nil
|
39
|
-
end
|
40
|
-
|
41
112
|
def insert_record(record, *)
|
42
|
-
super
|
43
|
-
|
44
|
-
|
45
|
-
attribute.push(record[klass_fk])
|
46
|
-
record
|
47
|
-
end
|
48
|
-
|
49
|
-
def empty?
|
50
|
-
size.zero?
|
113
|
+
super.tap do |saved|
|
114
|
+
ids_rewriter(record.read_attribute(klass_attr), :<<) if saved
|
115
|
+
end
|
51
116
|
end
|
52
117
|
|
53
|
-
|
54
|
-
|
55
|
-
|
118
|
+
## BELONGS TO
|
119
|
+
def default(&block)
|
120
|
+
writer(owner.instance_exec(&block)) if reader.nil?
|
56
121
|
end
|
57
122
|
|
58
123
|
private
|
59
124
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
125
|
+
## CUSTOM
|
126
|
+
def _create_record(attributes, raises = false, &block)
|
127
|
+
if attributes.is_a?(Array)
|
128
|
+
attributes.collect { |attr| _create_record(attr, raises, &block) }
|
129
|
+
else
|
130
|
+
build_record(attributes, &block).tap do |record|
|
131
|
+
transaction do
|
132
|
+
result = nil
|
133
|
+
add_to_target(record) do
|
134
|
+
result = insert_record(record, true, raises) { @_was_loaded = loaded? }
|
135
|
+
end
|
136
|
+
raise ActiveRecord::Rollback unless result
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
64
140
|
end
|
65
141
|
|
66
|
-
# When the idea is to
|
142
|
+
# When the idea is to nullify the association, then just set the owner
|
67
143
|
# +primary_key+ as empty
|
68
|
-
def delete_count(method, scope, ids
|
69
|
-
|
70
|
-
|
71
|
-
remove_stash_records(ids)
|
144
|
+
def delete_count(method, scope, ids)
|
145
|
+
size_cache = scope.delete_all if method == :delete_all
|
146
|
+
(size_cache || ids.size).tap { ids_rewriter(ids, :-) }
|
72
147
|
end
|
73
148
|
|
74
149
|
def delete_or_nullify_all_records(method)
|
75
|
-
delete_count(method, scope)
|
150
|
+
delete_count(method, scope, ids_reader)
|
76
151
|
end
|
77
152
|
|
78
153
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
79
154
|
def delete_records(records, method)
|
80
|
-
ids =
|
155
|
+
ids = read_records_ids(records)
|
81
156
|
|
82
157
|
if method == :destroy
|
83
158
|
records.each(&:destroy!)
|
84
|
-
|
159
|
+
ids_rewriter(ids, :-)
|
85
160
|
else
|
86
|
-
scope = self.scope.where(
|
161
|
+
scope = self.scope.where(klass_attr => records)
|
87
162
|
delete_count(method, scope, ids)
|
88
163
|
end
|
89
164
|
end
|
90
165
|
|
91
|
-
def
|
92
|
-
|
93
|
-
ids_writer(ids_reader)
|
94
|
-
result
|
166
|
+
def source_attr
|
167
|
+
reflection.foreign_key
|
95
168
|
end
|
96
169
|
|
97
|
-
def
|
98
|
-
|
99
|
-
ids_writer(ids_reader - Array.wrap(ids))
|
170
|
+
def klass_attr
|
171
|
+
reflection.active_record_primary_key
|
100
172
|
end
|
101
173
|
|
102
|
-
def
|
103
|
-
|
174
|
+
def read_records_ids(records)
|
175
|
+
return unless records.present?
|
176
|
+
Array.wrap(records).each_with_object(klass_attr).map(&:read_attribute).presence
|
177
|
+
end
|
178
|
+
|
179
|
+
def ids_rewriter(ids, operator)
|
180
|
+
list = owner[source_attr] ||= []
|
181
|
+
list = list.public_send(operator, ids)
|
182
|
+
owner[source_attr] = list.uniq.compact.presence
|
183
|
+
|
184
|
+
return if @_building_changes || !owner.persisted?
|
185
|
+
owner.update_attribute(source_attr, list)
|
186
|
+
end
|
187
|
+
|
188
|
+
## HAS MANY
|
189
|
+
def replace_records(*)
|
190
|
+
build_changes { super }
|
191
|
+
end
|
192
|
+
|
193
|
+
def concat_records(*)
|
194
|
+
build_changes { super }
|
195
|
+
end
|
196
|
+
|
197
|
+
def delete_or_destroy(*)
|
198
|
+
build_changes { super }
|
104
199
|
end
|
105
200
|
|
106
201
|
def difference(a, b)
|
@@ -110,6 +205,28 @@ module Torque
|
|
110
205
|
def intersection(a, b)
|
111
206
|
a & b
|
112
207
|
end
|
208
|
+
|
209
|
+
## BELONGS TO
|
210
|
+
def scope_for_create
|
211
|
+
super.except!(klass.primary_key)
|
212
|
+
end
|
213
|
+
|
214
|
+
def find_target?
|
215
|
+
!loaded? && foreign_key_present? && klass
|
216
|
+
end
|
217
|
+
|
218
|
+
def foreign_key_present?
|
219
|
+
stale_state.present?
|
220
|
+
end
|
221
|
+
|
222
|
+
def invertible_for?(record)
|
223
|
+
inverse = inverse_reflection_for(record)
|
224
|
+
inverse && (inverse.has_many? && inverse.connected_through_array?)
|
225
|
+
end
|
226
|
+
|
227
|
+
def stale_state
|
228
|
+
owner.read_attribute(source_attr)
|
229
|
+
end
|
113
230
|
end
|
114
231
|
|
115
232
|
::ActiveRecord::Associations.const_set(:BelongsToManyAssociation, BelongsToManyAssociation)
|