duck_record 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/duck_record/associations.rb +41 -347
- data/lib/duck_record/associations/association.rb +267 -0
- data/lib/duck_record/associations/belongs_to_association.rb +71 -0
- data/lib/duck_record/associations/builder/{embeds_association.rb → association.rb} +37 -9
- data/lib/duck_record/associations/builder/belongs_to.rb +44 -0
- data/lib/duck_record/associations/builder/collection_association.rb +45 -0
- data/lib/duck_record/associations/builder/embeds_many.rb +1 -44
- data/lib/duck_record/associations/builder/embeds_one.rb +1 -26
- data/lib/duck_record/associations/builder/has_many.rb +11 -0
- data/lib/duck_record/associations/builder/has_one.rb +20 -0
- data/lib/duck_record/associations/builder/singular_association.rb +33 -0
- data/lib/duck_record/associations/collection_association.rb +476 -0
- data/lib/duck_record/associations/collection_proxy.rb +1160 -0
- data/lib/duck_record/associations/foreign_association.rb +11 -0
- data/lib/duck_record/associations/has_many_association.rb +17 -0
- data/lib/duck_record/associations/has_one_association.rb +39 -0
- data/lib/duck_record/associations/singular_association.rb +73 -0
- data/lib/duck_record/nested_validate_association.rb +1 -1
- data/lib/duck_record/reflection.rb +345 -8
- data/lib/duck_record/version.rb +1 -1
- metadata +17 -4
@@ -0,0 +1,17 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
# = Active Record Has Many Association
|
3
|
+
module Associations
|
4
|
+
# This is the proxy that handles a has many association.
|
5
|
+
#
|
6
|
+
# If the association has a <tt>:through</tt> option further specialization
|
7
|
+
# is provided by its child HasManyThroughAssociation.
|
8
|
+
class HasManyAssociation < CollectionAssociation #:nodoc:
|
9
|
+
include ForeignAssociation
|
10
|
+
|
11
|
+
def insert_record(record, validate = true, raise = false)
|
12
|
+
set_owner_attributes(record)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
# = Active Record Has One Association
|
3
|
+
module Associations
|
4
|
+
class HasOneAssociation < SingularAssociation #:nodoc:
|
5
|
+
include ForeignAssociation
|
6
|
+
|
7
|
+
def replace(record)
|
8
|
+
if owner.class.readonly_attributes.include?(reflection.foreign_key.to_s)
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
raise_on_type_mismatch!(record) if record
|
13
|
+
load_target
|
14
|
+
|
15
|
+
return target unless target || record
|
16
|
+
|
17
|
+
self.target = record
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def foreign_key_present?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# The reason that the save param for replace is false, if for create (not just build),
|
27
|
+
# is because the setting of the foreign keys is actually handled by the scoping when
|
28
|
+
# the record is instantiated, and so they are set straight away and do not need to be
|
29
|
+
# updated within replace.
|
30
|
+
def set_new_record(record)
|
31
|
+
replace(record)
|
32
|
+
end
|
33
|
+
|
34
|
+
def nullify_owner_attributes(record)
|
35
|
+
record[reflection.foreign_key] = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Associations
|
3
|
+
class SingularAssociation < Association #:nodoc:
|
4
|
+
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
|
+
def reader
|
6
|
+
if !loaded? || stale_target?
|
7
|
+
reload
|
8
|
+
end
|
9
|
+
|
10
|
+
target
|
11
|
+
end
|
12
|
+
|
13
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
14
|
+
def writer(record)
|
15
|
+
replace(record)
|
16
|
+
end
|
17
|
+
|
18
|
+
def build(attributes = {})
|
19
|
+
record = build_record(attributes)
|
20
|
+
yield(record) if block_given?
|
21
|
+
set_new_record(record)
|
22
|
+
record
|
23
|
+
end
|
24
|
+
|
25
|
+
# Implements the reload reader method, e.g. foo.reload_bar for
|
26
|
+
# Foo.has_one :bar
|
27
|
+
def force_reload_reader
|
28
|
+
klass.uncached { reload }
|
29
|
+
target
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def create_scope
|
35
|
+
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_target
|
39
|
+
return scope.take if skip_statement_cache?
|
40
|
+
|
41
|
+
conn = klass.connection
|
42
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
43
|
+
ActiveRecord::StatementCache.create(conn) { |params|
|
44
|
+
as = ActiveRecord::Associations::AssociationScope.create { params.bind }
|
45
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
binds = ActiveRecord::Associations::AssociationScope.get_bind_values(owner, reflection.chain)
|
50
|
+
sc.execute(binds, klass, conn).first
|
51
|
+
rescue ::RangeError
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def replace(record)
|
56
|
+
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_new_record(record)
|
60
|
+
replace(record)
|
61
|
+
end
|
62
|
+
|
63
|
+
def _create_record(attributes, raise_error = false)
|
64
|
+
record = build_record(attributes)
|
65
|
+
yield(record) if block_given?
|
66
|
+
saved = record.save
|
67
|
+
set_new_record(record)
|
68
|
+
raise ActiveRecord::RecordInvalid.new(record) if !saved && raise_error
|
69
|
+
record
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -139,7 +139,7 @@ module DuckRecord
|
|
139
139
|
end
|
140
140
|
|
141
141
|
included do
|
142
|
-
Associations::Builder::
|
142
|
+
Associations::Builder::Association.extensions << AssociationBuilderExtension
|
143
143
|
mattr_accessor :index_nested_attribute_errors, instance_writer: false
|
144
144
|
self.index_nested_attribute_errors = false
|
145
145
|
end
|
@@ -12,18 +12,24 @@ module DuckRecord
|
|
12
12
|
self._reflections = {}
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.create(macro, name, options, ar)
|
15
|
+
def self.create(macro, name, scope, options, ar)
|
16
16
|
klass = \
|
17
17
|
case macro
|
18
18
|
when :embeds_many
|
19
19
|
EmbedsManyReflection
|
20
20
|
when :embeds_one
|
21
21
|
EmbedsOneReflection
|
22
|
+
when :belongs_to
|
23
|
+
BelongsToReflection
|
24
|
+
when :has_many
|
25
|
+
HasManyReflection
|
26
|
+
when :has_one
|
27
|
+
HasOneReflection
|
22
28
|
else
|
23
29
|
raise "Unsupported Macro: #{macro}"
|
24
30
|
end
|
25
31
|
|
26
|
-
klass.new(name, options, ar)
|
32
|
+
klass.new(name, scope, options, ar)
|
27
33
|
end
|
28
34
|
|
29
35
|
def self.add_reflection(ar, name, reflection)
|
@@ -110,7 +116,7 @@ module DuckRecord
|
|
110
116
|
# ThroughReflection
|
111
117
|
# PolymorphicReflection
|
112
118
|
# RuntimeReflection
|
113
|
-
class AbstractReflection
|
119
|
+
class AbstractReflection
|
114
120
|
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
115
121
|
# be passed to the class's constructor.
|
116
122
|
def build_association(attributes, &block)
|
@@ -143,6 +149,8 @@ module DuckRecord
|
|
143
149
|
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
144
150
|
attr_reader :name
|
145
151
|
|
152
|
+
attr_reader :scope
|
153
|
+
|
146
154
|
# Returns the hash of options used for the macro.
|
147
155
|
#
|
148
156
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
@@ -153,8 +161,9 @@ module DuckRecord
|
|
153
161
|
|
154
162
|
attr_reader :plural_name # :nodoc:
|
155
163
|
|
156
|
-
def initialize(name, options, duck_record)
|
164
|
+
def initialize(name, scope, options, duck_record)
|
157
165
|
@name = name
|
166
|
+
@scope = scope
|
158
167
|
@options = options
|
159
168
|
@duck_record = duck_record
|
160
169
|
@klass = options[:anonymous_class]
|
@@ -173,6 +182,16 @@ module DuckRecord
|
|
173
182
|
name.constantize
|
174
183
|
end
|
175
184
|
|
185
|
+
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
186
|
+
# and +other_aggregation+ has an options hash assigned to it.
|
187
|
+
def ==(other_aggregation)
|
188
|
+
super ||
|
189
|
+
other_aggregation.kind_of?(self.class) &&
|
190
|
+
name == other_aggregation.name &&
|
191
|
+
!other_aggregation.options.nil? &&
|
192
|
+
active_record == other_aggregation.active_record
|
193
|
+
end
|
194
|
+
|
176
195
|
private
|
177
196
|
|
178
197
|
def derive_class_name
|
@@ -182,7 +201,7 @@ module DuckRecord
|
|
182
201
|
|
183
202
|
# Holds all the metadata about an association as it was specified in the
|
184
203
|
# Active Record class.
|
185
|
-
class EmbedsAssociationReflection < MacroReflection
|
204
|
+
class EmbedsAssociationReflection < MacroReflection
|
186
205
|
# Returns the target association's class.
|
187
206
|
#
|
188
207
|
# class Author < ActiveRecord::Base
|
@@ -205,7 +224,7 @@ module DuckRecord
|
|
205
224
|
|
206
225
|
attr_accessor :parent_reflection # Reflection
|
207
226
|
|
208
|
-
def initialize(name, options, duck_record)
|
227
|
+
def initialize(name, scope, options, duck_record)
|
209
228
|
super
|
210
229
|
@constructable = calculate_constructable(macro, options)
|
211
230
|
|
@@ -286,7 +305,7 @@ module DuckRecord
|
|
286
305
|
end
|
287
306
|
end
|
288
307
|
|
289
|
-
class EmbedsManyReflection < EmbedsAssociationReflection
|
308
|
+
class EmbedsManyReflection < EmbedsAssociationReflection
|
290
309
|
def macro; :embeds_many; end
|
291
310
|
|
292
311
|
def collection?; true; end
|
@@ -296,7 +315,7 @@ module DuckRecord
|
|
296
315
|
end
|
297
316
|
end
|
298
317
|
|
299
|
-
class EmbedsOneReflection < EmbedsAssociationReflection
|
318
|
+
class EmbedsOneReflection < EmbedsAssociationReflection
|
300
319
|
def macro; :embeds_one; end
|
301
320
|
|
302
321
|
def has_one?; true; end
|
@@ -305,5 +324,323 @@ module DuckRecord
|
|
305
324
|
Associations::EmbedsOneAssociation
|
306
325
|
end
|
307
326
|
end
|
327
|
+
|
328
|
+
# Holds all the metadata about an association as it was specified in the
|
329
|
+
# Active Record class.
|
330
|
+
class AssociationReflection < MacroReflection
|
331
|
+
alias active_record duck_record
|
332
|
+
|
333
|
+
def quoted_table_name
|
334
|
+
klass.quoted_table_name
|
335
|
+
end
|
336
|
+
|
337
|
+
def primary_key_type
|
338
|
+
klass.type_for_attribute(klass.primary_key)
|
339
|
+
end
|
340
|
+
|
341
|
+
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
|
342
|
+
|
343
|
+
def join_keys
|
344
|
+
get_join_keys klass
|
345
|
+
end
|
346
|
+
|
347
|
+
# Returns a list of scopes that should be applied for this Reflection
|
348
|
+
# object when querying the database.
|
349
|
+
def scopes
|
350
|
+
scope ? [scope] : []
|
351
|
+
end
|
352
|
+
|
353
|
+
def scope_chain
|
354
|
+
chain.map(&:scopes)
|
355
|
+
end
|
356
|
+
deprecate :scope_chain
|
357
|
+
|
358
|
+
def join_scopes(table, predicate_builder) # :nodoc:
|
359
|
+
if scope
|
360
|
+
[ActiveRecord::Relation.create(klass, table, predicate_builder)
|
361
|
+
.instance_exec(&scope)]
|
362
|
+
else
|
363
|
+
[]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def klass_join_scope(table, predicate_builder) # :nodoc:
|
368
|
+
relation = ActiveRecord::Relation.create(klass, table, predicate_builder)
|
369
|
+
klass.scope_for_association(relation)
|
370
|
+
end
|
371
|
+
|
372
|
+
def constraints
|
373
|
+
chain.map(&:scopes).flatten
|
374
|
+
end
|
375
|
+
|
376
|
+
def alias_candidate(name)
|
377
|
+
"#{plural_name}_#{name}"
|
378
|
+
end
|
379
|
+
|
380
|
+
def chain
|
381
|
+
collect_join_chain
|
382
|
+
end
|
383
|
+
|
384
|
+
def get_join_keys(association_klass)
|
385
|
+
JoinKeys.new(join_pk(association_klass), join_fk)
|
386
|
+
end
|
387
|
+
|
388
|
+
# Returns the target association's class.
|
389
|
+
#
|
390
|
+
# class Author < ActiveRecord::Base
|
391
|
+
# has_many :books
|
392
|
+
# end
|
393
|
+
#
|
394
|
+
# Author.reflect_on_association(:books).klass
|
395
|
+
# # => Book
|
396
|
+
#
|
397
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
398
|
+
# a new association object. Use +build_association+ or +create_association+
|
399
|
+
# instead. This allows plugins to hook into association object creation.
|
400
|
+
def klass
|
401
|
+
@klass ||= compute_class(class_name)
|
402
|
+
end
|
403
|
+
|
404
|
+
def compute_class(name)
|
405
|
+
active_record.send(:compute_type, name)
|
406
|
+
end
|
407
|
+
|
408
|
+
def table_name
|
409
|
+
klass.table_name
|
410
|
+
end
|
411
|
+
|
412
|
+
attr_reader :type, :foreign_type
|
413
|
+
attr_accessor :parent_reflection # Reflection
|
414
|
+
|
415
|
+
def initialize(name, scope, options, active_record)
|
416
|
+
super
|
417
|
+
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
|
418
|
+
@foreign_type = options[:foreign_type] || "#{name}_type"
|
419
|
+
@constructable = calculate_constructable(macro, options)
|
420
|
+
@association_scope_cache = {}
|
421
|
+
@scope_lock = Mutex.new
|
422
|
+
|
423
|
+
if options[:class_name] && options[:class_name].class == Class
|
424
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
425
|
+
Passing a class to the `class_name` is deprecated and will raise
|
426
|
+
an ArgumentError in Rails 5.2. It eagerloads more classes than
|
427
|
+
necessary and potentially creates circular dependencies.
|
428
|
+
|
429
|
+
Please pass the class name as a string:
|
430
|
+
`#{macro} :#{name}, class_name: '#{options[:class_name]}'`
|
431
|
+
MSG
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def association_scope_cache(conn, owner)
|
436
|
+
key = conn.prepared_statements
|
437
|
+
@association_scope_cache[key] ||= @scope_lock.synchronize {
|
438
|
+
@association_scope_cache[key] ||= yield
|
439
|
+
}
|
440
|
+
end
|
441
|
+
|
442
|
+
def constructable? # :nodoc:
|
443
|
+
@constructable
|
444
|
+
end
|
445
|
+
|
446
|
+
def join_table
|
447
|
+
@join_table ||= options[:join_table] || derive_join_table
|
448
|
+
end
|
449
|
+
|
450
|
+
def foreign_key
|
451
|
+
@foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
|
452
|
+
end
|
453
|
+
|
454
|
+
def association_foreign_key
|
455
|
+
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
|
456
|
+
end
|
457
|
+
|
458
|
+
# klass option is necessary to support loading polymorphic associations
|
459
|
+
def association_primary_key(klass = nil)
|
460
|
+
options[:primary_key] || primary_key(klass || self.klass)
|
461
|
+
end
|
462
|
+
|
463
|
+
def association_primary_key_type
|
464
|
+
klass.type_for_attribute(association_primary_key.to_s)
|
465
|
+
end
|
466
|
+
|
467
|
+
def active_record_primary_key
|
468
|
+
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
|
469
|
+
end
|
470
|
+
|
471
|
+
def check_validity!
|
472
|
+
unless klass < ActiveRecord::Base
|
473
|
+
raise ArgumentError, "#{klass} must be inherited from ActiveRecord::Base."
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def check_preloadable!
|
478
|
+
return unless scope
|
479
|
+
|
480
|
+
if scope.arity > 0
|
481
|
+
raise ArgumentError, <<-MSG.squish
|
482
|
+
The association scope '#{name}' is instance dependent (the scope
|
483
|
+
block takes an argument). Preloading instance dependent scopes is
|
484
|
+
not supported.
|
485
|
+
MSG
|
486
|
+
end
|
487
|
+
end
|
488
|
+
alias :check_eager_loadable! :check_preloadable!
|
489
|
+
|
490
|
+
def join_id_for(owner) # :nodoc:
|
491
|
+
owner[active_record_primary_key]
|
492
|
+
end
|
493
|
+
|
494
|
+
def source_reflection
|
495
|
+
self
|
496
|
+
end
|
497
|
+
|
498
|
+
# A chain of reflections from this one back to the owner. For more see the explanation in
|
499
|
+
# ThroughReflection.
|
500
|
+
def collect_join_chain
|
501
|
+
[self]
|
502
|
+
end
|
503
|
+
|
504
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
505
|
+
# SQL queries on associations.
|
506
|
+
def clear_association_scope_cache # :nodoc:
|
507
|
+
@association_scope_cache.clear
|
508
|
+
end
|
509
|
+
|
510
|
+
def nested?
|
511
|
+
false
|
512
|
+
end
|
513
|
+
|
514
|
+
def has_scope?
|
515
|
+
scope
|
516
|
+
end
|
517
|
+
|
518
|
+
# Returns the macro type.
|
519
|
+
#
|
520
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
521
|
+
def macro; raise NotImplementedError; end
|
522
|
+
|
523
|
+
# Returns whether or not this association reflection is for a collection
|
524
|
+
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
525
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
526
|
+
def collection?
|
527
|
+
false
|
528
|
+
end
|
529
|
+
|
530
|
+
# Returns whether or not the association should be validated as part of
|
531
|
+
# the parent's validation.
|
532
|
+
#
|
533
|
+
# Unless you explicitly disable validation with
|
534
|
+
# <tt>validate: false</tt>, validation will take place when:
|
535
|
+
#
|
536
|
+
# * you explicitly enable validation; <tt>validate: true</tt>
|
537
|
+
# * you use autosave; <tt>autosave: true</tt>
|
538
|
+
# * the association is a +has_many+ association
|
539
|
+
def validate?
|
540
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
|
541
|
+
end
|
542
|
+
|
543
|
+
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
544
|
+
def belongs_to?; false; end
|
545
|
+
|
546
|
+
# Returns +true+ if +self+ is a +has_one+ reflection.
|
547
|
+
def has_one?; false; end
|
548
|
+
|
549
|
+
def association_class; raise NotImplementedError; end
|
550
|
+
|
551
|
+
def add_as_source(seed)
|
552
|
+
seed
|
553
|
+
end
|
554
|
+
|
555
|
+
def extensions
|
556
|
+
Array(options[:extend])
|
557
|
+
end
|
558
|
+
|
559
|
+
protected
|
560
|
+
|
561
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
562
|
+
self
|
563
|
+
end
|
564
|
+
|
565
|
+
private
|
566
|
+
|
567
|
+
def join_pk(_)
|
568
|
+
foreign_key
|
569
|
+
end
|
570
|
+
|
571
|
+
def join_fk
|
572
|
+
active_record_primary_key
|
573
|
+
end
|
574
|
+
|
575
|
+
def calculate_constructable(_macro, _options)
|
576
|
+
false
|
577
|
+
end
|
578
|
+
|
579
|
+
def derive_class_name
|
580
|
+
class_name = name.to_s
|
581
|
+
class_name = class_name.singularize if collection?
|
582
|
+
class_name.camelize
|
583
|
+
end
|
584
|
+
|
585
|
+
def derive_foreign_key
|
586
|
+
if options[:as]
|
587
|
+
"#{options[:as]}_id"
|
588
|
+
else
|
589
|
+
"#{name}_id"
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
def primary_key(klass)
|
594
|
+
klass.primary_key || raise(ActiveRecord::UnknownPrimaryKey.new(klass))
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
class BelongsToReflection < AssociationReflection # :nodoc:
|
599
|
+
def macro; :belongs_to; end
|
600
|
+
|
601
|
+
def belongs_to?; true; end
|
602
|
+
|
603
|
+
def association_class
|
604
|
+
Associations::BelongsToAssociation
|
605
|
+
end
|
606
|
+
|
607
|
+
def join_id_for(owner) # :nodoc:
|
608
|
+
owner[foreign_key]
|
609
|
+
end
|
610
|
+
|
611
|
+
private
|
612
|
+
|
613
|
+
def join_fk
|
614
|
+
foreign_key
|
615
|
+
end
|
616
|
+
|
617
|
+
def join_pk(_klass)
|
618
|
+
association_primary_key
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
class HasManyReflection < AssociationReflection # :nodoc:
|
623
|
+
def macro; :has_many; end
|
624
|
+
|
625
|
+
def collection?; true; end
|
626
|
+
|
627
|
+
def association_class
|
628
|
+
Associations::HasManyAssociation
|
629
|
+
end
|
630
|
+
|
631
|
+
def association_primary_key(klass = nil)
|
632
|
+
primary_key(klass || self.klass)
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
class HasOneReflection < AssociationReflection # :nodoc:
|
637
|
+
def macro; :has_one; end
|
638
|
+
|
639
|
+
def has_one?; true; end
|
640
|
+
|
641
|
+
def association_class
|
642
|
+
Associations::HasOneAssociation
|
643
|
+
end
|
644
|
+
end
|
308
645
|
end
|
309
646
|
end
|