chaadow/active_record-acts_as

undefined method `include?` for :author:Symbol when using `create_with.find_or_create_by!`

Opened this issue · 0 comments

Environment

rails 5.2.8.1
active_record-acts_as 4.0.3

Note: Higher versions of rails don't seem to have this issue (e.g. rails 6.1.7)

# schema.rb
ActiveRecord::Schema.define(version: 2023_10_25_160214) do

  create_table "books", force: :cascade do |t|
    t.string "author"
    t.string "external_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "products", force: :cascade do |t|
    t.integer "price"
    t.string "actable_type"
    t.integer "actable_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["actable_type", "actable_id"], name: "index_products_on_actable_type_and_actable_id"
  end

end

Description

I'm running into an error where when chaining the create_with and find_or_create_by! methods.

>> b = Book.create_with(author: "martin").find_or_create_by!(external_id: "1231236")
  Book Load (0.1ms)  SELECT  "books".* FROM "books" WHERE "books"."external_id" = ? LIMIT ?  [["external_id", "1231236"], ["LIMIT", 1]]
Traceback (most recent call last):
        2: from (irb):5
        1: from (irb):6:in `rescue in irb_binding'
NoMethodError (undefined method `include?' for :author:Symbol)

I think I've traced it down to this call in the scope_for_create method

scope.merge(create_with_value)

My reasoning for thinking this is that Rails 5.2.8.1 seems to stringifies their keys in the scope_for_create method https://github.com/rails/rails/blob/8030cff808657faa44828de001cd3b80364597de/activerecord/lib/active_record/relation.rb#L468-L468

So I overrode the scope_for_create method from the acts_as gem and it seems to work fine

module ActiveRecord
  module ActsAs
    module ScopeForCreate
      def scope_for_create(attributes = nil)
        unless acting_as?
          if Gem::Dependency.new("", ">= 5.2.1", "< 5.2.2").match?("", ActiveRecord.version) # rubocop:disable Style/GuardClause
            return super(attributes)
          else
            return super()
          end
        end

        scope = respond_to?(:values_for_create) ? values_for_create(attributes) : where_values_hash
        scope.merge!(where_values_hash(acting_as_model.table_name))
        scope.merge!(attributes) if attributes
        scope.merge(create_with_value.stringify_keys) # stringify keys here
      end
    end
  end
end

I'll be upgrading to rails 6 eventually (where this issue doesn't exist), but in the meantime, is overriding this one line appropriate? Or could it cause potential issues with other queries?

Update: As an alternative solution I found that you can use a hash in the create_with like Book.create_with("author" => "martin").find_or_create_by!(external_id: "1231236")