Back to Repositories

Testing Mongoid Adapter Implementation in Rails Admin

This test suite validates the Mongoid adapter implementation for Rails Admin, focusing on data access, associations, and query building functionality. It ensures proper interaction between Rails Admin and MongoDB databases through comprehensive testing of model operations and query interfaces.

Test Coverage Overview

The test suite provides extensive coverage of core Mongoid adapter functionality including:
  • Association handling and property management
  • Basic CRUD operations and data access methods
  • Complex query building and filtering capabilities
  • Support for various data types and search operations

Implementation Analysis

The testing approach employs RSpec to validate the adapter’s implementation, using a combination of unit tests and integration scenarios. Key patterns include factory-based test data generation, context-specific test groups, and comprehensive assertion chains for verifying MongoDB query construction.

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • FactoryBot for test data generation
  • Mongoid configuration and database setup
  • Custom matchers for MongoDB query validation

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through:
  • Comprehensive edge case coverage
  • Proper test isolation and setup
  • Clear test organization and naming
  • Thorough validation of query building logic

railsadminteam/rails_admin

spec/rails_admin/adapters/mongoid_spec.rb

            
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'RailsAdmin::Adapters::Mongoid', mongoid: true do
  describe '#associations' do
    it 'returns Association class' do
      expect(RailsAdmin::AbstractModel.new(Player).associations.first).
        to be_a_kind_of RailsAdmin::Adapters::Mongoid::Association
    end
  end

  describe '#properties' do
    it 'returns Property class' do
      expect(RailsAdmin::AbstractModel.new(Player).properties.first).
        to be_a_kind_of RailsAdmin::Adapters::Mongoid::Property
    end
  end

  describe '#base_class' do
    it 'returns inheritance base class' do
      expect(RailsAdmin::AbstractModel.new(Hardball).base_class).to eq Ball
    end
  end

  describe 'data access methods' do
    before do
      @players = FactoryBot.create_list(:player, 3)
      @abstract_model = RailsAdmin::AbstractModel.new('Player')
    end

    it '#new returns a Mongoid::Document instance' do
      expect(@abstract_model.new).to be_a(Mongoid::Document)
    end

    it '#get returns a Mongoid::Document instance' do
      expect(@abstract_model.get(@players.first.id.to_s)).to eq(@players.first)
    end

    it '#get returns nil when id does not exist' do
      expect(@abstract_model.get('4f4f0824dcf2315093000000')).to be_nil
    end

    context 'when Mongoid.raise_not_found_error is false' do
      before { allow(Mongoid).to receive(:raise_not_found_error).and_return(false) }

      it '#get returns nil when id does not exist' do
        expect(@abstract_model.get('4f4f0824dcf2315093000000')).to be_nil
      end
    end

    it '#first returns a player' do
      expect(@players).to include @abstract_model.first
    end

    it '#count returns count of items' do
      expect(@abstract_model.count).to eq(@players.count)
    end

    it '#destroy destroys multiple items' do
      @abstract_model.destroy(@players[0..1])
      expect(Player.all).to eq(@players[2..2])
    end

    it '#where returns filtered results' do
      expect(@abstract_model.where(name: @players.first.name).to_a).to eq([@players.first])
    end

    describe '#all' do
      it 'works without options' do
        expect(@abstract_model.all.to_a).to match_array @players
      end

      it 'supports eager loading' do
        expect(@abstract_model.all(include: :team).inclusions.collect(&:class_name)).to eq(['Team'])
      end

      it 'supports limiting' do
        expect(@abstract_model.all(limit: 2).to_a.size).to eq(2)
      end

      it 'supports retrieval by bulk_ids' do
        expect(@abstract_model.all(bulk_ids: @players[0..1].collect { |player| player.id.to_s }).to_a).to match_array @players[0..1]
      end

      it 'supports pagination' do
        expect(@abstract_model.all(sort: 'players._id', page: 2, per: 1).to_a).to eq(@players.sort_by(&:_id)[1..1])
        # To prevent RSpec matcher to call Mongoid::Criteria#== method,
        # (we want to test equality of query result, not of Mongoid criteria)
        # to_a is added to invoke Mongoid query
      end

      it 'supports ordering' do
        expect(@abstract_model.all(sort: 'players._id', sort_reverse: true).to_a).to eq(@players.sort)
        expect(@abstract_model.all(sort: 'players._id', sort_reverse: false).to_a).to eq(@players.sort.reverse)
      end

      it 'supports querying' do
        expect(@abstract_model.all(query: @players[1].name)).to eq(@players[1..1])
      end

      it 'supports filtering' do
        expect(@abstract_model.all(filters: {'name' => {'0000' => {o: 'is', v: @players[1].name}}})).to eq(@players[1..1])
      end

      it 'ignores non-existent field name on filtering' do
        expect { @abstract_model.all(filters: {'dummy' => {'0000' => {o: 'is', v: @players[1].name}}}) }.not_to raise_error
      end
    end
  end

  describe 'searching on association' do
    describe 'whose type is belongs_to' do
      before do
        RailsAdmin.config Player do
          field :team do
            queryable true
          end
        end
        @players = FactoryBot.create_list(:player, 3)
        @team = FactoryBot.create :team, name: 'foobar'
        @team.players << @players[1]
        @abstract_model = RailsAdmin::AbstractModel.new('Player')
      end

      it 'supports querying' do
        expect(@abstract_model.all(query: 'foobar').to_a).to eq(@players[1..1])
      end

      it 'supports filtering' do
        expect(@abstract_model.all(filters: {'team' => {'0000' => {o: 'is', v: 'foobar'}}}).to_a).to eq(@players[1..1])
      end
    end

    describe 'whose type is has_many' do
      before do
        RailsAdmin.config Team do
          field :players do
            queryable true
            searchable :name
          end
        end
        @teams = FactoryBot.create_list(:team, 3)
        @players = [{team: @teams[1]},
                    {team: @teams[1], name: 'foobar'},
                    {team: @teams[2]}].collect { |h| FactoryBot.create :player, h }
        @abstract_model = RailsAdmin::AbstractModel.new('Team')
      end

      it 'supports querying' do
        expect(@abstract_model.all(query: 'foobar').to_a).to eq(@teams[1..1])
      end

      it 'supports filtering' do
        expect(@abstract_model.all(filters: {'players' => {'0000' => {o: 'is', v: 'foobar'}}}).to_a).to eq(@teams[1..1])
      end
    end

    describe 'whose type is has_and_belongs_to_many' do
      before do
        RailsAdmin.config Team do
          field :fans do
            queryable true
            searchable :name
          end
        end
        @teams = FactoryBot.create_list(:team, 3)
        @fans = [{}, {name: 'foobar'}, {}].collect { |h| FactoryBot.create :fan, h }
        @teams[1].fans = [@fans[0], @fans[1]]
        @teams[2].fans << @fans[2]
        @abstract_model = RailsAdmin::AbstractModel.new('Team')
      end

      it 'supports querying' do
        expect(@abstract_model.all(query: 'foobar').to_a).to eq(@teams[1..1])
      end

      it 'supports filtering' do
        expect(@abstract_model.all(filters: {'fans' => {'0000' => {o: 'is', v: 'foobar'}}}).to_a).to eq(@teams[1..1])
      end
    end

    describe 'whose type is embedded has_many' do
      before do
        RailsAdmin.config FieldTest do
          field :embeds do
            queryable true
            searchable :all
          end
        end
        @field_tests = FactoryBot.create_list(:field_test, 3)
        @field_tests[0].embeds.create name: 'foo'
        @field_tests[1].embeds.create name: 'bar'
        @abstract_model = RailsAdmin::AbstractModel.new('FieldTest')
      end

      it 'supports querying' do
        expect(@abstract_model.all(query: 'bar').to_a).to eq(@field_tests[1..1])
      end

      it 'supports filtering' do
        expect(@abstract_model.all(filters: {'embeds' => {'0000' => {o: 'is', v: 'bar'}}}).to_a).to eq(@field_tests[1..1])
      end
    end
  end

  describe '#query_scope' do
    before do
      @abstract_model = RailsAdmin::AbstractModel.new('Player')
      @players = [{}, {name: 'Many foos'}, {position: 'foo shortage'}].
                 collect { |h| FactoryBot.create :player, h }
    end

    it 'makes correct query' do
      expect(@abstract_model.all(query: 'foo').to_a).to match_array @players[1..2]
    end

    context 'when parsing is not idempotent' do
      before do
        RailsAdmin.config do |c|
          c.model Player do
            field :name do
              def parse_value(value)
                "#{value}s"
              end
            end
          end
        end
      end

      it 'parses value only once' do
        expect(@abstract_model.all(query: 'foo')).to match_array @players[1..1]
      end
    end
  end

  describe '#filter_scope' do
    before do
      @abstract_model = RailsAdmin::AbstractModel.new('Player')
      @team = FactoryBot.create :team, name: 'king of bar'
      @players = [{}, {team: @team}, {name: 'Many foos', team: @team}, {name: 'Great foo'}].
                 collect { |h| FactoryBot.create :player, h }
    end

    it 'makes correct query' do
      expect(@abstract_model.all(filters: {'name' => {'0000' => {o: 'like', v: 'foo'}}, 'team' => {'0001' => {o: 'like', v: 'bar'}}})).to eq([@players[2]])
    end

    context 'when parsing is not idempotent' do
      before do
        RailsAdmin.config do |c|
          c.model Player do
            field :name do
              def parse_value(value)
                "#{value}s"
              end
            end
          end
        end
      end

      it 'parses value only once' do
        expect(@abstract_model.all(filters: {'name' => {'0000' => {o: 'like', v: 'foo'}}})).to match_array @players[2]
      end
    end
  end

  describe '#build_statement' do
    before do
      I18n.locale = :en
      @abstract_model = RailsAdmin::AbstractModel.new('FieldTest')
    end

    it "ignores '_discard' operator or value" do
      [['_discard', ''], ['', '_discard']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to be_nil
      end
    end

    it "supports '_blank' operator" do
      [['_blank', ''], ['', '_blank']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(name: {'$in' => [nil, '']})
      end
    end

    it "supports '_present' operator" do
      [['_present', ''], ['', '_present']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(name: {'$nin' => [nil, '']})
      end
    end

    it "supports '_null' operator" do
      [['_null', ''], ['', '_null']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(name: nil)
      end
    end

    it "supports '_not_null' operator" do
      [['_not_null', ''], ['', '_not_null']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(name: {'$ne' => nil})
      end
    end

    it "supports '_empty' operator" do
      [['_empty', ''], ['', '_empty']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(name: '')
      end
    end

    it "supports '_not_empty' operator" do
      [['_not_empty', ''], ['', '_not_empty']].each do |value, operator|
        expect(@abstract_model.send(:build_statement, :name, :string, value, operator)).to eq(name: {'$ne' => ''})
      end
    end

    it 'supports boolean type query' do
      %w[false f 0].each do |value|
        expect(@abstract_model.send(:build_statement, :field, :boolean, value, nil)).to eq(field: false)
      end
      %w[true t 1].each do |value|
        expect(@abstract_model.send(:build_statement, :field, :boolean, value, nil)).to eq(field: true)
      end
      expect(@abstract_model.send(:build_statement, :field, :boolean, 'word', nil)).to be_nil
    end

    it 'supports integer type query' do
      expect(@abstract_model.send(:build_statement, :field, :integer, '1', nil)).to eq(field: 1)
      expect(@abstract_model.send(:build_statement, :field, :integer, 'word', nil)).to be_nil
    end

    it 'supports integer type range query' do
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', '', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :integer, ['2', '', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', '3', ''], 'between')).to eq(field: {'$gte' => 3})
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', '', '5'], 'between')).to eq(field: {'$lte' => 5})
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', '10', '20'], 'between')).to eq(field: {'$gte' => 10, '$lte' => 20})
      expect(@abstract_model.send(:build_statement, :field, :integer, %w[15 10 20], 'between')).to eq(field: {'$gte' => 10, '$lte' => 20})
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', 'word1', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', '', 'word2'], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :integer, ['', 'word3', 'word4'], 'between')).to be_nil
    end

    it 'supports both decimal and float type queries' do
      expect(@abstract_model.send(:build_statement, :field, :decimal, '1.1', nil)).to eq(field: 1.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, 'word', nil)).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, '1.1', 'default')).to eq(field: 1.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, 'word', 'default')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, '1.1', 'between')).to eq(field: 1.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, 'word', 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['6.1', '', ''], 'default')).to eq(field: 6.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['7.1', '10.1', ''], 'default')).to eq(field: 7.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['8.1', '', '20.1'], 'default')).to eq(field: 8.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['9.1', '10.1', '20.1'], 'default')).to eq(field: 9.1)
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', '', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['2.1', '', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', '3.1', ''], 'between')).to eq(field: {'$gte' => 3.1})
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', '', '5.1'], 'between')).to eq(field: {'$lte' => 5.1})
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', '10.1', '20.1'], 'between')).to eq(field: {'$gte' => 10.1, '$lte' => 20.1})
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['15.1', '10.1', '20.1'], 'between')).to eq(field: {'$gte' => 10.1, '$lte' => 20.1})
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', 'word1', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', '', 'word2'], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :decimal, ['', 'word3', 'word4'], 'between')).to be_nil

      expect(@abstract_model.send(:build_statement, :field, :float, '1.1', nil)).to eq(field: 1.1)
      expect(@abstract_model.send(:build_statement, :field, :float, 'word', nil)).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, '1.1', 'default')).to eq(field: 1.1)
      expect(@abstract_model.send(:build_statement, :field, :float, 'word', 'default')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, '1.1', 'between')).to eq(field: 1.1)
      expect(@abstract_model.send(:build_statement, :field, :float, 'word', 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, ['6.1', '', ''], 'default')).to eq(field: 6.1)
      expect(@abstract_model.send(:build_statement, :field, :float, ['7.1', '10.1', ''], 'default')).to eq(field: 7.1)
      expect(@abstract_model.send(:build_statement, :field, :float, ['8.1', '', '20.1'], 'default')).to eq(field: 8.1)
      expect(@abstract_model.send(:build_statement, :field, :float, ['9.1', '10.1', '20.1'], 'default')).to eq(field: 9.1)
      expect(@abstract_model.send(:build_statement, :field, :float, ['', '', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, ['2.1', '', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, ['', '3.1', ''], 'between')).to eq(field: {'$gte' => 3.1})
      expect(@abstract_model.send(:build_statement, :field, :float, ['', '', '5.1'], 'between')).to eq(field: {'$lte' => 5.1})
      expect(@abstract_model.send(:build_statement, :field, :float, ['', '10.1', '20.1'], 'between')).to eq(field: {'$gte' => 10.1, '$lte' => 20.1})
      expect(@abstract_model.send(:build_statement, :field, :float, ['15.1', '10.1', '20.1'], 'between')).to eq(field: {'$gte' => 10.1, '$lte' => 20.1})
      expect(@abstract_model.send(:build_statement, :field, :float, ['', 'word1', ''], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, ['', '', 'word2'], 'between')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :float, ['', 'word3', 'word4'], 'between')).to be_nil
    end

    it 'supports string type query' do
      expect(@abstract_model.send(:build_statement, :field, :string, '', nil)).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'was')).to be_nil
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'default')).to eq(field: /foo/i)
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'like')).to eq(field: /foo/i)
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'not_like')).to eq(field: /^((?!foo).)*$/i)
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'starts_with')).to eq(field: /^foo/i)
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'ends_with')).to eq(field: /foo$/i)
      expect(@abstract_model.send(:build_statement, :field, :string, 'foo', 'is')).to eq(field: 'foo')
    end

    it 'supports date type query' do
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '2012-01-02', '2012-01-03'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 2), '$lte' => Date.new(2012, 1, 3)}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '2012-01-03', ''], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.new(2012, 1, 3)}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['', '', '2012-01-02'], o: 'between'}}).selector).to eq('$and' => [{'date_field' => {'$lte' => Date.new(2012, 1, 2)}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: ['2012-01-02'], o: 'default'}}).selector).to eq('$and' => [{'date_field' => Date.new(2012, 1, 2)}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'today'}}).selector).to eq('$and' => [{'date_field' => Date.today}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'yesterday'}}).selector).to eq('$and' => [{'date_field' => Date.yesterday}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'this_week'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => Date.today.beginning_of_week, '$lte' => Date.today.end_of_week}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'date_field' => {'1' => {v: [], o: 'last_week'}}).selector).to eq('$and' => [{'date_field' => {'$gte' => 1.week.ago.to_date.beginning_of_week, '$lte' => 1.week.ago.to_date.end_of_week}}])
    end

    it 'supports datetime type query' do
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '2012-01-02T12:00:00', '2012-01-03T12:00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 2, 12), '$lte' => Time.zone.local(2012, 1, 3, 12)}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '2012-01-03T12:00:00', ''], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Time.zone.local(2012, 1, 3, 12)}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['', '', '2012-01-02T12:00:00'], o: 'between'}}).selector).to eq('$and' => [{'datetime_field' => {'$lte' => Time.zone.local(2012, 1, 2, 12)}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: ['2012-01-02T12:00:00'], o: 'default'}}).selector).to eq('$and' => [{'datetime_field' => Time.zone.local(2012, 1, 2, 12)}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'today'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Date.today.beginning_of_day, '$lte' => Date.today.end_of_day}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'yesterday'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Date.yesterday.beginning_of_day, '$lte' => Date.yesterday.end_of_day}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'this_week'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => Date.today.beginning_of_week.beginning_of_day, '$lte' => Date.today.end_of_week.end_of_day}}])
      expect(@abstract_model.send(:filter_scope, FieldTest, 'datetime_field' => {'1' => {v: [], o: 'last_week'}}).selector).to eq('$and' => [{'datetime_field' => {'$gte' => 1.week.ago.to_date.beginning_of_week.beginning_of_day, '$lte' => 1.week.ago.to_date.end_of_week.end_of_day}}])
    end

    it 'supports enum type query' do
      expect(@abstract_model.send(:build_statement, :field, :enum, '1', nil)).to eq(field: {'$in' => ['1']})
    end
  end

  describe 'model attribute method' do
    before do
      @abstract_model = RailsAdmin::AbstractModel.new('Player')
    end

    it '#scoped returns relation object' do
      expect(@abstract_model.scoped).to be_instance_of(Mongoid::Criteria)
    end

    it '#table_name works' do
      expect(@abstract_model.table_name).to eq('players')
    end
  end

  describe 'serialization' do
    before do
      @abstract_model = RailsAdmin::AbstractModel.new('FieldTest')
      @controller = RailsAdmin::MainController.new
    end

    it 'accepts array value' do
      params = HashWithIndifferentAccess.new(array_field: '[1, 3]')
      @controller.send(:sanitize_params_for!, 'create', @abstract_model.config, params)
      expect(params[:array_field]).to eq([1, 3])
    end

    it 'accepts hash value' do
      params = HashWithIndifferentAccess.new(hash_field: '{a: 1, b: 3}')
      @controller.send(:sanitize_params_for!, 'create', @abstract_model.config, params)
      expect(params[:hash_field]).to eq('a' => 1, 'b' => 3)
    end
  end
end