Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 44 additions & 17 deletions lib/associations/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,63 @@ def self.extended(base)
end

def has_many(association_id, scope = nil, **options, &extension)
super

if options[:through]
source_association_name = options[:source]&.to_s || association_id.to_s.singularize

through_klass = reflect_on_association(options[:through])&.klass
klass = through_klass&.reflect_on_association(source_association_name)&.klass
if options[:source_type]
source_type = options[:source_type]
source_foreign_key = "#{source_association_name}_id"

if klass && klass < ActiveHash::Base
define_method(association_id) do
join_models = send(options[:through])
join_models.flat_map do |join_model|
join_model.send(source_association_name)
end.uniq
klass = source_type.safe_constantize
if klass < ActiveHash::Base
ids = send(options[:through]).map { |jm| jm.send(source_foreign_key) }.compact.uniq
ids.flat_map { |id| klass.find_by_id(id) }.compact
else
super()
end
end
else
define_method(association_id) do
through_klass = self.class.reflect_on_association(options[:through])&.klass
source_klass = through_klass&.reflect_on_association(source_association_name)&.class_name&.safe_constantize

if source_klass && source_klass < ActiveHash::Base
send(options[:through]).flat_map do |join_model|
join_model.send(source_association_name)
end.uniq
else
super()
end
end

return
end
end

super
end

def belongs_to(name, scope = nil, **options)
klass_name = options.key?(:class_name) ? options[:class_name] : name.to_s.camelize
klass = klass_name.safe_constantize
foreign_key = options[:foreign_key] || name.to_s.foreign_key

if klass && klass < ActiveHash::Base
options = { class_name: klass_name }.merge(options)
belongs_to_active_hash(name, options)
else
super
super

define_method(name) do
klass = klass_name.safe_constantize
if klass && klass < ActiveHash::Base
klass.send("find_by_#{klass.primary_key}", send(foreign_key))
else
super()
end
end

define_method("#{name}=") do |new_value|
klass = klass_name.safe_constantize
if klass && klass < ActiveHash::Base
send("#{foreign_key}=", new_value ? new_value.send(klass.primary_key) : nil)
else
super(new_value)
end
end
end

Expand Down
57 changes: 57 additions & 0 deletions spec/associations/active_record_extensions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,47 @@ def define_doctor_classes

end

# Physician(AH) <-- Appointment(AR) --> Patient(AR)
# polymorphic: providerable on Appointment, source_type targets ActiveHash model
def define_polymorphic_doctor_classes
define_ephemeral_class(:Physician, ActiveHash::Base) do
include ActiveHash::Associations

self.data = [
{:id => 1, :name => "ikeda"},
{:id => 2, :name => "sato"}
]
end

define_ephemeral_class(:Appointment, ActiveRecord::Base) do
establish_connection :adapter => "sqlite3", :database => ":memory:"
connection.create_table :appointments, force: true do |t|
t.references :providerable, polymorphic: true
t.references :patient
end

extend ActiveHash::Associations::ActiveRecordExtensions

# AR belongs_to (polymorphic)
belongs_to :providerable, polymorphic: true
# AR belongs_to
belongs_to :patient
end

define_ephemeral_class(:Patient, ActiveRecord::Base) do
establish_connection :adapter => "sqlite3", :database => ":memory:"
connection.create_table :patients, force: true do |t|
end

extend ActiveHash::Associations::ActiveRecordExtensions

# AR has_many
has_many :appointments
# AR has_many :through (source_type points to ActiveHash model)
has_many :physicians, through: :appointments, source: :providerable, source_type: "Physician"
end
end

before do
@ephemeral_classes = []
end
Expand Down Expand Up @@ -286,6 +327,22 @@ def define_doctor_classes
end
end

describe ":through with a polymorphic source and source_type" do
before { define_polymorphic_doctor_classes }

it "does not raise when defining the association" do
expect(Patient.instance_method(:physicians)).to be_a(UnboundMethod)
end

it "returns the correct ActiveHash records" do
physician = Physician.find(1)
patient = Patient.create!
Appointment.create!(providerable_type: "Physician", providerable_id: physician.id, patient_id: patient.id)

expect(patient.physicians).to contain_exactly(physician)
end
end

describe "with a lambda" do
before do
define_person_classes
Expand Down
Loading