Search code examples
rubyruby-on-rails-3devisejoinnomethoderror

NoMethodError - undefined method for a nested form from a join table using devise registration


I'm following this tutorial on how to nest other Models in my Devise registration form. I'm getting an error in my New controller:

'NoMethodError in Users::RegistrationsController#new undefined method `languages_user=' for #'.

Languages_Users is a join table, and I'm wondering if this is the reason it isn't working, but I don't understand what the solution is. I want to add 2 different records of Languages_Users when the user signs up.

Models:

User.rb

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  belongs_to :role

  has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100#" }, :default_url => "/images/:style/missing.png"

  validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
  validates_presence_of :first_name, :last_name, :location, :nationality, :bio

  before_save :assign_role

  def assign_role
    self.role = Role.find_by name: "user" if self.role.nil?
  end

  has_many :languages_users
    has_many :languages, :through => :languages_users
  accepts_nested_attributes_for :languages_users

Language.rb

class Language < ActiveRecord::Base

    has_many :languages_users
    has_many :users, :through => :languages_users
end

Langauges_user.rb

class LanguagesUser < ActiveRecord::Base
    belongs_to :user
    belongs_to :language

    validates_presence_of :user_id, :language_id, :level 
    validates :user_id, :uniqueness => {:scope => :language_id, :message => 'can only delcare each language once. Please change the level of the language in Manage Languages.'}

end

Controllers:

registrations_controller.rb

 class Users::RegistrationsController < Devise::RegistrationsController

  def new
    build_resource({})
     self.resource.languages_user = LanguagesUser.new[sign_up_params]
    respond_with self.resource
  end
 def create
    @user_id = current_user.id
    super
 end

 def sign_up_params
    allow = [:email, :password, :password_confirmation, [languages_user_attributes: [:language_id, :user_id, :level]]]
  end
end

Relevant sections of User's new.html.erb

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>
   <%= f.fields_for :langauges_user do |lu| %>
      <%#= lu.text_field :language_id %>
      <%= lu.collection_select(:language_id, Language.order('language ASC').all, :id, :language) %><br>
      <%= lu.hidden_field languages_user[level], value: 1 %>
    <% end %>
    <%= f.submit "Sign up" %>
 <% end %>

Relevant routes

 Rails.application.routes.draw do
  resources :languages_users
  devise_for :users, controllers: { registrations: "users/registrations" }
  resources :users
    get 'languages_users/:id/sign_up', to: 'languages_users#sign_up', as: 'sign_up'

 end

I'm still learning - so please let me know if you need to see anything else. Thanks!


Solution

  • I'm not that up to speed on Devise as I only recently started using it myself, but if I understand correctly it's not a Devise related problem - just harder to get a fix on because of Devise's self.resource abstraction.

    You've deviated from your tutorial in an important respect: in the tutorial a User creates a Company, but the Company has_many :users. In your case the User creates a LanguagesUser, but here, the User has_many :languages_users. This means new syntax. This line, that's causing your error currently:

    self.resource.languages_user = LanguagesUser.new[sign_up_params]
    

    Needs to be along the lines of:

    self.resource.languages_users.build(sign_up_params) #but see below re sign_up_params
    

    Or if you want to save the associated resource right off the bat (I assume not, since you're not at the moment), you can use create or create! instead of build.

    Aside

    You may run into different trouble with your sign_up_params method, which also appears to have deviated from the tutorial - it doesn't actually use the allow array to whitelist any params, at least as written in your question. In any case, note they didn't use it when instantiating the Company, so it may not be fit for purpose when building your LanguagesUser, either.

    A simple call to sign_up_params[:languages_user_attributes] should get you over the line, once you've fixed the sign_up_params method. Or you can set the nested object up with its own params whitelist.