Search code examples
rubymany-to-manyassociationsruby-on-rails-3.2one-to-many

How to save to Database with associations in rails protecting mass assignment


After trying for few hours I can not save to the database.

The context is this: I have two types of users, one for that I only need very basic information [Username, email, password] and another kind of user for who I need a lot of information [age, gender, city and so on]

I did not use STI becouse of the vast quantity of Null values there would be in the table. So I created this three modes in which a user has a profile (profiles table) or not depending of its type [1 or 2], and a field of this profile is the city this user is living in, that relates to another table in the DB, the cities table

class User < ActiveRecord::Base
  has_one :profile
  has_one :city, through: :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
  belongs_to :city
  [...a bunch of fields here]
end

class City < ActiveRecord::Base
  has_many :profiles
  has_many :users, through: :profiles
end

When I play with them in the rails console everything goes OK:

usr = User.new(name: "roxy", email: "[email protected]", password: "roxanna", password_confirmation: "roxanna", utype: 1)
cty = City.new(name: "Bucaramanga")
prf = Profile.new (rname: "Rosa Juliana Diaz del Castillo"...)
prf.city = cty
usr.profile = prf
usr.valid?
=> true
usr.save
=> true

but when I try to save in the app (View an Model)

<%= f.label :city, "En que ciudad te encuentras?"%>
<%= select_tag :city, options_from_collection_for_select(City.all, 'id', "name"),{:prompt => 'Selecciona tu ciudad'}%>

def new
  @profile = Profile.new
end

def create
  @profile = params[:profile]
  @city= City.find_by_id(params[:city].to_i)
  @profile.city = @city
end

I get this error:

undefined method `city=' for #<ActiveSupport::HashWithIndifferentAccess:0xa556fe0>

Can someone please help me?

UPDATE As David suggested I created the Profile object in the first line of the create method, so my controller now look like this:

def create
  @profile = Profile.new(params[:profile])
  @city= City.find_by_id(params[:city].to_i)
  @profile.city = @city
  @usr = current_user
  if @usr.profile.exists? @profile
    @usr.errors.add(:profile, "is already assigned to this user") # or something to that effect
    render :new
  else 
   @usr.profile << @profile
   redirect_to root_path
  end
end

But I'm getting this error now

undefined method `exists?' for nil:NilClass

current_user returns the @current_user

def current_user
  @current_user ||= User.find_by_remember_token(cookies[:remember_token])
end

Could you tell me please, what am I doing wrong?


Solution

  • I want to write this to all of you who are beginning as well as I am and are stuck in this step.

    I had to create a new project and play with it to realize what I was doing wrong. I figured out that I was validating a last time field I added to the Profiles table and had

    # education       :string(255)     not null
    

    but I had not added it yet to the form so the error launched is:

    Failed to save the new associated so_profile.
    

    Now, you know if you got this error, go check your schema and look for NOT_NULL fields you might be missing in the form, also you can comment out all your model validations and after it's working uncomment'em to be sure.

    So, my Final Models:

    class User < ActiveRecord::Base
      has_one :profile
      has_one :city, through: :profile
      attr_accessible :email, :name
    end
    
    class Profile < ActiveRecord::Base
      belongs_to :user
      belongs_to :city
      attr_accessible :age, :fcolor, :gender
    end
    
    class City < ActiveRecord::Base
      has_many :profiles
      has_many :users, through: :profiles
      attr_accessible :name
    end
    

    My controllers:

    class ProfilesController < ApplicationController
      def new
        @user = User.find_by_id(params[:id])
        @profile = Profile.new
      end
    
      def create
        @profile = Profile.new(params[:profile])
        city = City.find_by_id(params[:city])
        @profile.city = city
        @user = User.find_by_id(params[:userid])
        @user.profile = @profile
        if @user.save
          flash[:success] =  "Guardado"
          redirect_to profile_path(id: @user.id)
        end
      end
    
      def show
       @user = User.find(params[:id])
      end  
    end
    
    class UsersController < ApplicationController
      def new
        @user = User.new
      end
    
      def create
        @user = User.new(params[:user])
        if @user.save
          flash[:success] =  "Registrado!"
          redirect_to new_profile_path(id: @user.id)
        else
          flash[:error] =  "No Registrado :("
          redirect_to new
        end
      end
    
      def show
        @user = User.find_by_id(params[:id])
      end
    end
    

    In a real app you have to use Cookies or something else to keep the session alive and therefore the user_token from where you get the user_id, but it works to play with associations.

    The views:

    profiles/new.html.erb

    <%= @user.name %>
    <%= form_for @profile, url: {action: :create, userid: @user.id } do |f| %>
    <%= f.label :age, "Edad" %>
    <%= f.text_field :age%> <br />
    
    <%= label :city, "Ciudad"%>
    <%= select_tag :city, options_from_collection_for_select(City.all, 'id', 'name')%>
    
    <%= f.submit %>
    <% end %>
    

    profiles/show.html.erb

    Hello <%= @user.name %><br />
    Tu edad es: <%= @user.profile.age %><br />
    Vives en <%= @user.profile.city.name%>
    

    users/new.html.erb

    <%= form_for @user do |f|%>
    <%= f.label :name, "Nombre"%>
    <%= f.text_field :name, size: 20, placeholder: "Escribe tu nombre aqui" %><br />
    
    <%= f.label :email, "Email"%>
    <%= f.text_field :email, size: 20, placeholder: "Escribe tu email aqui" %><br />
    
    <%= f.submit "Sign me up!"%>
    

    users/show.html.erb

    Name: <%= @user.name %><br />
    Email: <%= @user.email %>
    

    And that's it!

    Cheers.