From 9245aeb27e66d5dc10554af43e0b0c87f97d2604 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Sat, 2 Dec 2023 09:58:20 -0500 Subject: [PATCH 1/3] refactor: remove unused and incorrect code `belongs_to :through` is not a thing, and this code should not have been included in e6a47bcc --- lib/associations/associations.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/associations/associations.rb b/lib/associations/associations.rb index 99e2f046..1e14f95a 100644 --- a/lib/associations/associations.rb +++ b/lib/associations/associations.rb @@ -55,9 +55,6 @@ def belongs_to_active_hash(association_id, options = {}) if ActiveRecord::Reflection.respond_to?(:create) if defined?(ActiveHash::Reflection::BelongsToReflection) reflection = ActiveHash::Reflection::BelongsToReflection.new(association_id.to_sym, nil, options, self) - if options[:through] - reflection = ActiveRecord::ThroughReflection.new(reflection) - end else reflection = ActiveRecord::Reflection.create( :belongs_to, From f1b792d0dadacaa91b159cf176c35c8ec3f29e04 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Sat, 2 Dec 2023 10:32:20 -0500 Subject: [PATCH 2/3] refactor: dynamically create test classes this will allow me to add new classes in the next commit, but should also speed things up slightly. --- .../active_record_extensions_spec.rb | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/spec/associations/active_record_extensions_spec.rb b/spec/associations/active_record_extensions_spec.rb index 1d03d437..f9070894 100644 --- a/spec/associations/active_record_extensions_spec.rb +++ b/spec/associations/active_record_extensions_spec.rb @@ -3,8 +3,36 @@ unless SKIP_ACTIVE_RECORD describe ActiveHash::Base, "active record extensions" do - before do - class Country < ActiveRecord::Base + def define_ephemeral_class(name, superclass, &block) + klass = Class.new(superclass) + Object.const_set(name, klass) + klass.class_eval(&block) if block_given? + @ephemeral_classes << name + end + + def define_book_classes + define_ephemeral_class(:Author, ActiveHash::Base) do + include ActiveHash::Associations + end + + define_ephemeral_class(:Book, ActiveRecord::Base) do + establish_connection :adapter => "sqlite3", :database => ":memory:" + connection.create_table(:books, :force => true) do |t| + t.integer :author_id + t.integer :author_code + t.boolean :published + end + + if Object.const_defined?(:ActiveModel) + scope(:published, proc { where(:published => true) }) + else + named_scope :published, {:conditions => {:published => true}} + end + end + end + + def define_school_classes + define_ephemeral_class(:Country, ActiveRecord::Base) do establish_connection :adapter => "sqlite3", :database => ":memory:" connection.create_table(:countries, :force => true) do |t| t.string :name @@ -12,7 +40,7 @@ class Country < ActiveRecord::Base extend ActiveHash::Associations::ActiveRecordExtensions end - class School < ActiveRecord::Base + define_ephemeral_class(:School, ActiveRecord::Base) do establish_connection :adapter => "sqlite3", :database => ":memory:" connection.create_table(:schools, :force => true) do |t| t.integer :country_id @@ -23,45 +51,27 @@ class School < ActiveRecord::Base extend ActiveHash::Associations::ActiveRecordExtensions end - class City < ActiveHash::Base - include ActiveHash::Associations - end - - class Author < ActiveHash::Base + define_ephemeral_class(:City, ActiveHash::Base) do include ActiveHash::Associations end - class SchoolStatus < ActiveHash::Base - end - - class Book < ActiveRecord::Base - establish_connection :adapter => "sqlite3", :database => ":memory:" - connection.create_table(:books, :force => true) do |t| - t.integer :author_id - t.integer :author_code - t.boolean :published - end + define_ephemeral_class(:SchoolStatus, ActiveHash::Base) + end - if Object.const_defined?(:ActiveModel) - scope(:published, proc { where(:published => true) }) - else - named_scope :published, {:conditions => {:published => true}} - end - end + before do + @ephemeral_classes = [] end after do - Object.send :remove_const, :City - Object.send :remove_const, :Author - Object.send :remove_const, :Country - Object.send :remove_const, :School - Object.send :remove_const, :SchoolStatus - Object.send :remove_const, :Book + @ephemeral_classes.each do |klass_name| + Object.send :remove_const, klass_name + end end describe "#has_many" do - context "with ActiveRecord children" do + before { define_book_classes } + context "with default options" do before do @book_1 = Book.create! :author_id => 1, :published => true @@ -142,11 +152,12 @@ class Book < ActiveRecord::Base author.books.to_a end end - end describe ActiveHash::Associations::ActiveRecordExtensions do describe "#belongs_to" do + before { define_school_classes } + it "doesn't interfere with AR's procs in belongs_to methods" do School.belongs_to :country, lambda { where(name: 'Japan') } school = School.new @@ -210,6 +221,8 @@ class Book < ActiveRecord::Base end describe "#belongs_to_active_hash" do + before { define_school_classes } + context "setting by id" do it "finds the correct records" do School.belongs_to_active_hash :city @@ -282,8 +295,9 @@ class Book < ActiveRecord::Base end describe "#belongs_to" do - context "with an ActiveRecord parent" do + before { define_school_classes } + it "find the correct records" do City.belongs_to :country country = Country.create @@ -297,12 +311,12 @@ class Book < ActiveRecord::Base expect(city.country).to be_nil end end - end describe "#has_one" do context "with ActiveRecord children" do before do + define_book_classes Author.has_one :book end From 1ffe44a2b6ce6202537c896353c37efa27d1ace6 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Sat, 2 Dec 2023 11:46:13 -0500 Subject: [PATCH 3/3] fix: support has_many :through pointing at ActiveHash Closes #239 --- lib/associations/associations.rb | 29 +++++++++- .../active_record_extensions_spec.rb | 58 ++++++++++++++++++- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/associations/associations.rb b/lib/associations/associations.rb index 1e14f95a..47b05386 100644 --- a/lib/associations/associations.rb +++ b/lib/associations/associations.rb @@ -2,11 +2,35 @@ module ActiveHash module Associations module ActiveRecordExtensions - def self.extended(base) require_relative 'reflection_extensions' end + def has_many(association_id, **options) + if options[:through] + klass_name = association_id.to_s.classify + klass = + begin + klass_name.constantize + rescue StandardError, LoadError + nil + end + + 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(association_id.to_s.singularize) + end.uniq + 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 = @@ -117,6 +141,7 @@ def has_many(association_id, options = {}) klass.where(foreign_key => primary_key_value) end end + define_method("#{association_id.to_s.underscore.singularize}_ids") do public_send(association_id).map(&:id) end @@ -140,7 +165,6 @@ def has_one(association_id, options = {}) end def belongs_to(association_id, options = {}) - options = { :class_name => association_id.to_s.classify, :foreign_key => association_id.to_s.foreign_key, @@ -156,7 +180,6 @@ def belongs_to(association_id, options = {}) define_method("#{association_id}=") do |new_value| attributes[options[:foreign_key].to_sym] = new_value ? new_value.send(options[:primary_key]) : nil end - end end diff --git a/spec/associations/active_record_extensions_spec.rb b/spec/associations/active_record_extensions_spec.rb index f9070894..1001df57 100644 --- a/spec/associations/active_record_extensions_spec.rb +++ b/spec/associations/active_record_extensions_spec.rb @@ -48,6 +48,7 @@ def define_school_classes t.integer :locateable_id t.integer :city_id end + extend ActiveHash::Associations::ActiveRecordExtensions end @@ -58,6 +59,45 @@ def define_school_classes define_ephemeral_class(:SchoolStatus, ActiveHash::Base) end + def define_doctor_classes + define_ephemeral_class(:Physician, ActiveHash::Base) do + include ActiveHash::Associations + + has_many :appointments + has_many :patients, through: :appointments + + 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 :physician + t.references :patient + end + + extend ActiveHash::Associations::ActiveRecordExtensions + + belongs_to :physician + 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 + + has_many :appointments + has_many :physicians, through: :appointments + end + + end + before do @ephemeral_classes = [] end @@ -152,6 +192,23 @@ def define_school_classes author.books.to_a end end + + describe ":through" do + before { define_doctor_classes } + + it "finds ActiveHash records through the join model" do + patient = Patient.create! + + physician1 = Physician.first + Appointment.create!(physician: physician1, patient: patient) + Appointment.create!(physician: physician1, patient: patient) + + physician2 = Physician.last + Appointment.create!(physician: physician2, patient: patient) + + expect(patient.physicians).to contain_exactly(physician1, physician2) + end + end end describe ActiveHash::Associations::ActiveRecordExtensions do @@ -332,6 +389,5 @@ def define_school_classes end end end - end end