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

MariaDB doesn't not support JSON type. Undefined method 'accessor' #425

Closed
deanpcmad opened this issue Sep 8, 2021 · 18 comments
Closed

Comments

@deanpcmad
Copy link
Contributor

deanpcmad commented Sep 8, 2021

I've just tried to set Pay v3 up on my Rails application (and I've also created a basic Rails app to test this error) but I'm running into issues.

customer = Customer.create email: "test@test.com
customer.set_payment_processor :stripe

customer.payment_processor
#<Pay::Customer id: 1, owner_type: "Customer", owner_id: 1, processor: "stripe", processor_id: nil, default: true, data: nil, deleted_at: nil, created_at: "2021-09-08 12:56:25.966822000 +0000", updated_at: "2021-09-08 12:56:25.966822000 +0000", plan: nil, quantity: nil, payment_method_token: nil>

customer.payment_processor.customer
# NoMethodError (undefined method `accessor' for #<ActiveRecord::Type::Text:0x00005634e2aebd08>)

When using the Fake Processor, it works fine:

Customer.first.set_payment_processor :fake_processor, allow_fake: true
#<Pay::Customer id: 2, owner_type: "Customer", owner_id: 1, processor: "fake_processor", processor_id: "c3X0samQM26S1uBaKBYVy", default: true, data: nil, deleted_at: nil, created_at: "2021-09-08 12:58:06.764957000 +0000", updated_at: "2021-09-08 13:00:20.902029000 +0000", plan: nil, quantity: nil, payment_method_token: nil>

Customer.first.payment_processor.customer
#<Pay::Customer id: 2, owner_type: "Customer", owner_id: 1, processor: "fake_processor", processor_id: "c3X0samQM26S1uBaKBYVy", default: true, data: nil, deleted_at: nil, created_at: "2021-09-08 12:58:06.764957000 +0000", updated_at: "2021-09-08 13:00:20.902029000 +0000", plan: nil, quantity: nil, payment_method_token: nil>

I'm using Rails 6.1.4.1 with MariaDB 10.5.12. I've also tried MariaDB 10.6.4.

Any ideas?

@excid3
Copy link
Collaborator

excid3 commented Sep 8, 2021

You have a stacktrace I can see?

@deanpcmad
Copy link
Contributor Author

That's the thing, it doesn't give a proper stacktrace. I've attached a screenshot:

Screenshot from 2021-09-08 15-05-36

@excid3
Copy link
Collaborator

excid3 commented Sep 8, 2021

The console truncates it, but you can always get a backtrace.

begin
  mycode
rescue => exception
  puts exception.backtrace
end

That should print it all out. 👍

@deanpcmad
Copy link
Contributor Author

Ah, here we go:

/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/store.rb:217:in `store_accessor_for'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/store.rb:207:in `read_store_attribute'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/store.rb:140:in `block (3 levels) in store_accessor'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/pay-3.0.11/lib/pay/stripe/billable.rb:8:in `stripe_account'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/pay-3.0.11/lib/pay/stripe/billable.rb:235:in `stripe_options'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/pay-3.0.11/lib/pay/stripe/billable.rb:29:in `customer'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/module/delegation.rb:310:in `public_send'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/module/delegation.rb:310:in `method_missing'
(irb):8:in `rescue in irb_binding'
(irb):6:in `irb_binding'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/workspace.rb:114:in `eval'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/workspace.rb:114:in `evaluate'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/context.rb:459:in `evaluate'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:541:in `block (2 levels) in eval_input'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:704:in `signal_status'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:538:in `block in eval_input'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/ruby-lex.rb:166:in `block (2 levels) in each_top_level_statement'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/ruby-lex.rb:151:in `loop'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/ruby-lex.rb:151:in `block in each_top_level_statement'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/ruby-lex.rb:150:in `catch'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb/ruby-lex.rb:150:in `each_top_level_statement'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:537:in `eval_input'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:472:in `block in run'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:471:in `catch'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:471:in `run'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/2.7.0/irb.rb:400:in `start'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/railties-6.1.4.1/lib/rails/commands/console/console_command.rb:70:in `start'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/railties-6.1.4.1/lib/rails/commands/console/console_command.rb:19:in `start'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/railties-6.1.4.1/lib/rails/commands/console/console_command.rb:102:in `perform'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/railties-6.1.4.1/lib/rails/command/base.rb:69:in `perform'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/railties-6.1.4.1/lib/rails/command.rb:48:in `invoke'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/railties-6.1.4.1/lib/rails/commands.rb:18:in `<main>'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/bootsnap-1.8.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/bootsnap-1.8.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/bootsnap-1.8.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/bootsnap-1.8.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/home/dean/.asdf/installs/ruby/2.7.4/lib/ruby/gems/2.7.0/gems/bootsnap-1.8.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
bin/rails:5:in `<main>'

@excid3
Copy link
Collaborator

excid3 commented Sep 8, 2021

Perfect, thanks!

It looks like your pay_customers table is missing the data json column. Can you check your table?

Maybe one of the migrations is wrong.

@deanpcmad
Copy link
Contributor Author

I just used the standard migration installer: bin/rails pay:install:migrations

Here's my schema:

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2021_09_08_125550) do

  create_table "customers", charset: "utf8mb4", force: :cascade do |t|
    t.string "email"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "pay_charges", charset: "utf8mb4", force: :cascade do |t|
    t.bigint "customer_id", null: false
    t.bigint "subscription_id"
    t.string "processor_id", null: false
    t.integer "amount", null: false
    t.string "currency"
    t.integer "application_fee_amount"
    t.integer "amount_refunded"
    t.text "metadata", size: :long, collation: "utf8mb4_bin"
    t.text "data", size: :long, collation: "utf8mb4_bin"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["customer_id", "processor_id"], name: "index_pay_charges_on_customer_id_and_processor_id", unique: true
    t.index ["subscription_id"], name: "index_pay_charges_on_subscription_id"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`metadata`)", name: "metadata"
    t.check_constraint "json_valid(`metadata`)", name: "metadata"
  end

  create_table "pay_customers", charset: "utf8mb4", force: :cascade do |t|
    t.string "owner_type"
    t.bigint "owner_id"
    t.string "processor", null: false
    t.string "processor_id"
    t.boolean "default"
    t.text "data", size: :long, collation: "utf8mb4_bin"
    t.datetime "deleted_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["owner_type", "owner_id", "deleted_at", "default"], name: "pay_customer_owner_index"
    t.index ["processor", "processor_id"], name: "index_pay_customers_on_processor_and_processor_id", unique: true
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
  end

  create_table "pay_merchants", charset: "utf8mb4", force: :cascade do |t|
    t.string "owner_type"
    t.bigint "owner_id"
    t.string "processor", null: false
    t.string "processor_id"
    t.boolean "default"
    t.text "data", size: :long, collation: "utf8mb4_bin"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["owner_type", "owner_id", "processor"], name: "index_pay_merchants_on_owner_type_and_owner_id_and_processor"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
  end

  create_table "pay_payment_methods", charset: "utf8mb4", force: :cascade do |t|
    t.bigint "customer_id", null: false
    t.string "processor_id", null: false
    t.boolean "default"
    t.string "type"
    t.text "data", size: :long, collation: "utf8mb4_bin"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["customer_id", "processor_id"], name: "index_pay_payment_methods_on_customer_id_and_processor_id", unique: true
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
  end

  create_table "pay_subscriptions", charset: "utf8mb4", force: :cascade do |t|
    t.bigint "customer_id", null: false
    t.string "name", null: false
    t.string "processor_id", null: false
    t.string "processor_plan", null: false
    t.integer "quantity", default: 1, null: false
    t.string "status", null: false
    t.datetime "trial_ends_at"
    t.datetime "ends_at"
    t.decimal "application_fee_percent", precision: 8, scale: 2
    t.text "metadata", size: :long, collation: "utf8mb4_bin"
    t.text "data", size: :long, collation: "utf8mb4_bin"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["customer_id", "processor_id"], name: "index_pay_subscriptions_on_customer_id_and_processor_id", unique: true
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`data`)", name: "data"
    t.check_constraint "json_valid(`metadata`)", name: "metadata"
    t.check_constraint "json_valid(`metadata`)", name: "metadata"
  end

  create_table "pay_webhooks", charset: "utf8mb4", force: :cascade do |t|
    t.string "processor"
    t.string "event_type"
    t.text "event", size: :long, collation: "utf8mb4_bin"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.check_constraint "json_valid(`event`)", name: "event"
  end

  add_foreign_key "pay_charges", "pay_customers", column: "customer_id"
  add_foreign_key "pay_charges", "pay_subscriptions", column: "subscription_id"
  add_foreign_key "pay_payment_methods", "pay_customers", column: "customer_id"
  add_foreign_key "pay_subscriptions", "pay_customers", column: "customer_id"
end

@excid3
Copy link
Collaborator

excid3 commented Sep 8, 2021

Ah, yeah your data column is not json.

t.text "data", size: :long, collation: "utf8mb4_bin"

You're using MySQL? It supports a json column type doesn't it? I run my tests against MySQL and it uses a json column fine.

@deanpcmad
Copy link
Contributor Author

Hm. Even when setting the column type in the migration, it still does the same thing 🤔

@excid3
Copy link
Collaborator

excid3 commented Sep 8, 2021

I guess maybe MYSQL uses this to validate that it's a JSON column.

t.check_constraint "json_valid(`data`)", name: "data"

Tests run on MySQL 8: https://github.com/pay-rails/pay/blob/master/.github/workflows/ci.yml#L97

Which version are you on?

@deanpcmad
Copy link
Contributor Author

I'm using MariaDB 10.6.4, which should support it 🤔

@excid3
Copy link
Collaborator

excid3 commented Sep 8, 2021

Do you use the mysql2 gem or something else for mariadb?

@deanpcmad
Copy link
Contributor Author

Yep, mysql2. Everything else works fine so I'd rather not move all my apps back to normal MySQL.
Is there a reason you're using the JSON field and not just a normal data field? I've used serialize multiple times with no issues on a data field, plus it works on different database types

@deanpcmad
Copy link
Contributor Author

Having a quick Google, I've found this:

- MariaDB stores JSON as true text, not in binary format as MySQL. MariaDB's JSON functions are much faster than MySQL's so there is no need to store in binary format, which would add complexity when manipulating JSON objects.
- For the same reason, MariaDB's JSON data type is an alias for LONGTEXT. If you want to replicate JSON columns from MySQL to MariaDB, you should store JSON objects in MySQL in a TEXT or LONGTEXT column or use statement based replication. If you are using JSON columns and want to upgrade to MariaDB, you need to either convert them to TEXT or use mysqldump to copy these tables to MariaDB

I still think Pay should work with any database type, especially seeing as most people are using MariaDB these days.

I just tried setting them as text in the migration and it still errors out which makes me think it's a bug with Rails?

@excid3
Copy link
Collaborator

excid3 commented Sep 9, 2021

That's unfortunate that MariaDB has implemented them as text columns. Rails will see them as text and not json. The store_accessor feature only applies to columns that Rails considers json in the schema.

Seems like Rails could be improved to treat them as json, but I'm not sure how you'd reasonably detect that.

@excid3
Copy link
Collaborator

excid3 commented Sep 9, 2021

@excid3 excid3 changed the title undefined method 'accessor' MariaDB doesn't not support JSON type. Undefined method 'accessor' Sep 9, 2021
@deanpcmad
Copy link
Contributor Author

Ah, good find.
Well I've managed to get it working by using store which I think uses a Hash by default and all the model tests pass

store :data, accessors: [:stripe_connect_account_id, :onboarding_complete]

@excid3
Copy link
Collaborator

excid3 commented Sep 9, 2021

Ah yeah, I didn't think of trying that for some reason. I just tried attribute :data, :json and it didn't work.

I guess the solution might be to add store :data if it's a text column, and then we can leave the store_accessor for the attributes and I bet that works.

Will add a test and confirm that works. Then it'll probably be good to add Maria to the CI.

@excid3
Copy link
Collaborator

excid3 commented Sep 9, 2021

#429

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

Successfully merging a pull request may close this issue.

2 participants