Search code examples
jsonruby-on-rails-4strong-parameters

has_secure_password doesnt work on JSON but it does when form for


I'm using the method

has_secure_password 

on my user model which i understand creates two virtual fields:

Password 
Password_confirmation

and validates the precense of them, so by my side i'm doing these others validations:

validates :password  , length: {minimum: 6} 
validates :password_confirmation  , length: {minimum: 6}

in my view i have this form:

<%= form_for :user, url:{controller: 'users', action: 'create'}, html:{class:"form-horizontal"}  do |f| %>

        <div class="form-group">
          <%= f.label(:name, "Nombre", class:"control-label col-sm-1") %>
          <div class="col-sm-3">
          <%= f.text_field(:name, class:"form-control") %>
          </div>
        </div>

        <div class="form-group ">
              <%= f.label(:last_name, "Apellido", class:"control-label col-sm-1") %>
              <div class="col-sm-3">
                    <%= f.text_field(:last_name,  class:"form-control ") %>
              </div>
        </div>

        <div class="form-group ">
              <%= f.label(:cellphone, "Celular", class:"control-label col-sm-1") %>
              <div class="col-sm-3">
              <%= f.text_field(:cellphone, class:"form-control") %>
                </div>
        </div>

        <div class="form-group ">
              <%= f.label(:phone, "Telefono", class:"control-label col-sm-1") %>
              <div class="col-sm-3">
              <%= f.text_field(:phone, class:"form-control") %>
                </div>
        </div>

        <div class="form-group ">
              <%= f.label(:address, "Direccion", class:"control-label col-sm-1") %>
          <div class="col-sm-3">
              <%= f.text_field(:address, class:"form-control") %>
            </div>

        </div>

        <div class="form-group ">
              <%= f.label(:email, "Correo", class:"control-label col-sm-1") %>
            <div class="col-sm-3">
              <%= f.text_field(:email, class:"form-control") %>
            </div>
        </div>

        <div class="form-group ">
            <%= f.label(:password, "Contraseña", class:"control-label col-sm-1") %>
            <div class="col-sm-3">
                <%= f.password_field(:password, class:"form-control") %>
            </div>
        </div>

      <div class="form-group ">
        <%= f.label(:password_confirmation, "Confirmar Contraseña", class:"control-label col-sm-1") %>
        <div class="col-sm-3">
          <%= f.password_field(:password_confirmation, class:"form-control") %>
        </div>
      </div>

        <div class="form-group ">
            <%= f.label(:city_id, "Ciudad", class:"control-label col-sm-1") %>
            <div class="col-sm-3">
                 <%= f.select(:city_id, @cities.map {|c| [c.name, c.id]}) %>
            </div>

        </div>

        <div class="form-buttons">
            <%= submit_tag("Create Section") %>
        </div>

  <% end %>

and the controller has these actions:

def create

@user = User.new(user_params)
    if @user.save
      session[:user_id] = @user.id
      redirect_to controller: "users", action: "show"

    else
      @cities = City.all
      render 'new'
    end
  end

private

     def user_params
        params.require(:user).permit(:name, :last_name, :email, :address, :password,    :password_confirmation, :cellphone, :city_id, :phone)
      end

so when the user submmit the the form the user gets created with the encrypted password under the field password_digest and it works fine.

my actual problem its with a controller wich let the client create a new user by sending a json, this is the method for the api controller:

respond_to :json

def create
    respond_with User.create(user_params)
end 

private

  def user_params
    params.require(:user).permit(:name, :last_name, :email, :address, :password, :password_confirmation, :cellphone, :city_id, :phone)
  end

when i send this JSON:

var ad = {

            name:"juan",
            last_name:"paco",
            cellphone:"1234567890",
            phone:"1234567890",
            address:"porche",
            password:"porche",
            password_confirmation:"porche",
            email:"[email protected]",
            city_id: 1,

        };

to

localhost.com:3000/api/v1/users/create

i get :

{"errors":{"password":["can't be blank","is too short (minimum is 6 characters)"],"password_confirmation":["is too short (minimum is 6 characters)"]}}

Solution

  • Looks like this issue has to do with Parameter Wrapping http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html

    Since your json parameters are not under a root node (and you probably have parameter wrapping turned on in config/initializers/wrap_parameters.rb), the fields are automatically being wrapped in an root with the same name as the controller (user). Since password is a virtual attribute and is not returned by attribute_names it is not wrapped:

    On ActiveRecord models with no :include or :exclude option set, it will only wrap the parameters returned by the class method attribute_names.

    I think the correct solution here is to either specify password in the include list of wrap_parameters by adding this to the top of your UsersController

    wrap_parameters include: User.attribute_names + [:password]
    

    or make the JSON request with a user root node

    var ad = { 
              user: 
              {
                name:"juan",
                last_name:"paco",
                cellphone:"1234567890",
                phone:"1234567890",
                address:"porche",
                password:"porche",
                password_confirmation:"porche",
                email:"[email protected]",
                city_id: 1
              }
            };