active-hash / active_hash

A readonly ActiveRecord-esque base class that lets you use a hash, a Yaml file or a custom file as the datasource
MIT License
1.2k stars 179 forks source link

has_many ActiveRecord though ActiveHash generates invalid SQL #323

Open MoAI522 opened 6 hours ago

MoAI522 commented 6 hours ago

Describe the bug

I can't retrieve ActiveRecord records from ActiveHash object when the ActiveHash is related to the ActiveRecord through another ActiveHash.

To Reproduce

  1. Create books table below.
    
    # db/schema.rb

ActiveRecord::Schema[7.2].define(version: 2024_09_21_030534) do create_table "books", force: :cascade do |t| t.string "title" t.integer "author_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end

test/fixtures/books.yml

one: title: MyString author_id: 1

two: title: MyString author_id: 1


2. Execute `bin/rails db:fixtures:load`

3. Create models below.
```ruby
# app/models/book.rb

class Book < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :author
  delegate :country, to: :author
end
# app/models/author.rb

class Author < ActiveHash::Base
  include ActiveHash::Associations

  has_many :books
  belongs_to :country

  self.data = [
    { id: 1, name: 'Author A', country_id: 1 },
    { id: 2, name: 'Author B', country_id: 2 },
  ]
end
# app/models/country.rb

class Country < ActiveHash::Base
  include ActiveHash::Associations

  has_many :authors
  has_many :books, through: :authors

  self.data = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Japan' },
  ]
end
  1. Execute code below on rails console.
Country.find(1).books

Expected Behavior

Returns all Book records.(They are all have author_id=1, and the author relates to country 1.)

[#<Book:0x00007faef7873860
  id: 298486374,
  title: "MyString",
  author_id: 1,
  created_at: "2024-09-21 03:17:10.084083000 +0000",
  updated_at: "2024-09-21 03:17:10.084083000 +0000">,
 #<Book:0x00007faef7873720
  id: 980190962,
  title: "MyString",
  author_id: 1,
  created_at: "2024-09-21 03:17:10.084083000 +0000",
  updated_at: "2024-09-21 03:17:10.084083000 +0000">]

Actual Behavior

The output is below.

  Book Load (0.7ms)  SELECT "books".* FROM "books" WHERE "books"."country_id" = ? /* loading for pp */ LIMIT ?  [["country_id", 1], ["LIMIT", 11]]
An error occurred when inspecting the object: #<ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: books.country_id>
Result of Kernel#inspect: #<Book::ActiveRecord_Relation:0x00007fb3c5382ef0 @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime), @table=#<Arel::Table:0x00007fb3c53ff6a8 @name="books", @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime), @type_caster=#<ActiveRecord::TypeCaster::Map:0x00007fb3c572f2d8 @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime)>, @table_alias=nil>, @values={:where=>#<ActiveRecord::Relation::WhereClause:0x00007fb3c56d1cc8 @predicates=[#<Arel::Nodes::Equality:0x00007fb3c56d1d40 @left=#<struct Arel::Attributes::Attribute relation=#<Arel::Table:0x00007fb3c53ff6a8 @name="books", @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime), @type_caster=#<ActiveRecord::TypeCaster::Map:0x00007fb3c572f2d8 @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime)>, @table_alias=nil>, name="country_id">, @right=#<ActiveRecord::Relation::QueryAttribute:0x00007fb3c54bcb90 @name="country_id", @value_before_type_cast=1, @type=#<ActiveModel::Type::Value:0x00007fb3c5ec6938 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil, @value=1, @_unboundable=nil, @value_for_database=1>>]>}, @loaded=nil, @predicate_builder=#<ActiveRecord::PredicateBuilder:0x00007fb3c572f210 @table=#<ActiveRecord::TableMetadata:0x00007fb3c572f238 @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime), @arel_table=#<Arel::Table:0x00007fb3c53ff6a8 @name="books", @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime), @type_caster=#<ActiveRecord::TypeCaster::Map:0x00007fb3c572f2d8 @klass=Book(id: integer, title: string, author_id: integer, created_at: datetime, updated_at: datetime)>, @table_alias=nil>, @reflection=nil>, @handlers=[[Set, #<ActiveRecord::PredicateBuilder::ArrayHandler:0x00007fb3c572f080 @predicate_builder=#<ActiveRecord::PredicateBuilder:0x00007fb3c572f210 ...>>], [Array, #<ActiveRecord::PredicateBuilder::ArrayHandler:0x00007fb3c572f0d0 @predicate_builder=#<ActiveRecord::PredicateBuilder:0x00007fb3c572f210 ...>>], [ActiveRecord::Relation, #<ActiveRecord::PredicateBuilder::RelationHandler:0x00007fb3c572f120>], [Range, #<ActiveRecord::PredicateBuilder::RangeHandler:0x00007fb3c572f170 @predicate_builder=#<ActiveRecord::PredicateBuilder:0x00007fb3c572f210 ...>>], [BasicObject, #<ActiveRecord::PredicateBuilder::BasicObjectHandler:0x00007fb3c572f1c0 @predicate_builder=#<ActiveRecord::PredicateBuilder:0x00007fb3c572f210 ...>>]]>, @delegate_to_klass=false, @future_result=nil, @records=nil, @async=false, @none=false, @should_eager_load=nil, @arel=nil, @to_sql=nil, @take=nil, @offsets=nil, @cache_keys=nil, @cache_versions=nil>
=> 

My environment

OS: Ubuntsu 20.04.3(WSL) Ruby version: ruby 3.2.4 (2024-04-23 revision af471c0e01) [x86_64-linux] Rails version: 7.2.1 active-hash version: 3.3.1

The rails uses sqlite3 as database. sqlite3 version: 3.37.2 2022-01-06 13:25:41 872ba256cbf61d9290b571c0e6d82a20c224ca3ad82971edc46b29818d5dalt1

MoAI522 commented 6 hours ago

I think it occurs by line below: https://github.com/active-hash/active_hash/blob/0b3701c54cfcd195423885cd5df70e6959a3a2c0/lib/associations/associations.rb#L18

join_model is treated without identifying whether it is ActiveRecord or ActiveHash. But I couldn't find the way to fix it.