Rails API prevents booleans from being arbitrarily cast and passed validation

Event

When creating and updating boolean type columns with Rails API, I want to play types other than boolean type

$ rails -v
Rails 6.0.3.2
$ rails g model Post body:text opened:boolean

app/models/post.rb


class Post < ApplicationRecord
  validates :opened, inclusion: { in: [true, false]}
end

If you do this, you should be able to play anything other than boolean ...

$ curl localhost:3000/posts -X POST -H "Content-Type: application/json" -d '{"body": "hoge", "opened": "moge"}'
{"status":"success","data":{"id":1,"body":"hoge","opened":true,"created_at":"2020-08-16T01:31:14.277Z","updated_at":"2020-08-16T01:31:14.277Z"}}

Specify "moge" for boolean open column

Expectation: It's not boolean, so it's an error and it's played Actual condition: Cast to true and saved without error

$ curl localhost:3000/posts -X POST -H "Content-Type: application/json" -d '{"body": "hoge", "opened": "0"}'
{"status":"SUCCESS","data":{"id":2,"body":"hoge","opened":false,"created_at":"2020-08-16T01:31:28.498Z","updated_at":"2020-08-16T01:31:28.498Z"}}

Expectation: Since it is not boolean, it will be played with an error Actual condition: Cast to false and saved without error

Apparently it seems that the value will be passed in the cast state

Countermeasures

Make a custom validation

app/validators/boolean_validator.rb


class BooleanValidator < ActiveModel::EachValidator
  def validate_each(record, attr, _value)
    before_value = record.send("#{attr}_before_type_cast")
    record.errors.add(attr, "is invalid") unless %w[true false].include?(before_value.to_s.downcase)
  end
end

app/models/post.rb


class Post < ApplicationRecord
  validates :opened, boolean: true
end

result

$ curl localhost:3000/posts -X POST -H "Content-Type: application/json" -d '{"body": "hoge", "opened": "moge"}' 
{"status":"error","data":{"opened":["is invalid"]}}
$ curl localhost:3000/posts -X POST -H "Content-Type: application/json" -d '{"body": "hoge", "opened": "0"}' 
{"status":"error","data":{"opened":["is invalid"]}}
$ curl localhost:3000/posts -X POST -H "Content-Type: application/json" -d '{"body": "hoge", "opened": "true"}'
{"status":"success","data":{"id":3,"body":"hoge","opened":true,"created_at":"2020-08-16T02:25:18.211Z","updated_at":"2020-08-16T02:25:18.211Z"}}
$ curl localhost:3000/posts -X POST -H "Content-Type: application/json" -d '{"body": "hoge", "opened": "false"}'
{"status":"success","data":{"id":4,"body":"hoge","opened":false,"created_at":"2020-08-16T02:25:30.700Z","updated_at":"2020-08-16T02:25:30.700Z"}}

As expected, all but true and false are now played

reference

How to validate a boolean type column in Rails and return it as an error if the type is different If you implement it with the above URL, it will get caught in rubocop in various ways, so I am writing this article

Recommended Posts

Rails API prevents booleans from being arbitrarily cast and passed validation
[Rails] [jQuery] Prevents the submit button from being pressed until the form is filled out and selected.
[Rails] Validation settings and Japanese localization