Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different column types and enum issues #57

Open
antulik opened this issue Dec 22, 2020 · 5 comments
Open

Different column types and enum issues #57

antulik opened this issue Dec 22, 2020 · 5 comments

Comments

@antulik
Copy link

antulik commented Dec 22, 2020

each_instance incorrectly sets the column types, for example

class User < ActiveRecord::Base
  enum state: {
    active: 1,
  }
end

pp User.first
# #<User:0x00007f884e1bee40
#  id: 1,
#  created_at: Wed, 17 Apr 2019 14:33:34 AEST +10:00
#  state: "active">

pp User.each_instance.first
# #<User:0x00007f884e1bee40
#  id: 1,
#  created_at: 2019-04-16 22:24:59 UTC
#  state: 1>

Expected.
in the example above objects from both queries should be identical

rails -v => Rails 6.0.3.4
ruby -v => ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]

@antulik
Copy link
Author

antulik commented Dec 22, 2020

A workaround

User.each_row do |attributes|
  user = User.instantiate attributes
  # do stuff
end

Note: it only works for simple queries where returned columns map directly to model columns

@antulik
Copy link
Author

antulik commented Dec 22, 2020

Looking at rails code, rails defaults column types instead of relying on postgres.

https://github.com/rails/rails/blob/23019c3969906b032235a644eb73768809e289a6/activerecord/lib/active_record/querying.rb#L50-L52

while postgresql_cursor gem forces all column types from postgres

def column_types
return nil if ::ActiveRecord::VERSION::MAJOR < 4
return @column_types if @column_types
types = {}
fields = @result.fields
fields.each_with_index do |fname, i|
ftype = @result.ftype i
fmod = @result.fmod i
types[fname] = @connection.get_type_map.fetch(ftype, fmod) do |oid, mod|
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
if ::ActiveRecord::VERSION::MAJOR <= 4
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Identity.new
else
::ActiveRecord::Type::Value.new
end
end
end
@column_types = types
end

@JeanSebTr
Copy link

JeanSebTr commented Nov 11, 2022

We had the same issue with an enum column and I suspect this would do to this as well for our columns using store_model.

Our workaround was to use the cursor instance directly to override the klass param passed to each_instance:

class ModelInstantiator
  def initialize(model)
    @model = model
  end

  def instantiate(row, column_types)
    column_types = column_types.reject { |k, _| @model.attribute_types.key?(k) }
    @model.instantiate(row, column_types)
  end
end

SomeModel.all.each_row(**some_kwargs).each_instance(ModelInstantiator.new(SomeModel)) do |record|
  # .... do something with record
end

@keymastervn
Copy link

keymastervn commented Nov 22, 2023

I have an issue with DateTime which is solved by running eg. created_at.in_time_zone.to_fs(:dmy), else it is printing UTC.

activerecord (= 7.0.5.1)

@keymastervn
Copy link

Update: I endup following to what Rails has been doing with column_types

https://github.com/rails/rails/blob/92d204e7ac0275ec6b566559006fe64fa4319259/activerecord/lib/active_record/querying.rb#L60C14-L62

by rejecting if the attribute_types has the mapping keys column_types.reject { |k, _| klass.attribute_types.key?(k) }, the column_types then becomes an empty hash.

    def each_instance(klass = nil, &block)
      klass ||= @type
      each_tuple do |row|
        if ::ActiveRecord::VERSION::MAJOR < 4
          model = klass.send(:instantiate, row)
        else
          @column_types ||= column_types.reject { |k, _| klass.attribute_types.key?(k) }

          model = klass.send(:instantiate, row, @column_types)
        end
        block.call(model)
      end
    end

    def each_instance_batch(klass = nil, &block)
      klass ||= @type
      each_batch do |batch|
        models = batch.map do |row|
          if ::ActiveRecord::VERSION::MAJOR < 4
            klass.send(:instantiate, row)
          else
            @column_types ||= column_types.reject { |k, _| klass.attribute_types.key?(k) }

            klass.send(:instantiate, row, @column_types)
          end
        end
        block.call(models)
      end
    end

and it works

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants