Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

Commit cdf336a

Browse files
committed
initial STI spec passes only
1 parent d2ce6cc commit cdf336a

21 files changed

+392
-253
lines changed

.rubocop.yml

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
Metrics/LineLength:
23
Max: 100
34

@@ -9,3 +10,6 @@ Style/MutableConstant:
910

1011
Style/CommandLiteral:
1112
EnforcedStyle: mixed
13+
14+
AllCops:
15+
TargetRubyVersion: 2.4.1

lib/hyper-mesh.rb

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'date'
99
require 'kernel/itself' unless Object.instance_methods.include?(:itself)
1010
require 'object/tap'
11+
require "reactive_record/active_record_error"
1112
require "reactive_record/active_record/errors"
1213
require "reactive_record/active_record/error"
1314
require "reactive_record/server_data_cache"

lib/reactive_record/active_record/class_methods.rb

+54-21
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ module ActiveRecord
22

33
module ClassMethods
44

5-
def inherited(subclass)
6-
['_attribute_aliases'].each do |inheritable_attribute|
7-
instance_var = "@#{inheritable_attribute}"
8-
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
9-
end
5+
alias _new_without_sti_type_cast new
6+
7+
def new(*args, &block)
8+
_new_without_sti_type_cast(*args, &block).cast_to_current_sti_type
109
end
1110

1211
def base_class
@@ -16,10 +15,8 @@ def base_class
1615

1716
if superclass == Base || superclass.abstract_class?
1817
self
19-
elsif respond_to?(:each)
20-
superclass.base_class
2118
else
22-
self
19+
superclass.base_class
2320
end
2421
end
2522

@@ -28,19 +25,26 @@ def abstract_class?
2825
end
2926

3027
def primary_key
31-
base_class.instance_eval { @primary_key_value || :id }
28+
@primary_key_value ||= (self == base_class) ? :id : base_class.primary_key
3229
end
3330

3431
def primary_key=(val)
35-
base_class.instance_eval { @primary_key_value = val }
32+
@primary_key_value = val.to_s
3633
end
3734

3835
def inheritance_column
39-
base_class.instance_eval { @inheritance_column_value || "type" }
36+
return nil if @no_inheritance_column
37+
@inheritance_column_value ||=
38+
if self == base_class
39+
@inheritance_column_value || 'type'
40+
else
41+
superclass.inheritance_column.tap { |v| @no_inheritance_column = !v }
42+
end
4043
end
4144

4245
def inheritance_column=(name)
43-
base_class.instance_eval { @inheritance_column_value = name }
46+
@no_inheritance_column = !name
47+
@inheritance_column_value = name
4448
end
4549

4650
def model_name
@@ -49,15 +53,11 @@ def model_name
4953
end
5054

5155
def find(id)
52-
klass = self
53-
base_class.instance_eval { ReactiveRecord::Base.find(klass, primary_key, id) }
56+
ReactiveRecord::Base.find(self, primary_key, id)
5457
end
5558

5659
def find_by(opts = {})
57-
klass = self
58-
base_class.instance_eval do
59-
ReactiveRecord::Base.find(klass, _dealias_attribute(opts.first.first), opts.first.last)
60-
end
60+
ReactiveRecord::Base.find(self, _dealias_attribute(opts.first.first), opts.first.last)
6161
end
6262

6363
def enum(*args)
@@ -69,7 +69,11 @@ def serialize(attr, *args)
6969
end
7070

7171
def _dealias_attribute(new)
72-
_attribute_aliases[new] || new
72+
if self == base_class
73+
_attribute_aliases[new] || new
74+
else
75+
_attribute_aliases[new] ||= superclass._dealias_attribute(new)
76+
end
7377
end
7478

7579
def _attribute_aliases
@@ -80,7 +84,7 @@ def alias_attribute(new_name, old_name)
8084
['', '=', '_changed?'].each do |variant|
8185
define_method("#{new_name}#{variant}") { |*args, &block| send("#{old_name}#{variant}", *args, &block) }
8286
end
83-
_attribute_aliases[new_name] = old_name
87+
@_attribute_aliases[new_name] = old_name
8488
end
8589

8690
# ignore any of these methods if they get called on the client. This list should be trimmed down to include only
@@ -222,15 +226,43 @@ def default_scope(*args, &block)
222226
def all
223227
ReactiveRecord::Base.default_scope[self] ||=
224228
begin
229+
puts "defining all for #{self}"
225230
root = ReactiveRecord::Collection
226231
.new(self, nil, nil, self, 'all')
227232
.extend(ReactiveRecord::UnscopedCollection)
228-
(@_default_scopes || [{ client: -> () { true } }]).inject(root) do |scope, opts|
233+
(@_default_scopes || [{ client: _all_filter }]).inject(root) do |scope, opts|
229234
scope.build_child_scope(ReactiveRecord::ScopeDescription.new(self, :all, opts))
230235
end
231236
end
232237
end
233238

239+
def _all_filter
240+
# provides a filter for the all scopes taking into account STI subclasses
241+
# note: within the lambda `self` will be the model instance
242+
defining_class_is_base_class = base_class == self
243+
defining_model_name = model_name
244+
lambda do
245+
# have to delay computation of inheritance column since it might
246+
# not be defined when class is first defined
247+
ic = self.class.inheritance_column
248+
(defining_class_is_base_class || !ic || self[ic] == defining_model_name).tap do |x|
249+
puts "_all_filter(#{self}) returns: #{x} defining_model_name: #{defining_model_name}, is base: #{defining_class_is_base_class}, ic: #{ic}, self[ic]: #{self[ic]}"
250+
end
251+
end
252+
#
253+
# if base_class == self || !inheritance_column
254+
# puts "creating a base filter: #{model_name} #{base_class} #{self} #{inheritance_column}"
255+
# # always return true for the base_class (or if there is no inheritance column)
256+
# -> () { puts "#{model_name} #{self.class.base_class} #{self.class} #{self.class.inheritance_column} base all filter"; true }
257+
# else
258+
# # otherwise we are in a STI subclass so inheritance column name must match model name
259+
# # optimize by capturing the inheritance column and model_name as local vars
260+
# klass_inheritance_column = inheritance_column
261+
# klass_model_name = model_name
262+
# -> () { puts "subclass all filter #{klass_inheritance_column} #{klass_model_name} #{self[klass_inheritance_column] == klass_model_name}"; self[klass_inheritance_column] == klass_model_name }
263+
# end
264+
end
265+
234266
# def all=(_collection)
235267
# raise "NO LONGER IMPLEMENTED DOESNT PLAY WELL WITH SYNCHROMESH"
236268
# end
@@ -333,6 +365,7 @@ def define_attribute_methods
333365
define_method("#{name}_changed?") { @backing_record.changed?(name) }
334366
define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? }
335367
end
368+
self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column)
336369
end
337370

338371
def _react_param_conversion(param, opt = nil)

lib/reactive_record/active_record/instance_methods.rb

+20-5
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@ def initialize(hash = {})
1919
# standard active_record new -> creates a new instance, primary key is ignored if present
2020
# we have to build the backing record first then initialize it so associations work correctly
2121
@backing_record = ReactiveRecord::Base.new(self.class, {}, self)
22+
if self.class.inheritance_column && !hash.key?(self.class.inheritance_column)
23+
hash[self.class.inheritance_column] = self.class.name
24+
end
2225
@backing_record.instance_eval do
23-
h = Hash.new
26+
h = {}
2427
hash.each do |a, v|
2528
a = model._dealias_attribute(a)
2629
h[a] = convert(a, v).itself
2730
end
2831
self.class.load_data do
2932
h.each do |attribute, value|
30-
unless attribute == primary_key
31-
@ar_instance[attribute] = value
32-
changed_attributes << attribute
33-
end
33+
next if attribute == primary_key
34+
@ar_instance[attribute] = value
35+
changed_attributes << attribute
3436
end
3537
end
3638
end
@@ -160,5 +162,18 @@ def update(attrs = {}, &block)
160162
def <=>(other)
161163
id.to_i <=> other.id.to_i
162164
end
165+
166+
def becomes(klass)
167+
klass._new_without_sti_type_cast(backing_record)
168+
end
169+
170+
def becomes!(klass)
171+
self[self.class.inheritance_column] = klass.name
172+
becomes(klass)
173+
end
174+
175+
def cast_to_current_sti_type
176+
@backing_record.set_ar_instance!
177+
end
163178
end
164179
end

lib/reactive_record/active_record/reactive_record/base.rb

+10-4
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def self.find(model, attribute, value)
9393
record.sync_attribute(model.primary_key, id) if id
9494
end
9595
# finally initialize and return the ar_instance
96-
record.ar_instance ||= infer_type_from_hash(model, record.attributes).new(record)
96+
record.set_ar_instance!
9797
end
9898

9999
def self.new_from_vector(model, aggregate_owner, *vector)
@@ -111,7 +111,7 @@ def self.new_from_vector(model, aggregate_owner, *vector)
111111
set_vector_lookup(record, vector)
112112
end
113113

114-
record.ar_instance ||= infer_type_from_hash(model, record.attributes).new(record)
114+
record.set_ar_instance!
115115

116116
if aggregate_owner
117117
record.aggregate_owner = aggregate_owner
@@ -292,6 +292,12 @@ def new?
292292
!id && !vector
293293
end
294294

295+
def set_ar_instance!
296+
klass = self.class.infer_type_from_hash(model, @attributes)
297+
@ar_instance = klass._new_without_sti_type_cast(self) unless @ar_instance.class == klass
298+
@ar_instance
299+
end
300+
295301
class << self
296302
def infer_type_from_hash(klass, hash)
297303
klass = klass.base_class
@@ -300,9 +306,9 @@ def infer_type_from_hash(klass, hash)
300306
begin
301307
return Object.const_get(type)
302308
rescue Exception => e
303-
message = "Could not subclass #{@model_klass.model_name} as #{type}. Perhaps #{type} class has not been required. Exception: #{e}"
309+
message = "Could not subclass #{klass} as #{type}. Perhaps #{type} class has not been required. Exception: #{e}"
304310
`console.error(#{message})`
305-
end if type
311+
end unless !type || type == ''
306312
klass
307313
end
308314

lib/reactive_record/active_record/reactive_record/collection.rb

+18-4
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ def apply_to_all_collections(method, record, dont_gather)
128128
unless dont_gather
129129
related_records = collection.gather_related_records(record)
130130
end
131+
puts "about to do #{collection}.#{method}(#{related_records.count}, record) (collection.send)"
131132
collection.send method, related_records, record
133+
puts "done"
132134
end
133135
end
134136
end
@@ -142,9 +144,12 @@ def gather_related_records(record, related_records = Set.new)
142144
end
143145

144146
def merge_related_records(record, related_records)
147+
puts "#{self}.merge_related_records(#{record}, #{related_records.count})"
145148
if filter? && joins_with?(record)
149+
puts "merging #{self}"
146150
related_records.merge(related_records_for(record))
147151
end
152+
puts "merge_related_records returns #{related_records.count}"
148153
related_records
149154
end
150155

@@ -155,14 +160,22 @@ def filter?
155160
# is it necessary to check @association in the next 2 methods???
156161

157162
def joins_with?(record)
158-
if @association && @association.through_association
163+
klass = record.class
164+
if @association&.through_association
159165
@association.through_association.klass == record.class
160-
else
161-
@target_klass == record.class
162-
end
166+
elsif @target_klass == klass
167+
true
168+
elsif !klass.inheritance_column
169+
false
170+
elsif klass.base_class == @target_class
171+
klass < @target_klass
172+
elsif klass.base_class == klass
173+
@target_klass < klass
174+
end.tap { |x| puts "#{self}.joins_with?(#{record}) returns #{x}"}
163175
end
164176

165177
def related_records_for(record)
178+
puts "#{self}.related_records_for(#{record}), @association = #{@association}"
166179
return [] unless @association
167180
attrs = record.attributes
168181
return [] unless attrs[@association.inverse_of] == @owner
@@ -195,6 +208,7 @@ def set_pre_sync_related_records(related_records, _record = nil)
195208
live_scopes.each { |scope| scope.set_pre_sync_related_records(@pre_sync_related_records) }
196209
end
197210

211+
# NOTE sync_scopes is overridden in scope_description.rb
198212
def sync_scopes(related_records, record, filtering = true)
199213
#related_records = related_records.intersection([*@collection])
200214
#related_records = in_this_collection related_records

lib/reactive_record/active_record/reactive_record/getters.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def get_belongs_to(assoc, reload = nil)
88
value = Base.fetch_from_db([@model, [:find, id], attr, @model.primary_key]) if id.present?
99
value = find_association(assoc, value)
1010
sync_ignore_dummy attr, value, has_key
11-
end
11+
end&.cast_to_current_sti_type
1212
end
1313

1414
def get_has_many(assoc, reload = nil)

0 commit comments

Comments
 (0)