ruby-on-railsunit-testing

Ruby on Rails testing controller for nested form creation


I have a model entities with a HMT relationship in the model user_entity_roles. I create new entities with a nested form. In the controller, this is a 2 step process via the new and create methods. I can't figure out how to write a test for creating a new entity.

Models:

class Entity < ApplicationRecord
  has_many :invoices, dependent: :restrict_with_error
  has_many :user_entity_roles, dependent: :destroy
  accepts_nested_attributes_for :user_entity_roles
  has_many :users, through: :user_entity_roles, dependent: :restrict_with_error
  belongs_to :invoice_number_format
  has_many :debitors, dependent: :restrict_with_error
  validates_associated :user_entity_roles

  validates :company, presence: true
  validates :street, presence: true
  validates :house_number, presence: true
  validates :zip, presence: true
  validates :city, presence: true
end

class UserEntityRole < ApplicationRecord
  belongs_to :entity, touch: true
  belongs_to :user
  validates :entity_id, uniqueness: { scope: :user_id }
end

Entities Controller:

def new
  @entity = Entity.new
  @entity.user_entity_roles.build
  @entity.user_entity_roles.first.user_id = Current.user.id
end

def create
  @entity = Entity.new(entity_params)
  if @entity.save
    redirect_to @entity
  else
    render :new, status: :unprocessable_entity
  end
end

Entities Test Controller:

test 'should create entity with valid params' do
  sign_in_as @user
  assert_difference -> { Entity.count }, 1 do
    post entities_url, params: {
      entity: {
        company: 'MyString',
        street: 'MyString',
        house_number: 'MyString',
        zip: 'MyString',
        city: 'MyString',
        tax_number: 'MyString',
        vat_id: 'MyString',
        hrb: 'MyString',
        invoice_number_format_id: 2,
        user_entity_roles_attributes: {
          id: 0, # This is wrong
          user_id: @user.id,
          is_manager: 1,
          is_editor: 1
        }
      }
    }
  end
end

This doesn't work, because the entity_id in the nested user_entity_role must be the id of the newly created entity.

Edit: Per the first answer, this is also in the controller. Maybe the error is there?

  def entity_params
    params.require(:entity).permit(:id, :company, :street, :house_number, :zip, :city, :tax_number, :vat_id, :hrb,:managing_director, :iban, :bank, :invoice_number_format_id, user_entity_roles_attributes: %i[id is_manager is_editor user_id])
  end

Solution

  • What I don't get here is why the user_entity_role is being created from user input at all and not just being created in the controller.

    Surely it's not up to the user to decide the authorization logic for newly created items in your application?

    def new
      @entity = Entity.new
    end
    
    def create
      @entity = Entity.new(entity_params)
      @entity.user_entity_roles.new(
        user: Current.user,
        is_manager: true,
        is_editor: true
      )
      if @entity.save
        redirect_to @entity
      else
        render :new, status: :unprocessable_entity
      end
    end
    
    # ...
    
    def entity_params
        params.require(:entity) # line breaks are free dude
              .permit(
                 :company, :street, :house_number, # do not include id
                 :zip, :city, :tax_number, 
                 :vat_id, :hrb,:managing_director, 
                 :iban, :bank, :invoice_number_format_id
               )
    end
    
    def valid_params
     {
       entity: {
         company: 'MyString',
         street: 'MyString',
         house_number: 'MyString',
         zip: 'MyString',
         city: 'MyString',
         tax_number: 'MyString',
         vat_id: 'MyString',
         hrb: 'MyString',
         invoice_number_format_id: 2
       }
     }
    end
    
    test 'should create entity with valid params' do
      sign_in_as @user
      assert_difference -> { Entity.count }, 1 do
        post entities_url, params: valid_params
      end
    end
    
    test 'should add role to user' do
      assert_difference -> { @user.user_entity_roles.count }, 1 do
        post entities_url, params: valid_params
      end  
    end
    

    You should carefully consider what attributes in your model you're actually exposing though your API.

    Additionally having boolean attributes prefaced with is_ is in itself an anti-pattern. There are much better ways to this beyond having an ever growing number of boolean columns in the table like for example having a roles table containing a definition and a user_roles table that joins roles and users.