diff --git a/lib/associations/associations.rb b/lib/associations/associations.rb index 99e2f046..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 = @@ -55,9 +79,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, @@ -120,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 @@ -143,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, @@ -159,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 1d03d437..1001df57 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 @@ -20,48 +48,70 @@ class School < ActiveRecord::Base t.integer :locateable_id t.integer :city_id end + extend ActiveHash::Associations::ActiveRecordExtensions end - class City < ActiveHash::Base + define_ephemeral_class(:City, ActiveHash::Base) do include ActiveHash::Associations end - class Author < ActiveHash::Base + define_ephemeral_class(:SchoolStatus, ActiveHash::Base) + end + + def define_doctor_classes + define_ephemeral_class(:Physician, ActiveHash::Base) do include ActiveHash::Associations - end - class SchoolStatus < ActiveHash::Base + has_many :appointments + has_many :patients, through: :appointments + + self.data = [ + {:id => 1, :name => "ikeda"}, + {:id => 2, :name => "sato"} + ] end - class Book < ActiveRecord::Base + define_ephemeral_class(:Appointment, 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 + connection.create_table :appointments, force: true do |t| + t.references :physician + t.references :patient end - if Object.const_defined?(:ActiveModel) - scope(:published, proc { where(:published => true) }) - else - named_scope :published, {:conditions => {:published => true}} + 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 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 @@ -143,10 +193,28 @@ class Book < ActiveRecord::Base 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 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 +278,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 +352,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 +368,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 @@ -318,6 +389,5 @@ class Book < ActiveRecord::Base end end end - end end