I'm trying to implement the devise gem into my web app - everything with user login and registration is working, but when a user tries to actually leave a prediction (the only thing you need to be logged in for), I get this error message: undefined method `user=' for nil:NilClass. I can't seem to figure out what's causing this problem. Might anyone know?
_login_items.html.erb:
<ul>
<% if user_signed_in? %>
<li>
<%= link_to('Logout', destroy_user_session_path, :method => :delete) %>
</li>
<% else %>
<li>
<%= link_to('Login', new_user_session_path) %>
</li>
<% end %>
<% if user_signed_in? %>
<li>
<%= link_to('Edit registration', edit_user_registration_path) %>
</li>
<% else %>
<li>
<%= link_to('Register', new_user_registration_path) %>
</li>
<% end %>
</ul>
prediction model:
class Prediction < ActiveRecord::Base
belongs_to :student
belongs_to :user
end
user model:
class User < ActiveRecord::Base
has_many :predictions
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
predictions migration:
class CreatePredictions < ActiveRecord::Migration
def change
create_table :predictions do |t|
t.string :prediction
t.belongs_to :student, index: true
t.belongs_to :user
t.timestamps
end
end
end
user migration:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.inet :current_sign_in_ip
t.inet :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
predictions controller:
class PredictionsController <ApplicationController
def create
#prediction.user = current_user
Prediction.create(prediction_params)
redirect_to :back
end
def new
#prediction = Prediction.new(student_id: params[:student_id])
end
def destroy
Prediction.find(params[:id]).destroy
redirect_to :back
end
private
def prediction_params
params.require(:prediction).permit(:prediction) #, :student_id
end
end
The problem is in your PredictionsController. You have to create the prediction and assign it to a variable first. Change your create action to this:
def create
#prediction = Prediction.create(prediction_params)
#prediction.user = current_user
redirect_to :back
end
You were trying to assign a user to a non existing prediction. This correction creates the prediction first then assigns the user to it.
Edit: I noticed one other problem: Your prediction_params method is incorrect. The arguments you pass to the permit method must be the attributes of your prediction model which you want to mass assign. The :prediction key in the params is the one you want to require but within that nested hash you want to permit attributes of the prediction model. So, hypothetically, if your prediction model has :name, :value and :student_id attributes, your prediction_params method should look like this:
def prediction_params
params.require(:prediction).permit(:name, :value, :student_id)
end
This will work with a params hash that looks like:
{
prediction: {
name: 'something',
value: 'stuff',
student_id: 1
}
}
Related
I am new to Rails and in this project I am suppose to build a program that allows you to create user accounts and upload profile pictures to it. Then it suppose to send you an email to notify that your upload was succesfull.
User Accounts are handled with Devise
Image uploads are handled with Active Storage avatar attachments
On Devise, these kind of attachment uploads are handled in Edit Page, which is connected to Devise::RegistrationsController :
# frozen_string_literal: true
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
# def new
# super
# end
# POST /resource
# def create
# super
# end
# GET /resource/edit
# def edit
# super
# end
# PUT /resource
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
set_flash_message_for_update(resource, prev_unconfirmed_email)
bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?
respond_with resource, location: after_update_path_for(resource)
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
binding.irb
# PAY ATTENTION TO HERE
if #user.avatar.attached?
UserMailer.upload_email(#user).deliver
end
end
# DELETE /resource
# def destroy
# #user = User.find(params[:user_id])
# puts #user
# if #user.destroy
# redirect_to root_path
# end
# end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end
# protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [])
end
# If you have extra params to permit, append them to the sanitizer.
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:avatar])
end
# The path used after sign up.
# def after_sign_up_path_for(resource)
# super(resource)
# end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
edit.html.erb :
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
# PAY ATTENTION TO HERE
<div class="field">
<%= f.label :avatar %><br />
<%= f.file_field :avatar %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<div class="actions">
<%= f.submit "Update"%>
</div>
<% end %>
I want you to pay attention to the update action in registrations_controller, especially to the line that goes like:
if #user.avatar.attached?
UserMailer.upload_email(#user).deliver
end
Now this is completely wrong, even though it sends email the first time you upload an image, after that it still sends emails once you click "update" without uploading any attachments, because the only condition is "avatar.attached?", which returns true since there is already an image attached.
Now, thanks to the binding.irb, I can check what is passed to update as params when there is a file added and when there is not.
When there is a file added:
irb(#<Users::RegistrationsController:0x0000023b767c6790>):001:0> params
=> #<ActionController::Parameters {"_method"=>"put", "authenticity_token"=>"6O0jz5U2XWfb4pu_An3pMh1BUq5o71n0U2_E3zWy0-efqm0orl_Vi5o_jo1B2o4Oc11AyRt-Zd6XBPJnmXpr8w", "user"=>#<ActionController::Parameters {"email"=>"***#hotmail.com", "avatar"=>#<ActionDispatch::Http::UploadedFile:0x0000023b767c5660 #tempfile=#<Tempfile:C:/Users/pc/AppData/Local/Temp/RackMultipart20220903-13632-upo8fa.png>, #original_filename="WhatsApp Image 2022-08-06 at 23.03.10.png", #content_type="image/png", #headers="Content-Disposition: form-data; name=\"user[avatar]\"; filename=\"WhatsApp Image 2022-08-06 at 23.03.10.png\"\r\nContent-Type: image/png\r\n">, "password"=>"", "password_confirmation"=>"", "current_password"=>"password"} permitted: false>, "commit"=>"Update", "controller"=>"users/registrations", "action"=>"update"} permitted: false>
When there is not:
irb(#<Users::RegistrationsController:0x0000023b763ea950>):001:0> params
=> #<ActionController::Parameters {"_method"=>"put", "authenticity_token"=>"NCJA7QSTCuAVdogoAN9Z96B4EsJIRoGQyvwmNPVqvu1DZQ4KP_qCDFSrnRpDeD7LzmQApTvXvboOlxCMWaIG-Q", "user"=>#<ActionController::Parameters {"email"=>"***#hotmail.com", "password"=>"", "password_confirmation"=>"", "current_password"=>"123456"} permitted: false>, "commit"=>"Update", "controller"=>"users/registrations", "action"=>"update"} permitted: false>
As you can see, when there is a file added to f.file_field, you can see things like "avatar" "#tempfile" "original_filename" in the params.
So my logic is that before update action, I should check if there is a file added to f.file_field or not. If there is a file added to it, once the User is updated THEN I should send an email to notify it is succesfully uploaded but I dont know how to do that. Any ideas?
Rest of the relevant parts of my code is like :
UsersController:
class UsersController < ApplicationController
before_action :set_user
def profile;end
private
def set_user
#user = User.find(params[:id])
end
end
User Model (user.rb):
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one_attached :avatar
end
My Tables (schema.rb) :
ActiveRecord::Schema[7.0].define(version: 2022_08_28_175416) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.string "service_name", null: false
t.bigint "byte_size", null: false
t.string "checksum"
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end
There are several ways to do this. But my suggestion is to create an after_save callback on the User model, something like this:
after_save :notify_new_avatar
def notify_new_avatar
notify if new_avatar?
end
def notify
UserMailer.notify_new_avatar
end
def new_avatar?
# how do we determine this?
end
To determine if a new avatar was uploaded, I suggest using the features of Active Model Dirty.
I'm not clear what the attribute is that connotes an avatar change in your app, but User.avatar_id_previously_changed? will probably give you the detection you need.
hey guys im working on a application where a devise user sign ups and logs in, Once the user logs in they can 'create a team' or 'join a team'. I have my associations set up like this
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
validates_presence_of :phone, :city, :state, :street, :zip, presence: true, on: :create
belongs_to :team
end
team.rb
class Team < ApplicationRecord
has_many :users
end
and my tables are set up
schema.rb
create_table "teams", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "team_name"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "firstname"
t.integer "team_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
team_controller.rb
class TeamController < ApplicationController
before_action :authenticate_user!
def index
#team = current_user.team
end
def new_team
end
def create_team
#team = current_user.create_team(sanitize_team)
if #team.save
redirect_to team_root_path
else
render json: #team.errors.full_messages
end
end
def join_team
#teams = Team.all
end
def team
end
private
def sanitize_team
params.require(:team).permit(:team_name, :team_statement)
end
end
I want the users 'team_id' attribute to update with the teams id when they create a team. or when they join a team. Are my associations correct? how would i make this happen in the controller ?
Yes, associations are correct. You can do it better only by adding foreign key to your database schema. It can be done by generator rails g migration AddTeamToUsers team:references
More information about associations can be found here: https://guides.rubyonrails.org/association_basics.html
In controller you have to change only the whitelisting params to allow team_id. And you probably need to add to your form in view something like this:
<%= f.select :team_id, Team.all.map { |t| [t.team_name, t.id] } %>
Let's strip your code example down to the minimum required:
# app/models/team.rb
class Team < ApplicationRecord
has_many :users
end
# app/models/user.rb
class User < ApplicationRecord
belongs_to :team
end
# db/migrate/20181124230131_create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.string :team_name
t.timestamps
end
end
end
# db/migrate/20181124230136_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.belongs_to :team
t.timestamps
end
end
end
Then in your controller:
team = Team.where(team_name: 'foo').first_or_create!
team.users << current_user
Start by setting the association up as optional:
class User < ApplicationController
belongs_to :team, optional: true
end
Otherwise the validations on the user model will not let the user be saved without a team.
Then setup the teams resource:
# config/routes.rb
resources :teams do
post :join
end
post :join creates an additional POST /teams/:team_id/join route.
Then setup the controller:
class TeamsController
# ...
# GET /teams/new
def new
#team = Team.find
end
# POST /teams
def create
#team = Team.new(team_params)
if #team.save
unless current_user.team
current_user.update(team: #team)
end
redirect_to 'somewhere'
else
render :new
end
end
# ...
def join
#team = Team.find(params[:team_id])
if current_user.update(team: #team)
redirect_to #team, notice: 'Team joined'
else
redirect_to #team, error: 'Could not join team'
end
end
#
private
def team_params
params.require(:team).permit(:team_name, :team_statement)
end
end
Note that prefixing your action names is neither needed nor compatible with the "Rails way". Prefixing column names is also largely superfluous.
I used devide in gem and now I made Sign up&Log in pages.
When I wrote my id and email address in my app,blowser told me 1 error prohibited this user from being saved: Id can't be blank.Of course,I surely wrote id,so I don't know why.
Is this blowser error?
I wrote in home_controller,
class HomeController < ApplicationController
before_filter :find_user, only: [:index]
def index
# #id = params[:id]
# #email = params[:email]
if id == #user.id && email == #user.email
render :text => "sucsess"
else
render :text => "fail"
end
end
def create
id = params[:id]
email = params[:email]
#user = UserData.new(user_params)
#user.save
unless userData.save
#error_message = errors.full_messages.compact
end
end
private
def find_user
user = User.find(params[:id])
if current_user.id != user.id
redirect_to root_path
end
end
end
in users.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
validates :id, presence: true
validates :email, presence: true, uniqueness: true
end
in migration file
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
in routes.rb
Rails.application.routes.draw do
get 'notes/new'
devise_for :users
root to: "home#index"
get 'home/index'
get 'home/create'
namespace :home, default: {format: :json} do
resources :index, only: :create
end
end
Validation runs before creation. So the id will have no chance of being created before you save.
Remove this
validates :id, presence: true
and don't set the id manually on the create action.
Remove below line from model, as ID is auto generated field.
validates :id, presence: true
Update create action in controller file as below,
def create
#user = User.new(user_params)
#user.save
unless #user.save
#error_message = errors.full_messages.compact
end
end
Replace "UserData" with "User" which is the model name as per your code.
Comment or remove below line
validates :id, presence: true
as id is create automatically by rails and no need to validate it.
And
In your controller, change private method code:
def find_user
#user = User.find(params[:id]) #replace user to #user
if current_user.id != user.id
redirect_to root_path
end
end
I have 3 models - User, Shipment and Friendship. User can be friends with another user via Friendship-model. User also can create Shipments and can add a Friend-User to it. There is address-attribute in User and Shipment models. I need to give User a possibility to fill that address field in 2 ways at the same form:
By filling the address field manually.
By choosing from select-list a Friend of that User - so the Friends
address-attribute transfers and fills the Shipments adress-attribute
(like ctrl-c/ctrl-v) and User can Submit the form.
I can guess, that AJAX is needed to refresh the content without refreshing the page.
Shipment model:
class Shipment < ActiveRecord::Base
belongs_to :user
belongs_to :friendship
validates :image, presence: true
validates :user_id, presence: true
end
Shipments controller:
class ShipmentsController < ApplicationController
helper_method :shipment, :user
before_action :set_shipment, only: [:show]
before_action :authenticate_user!
before_action :require_same_user, only: [:show]
def index
#shipments = Shipment.all
end
def new
#shipment = Shipment.new
end
def create
#shipment = Shipment.new(shipment_params)
#shipment.user = current_user
if #shipment.save
flash[:success] = "Shipment etc."
redirect_to shipment_path(#shipment)
else
render 'new'
end
end
def show
#shipment = Shipment.find(params[:id])
end
private
def user
#user = current_user
end
def shipment
#shipment = user.shipments.new
end
def shipment_params
params.require(:shipment).permit(:name, :kg, :length, :width, :height,
:adress, :image, :user_id, :friend_id)
end
def set_shipment
#shipment = Shipment.find(params[:id])
end
def require_same_user
if current_user != #shipment.user
flash[:alert] = "Restricted/"
redirect_to root_path
end
end
end
User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :shipments, dependent: :destroy
has_many :friendships
has_many :friends, through: :friendships
has_many :inverse_friendships, :class_name => 'Friendship',
:foreign_key => 'friend_id'
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
end
Users controller (the User itself is created by Devise)
class UsersController < ApplicationController
before_action :authenticate_user!
def show
#user = User.find(params[:id])
end
def my_friends
#friendships = current_user.friends
end
def search
#users = User.search(params[:search_param])
if #users
#users = current_user.except_current_user(#users)
render partial: 'friends/lookup'
else
render status: :not_found, nothing: true
end
end
private
def require_same_user
if current_user != set_user
flash[:alert] = "Restricted."
redirect_to root_path
end
end
def set_user
#user = User.find(params[:id])
end
end
Friendship model:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
has_many :shipments
end
Friendships controller:
class FriendshipsController < ApplicationController
def index
#friendships = Friendship.all
end
def create
#friendship = current_user.friendships.build(:friend_id => params[:friend_id])
if #friendship.save
flash[:success] = "Added to friends."
redirect_to my_friends_path
else
flash[:alert] = "Impossible to add as a friend."
redirect_to my_friends_path
end
end
def destroy
#friendship = current_user.friendships.find_by(friend_id: params[:id])
#friendship.destroy
flash[:notice] = "Unfriended."
redirect_to my_friends_path
end
private
def name
#name = friend_id.name
end
end
Schema:
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "shipments", force: :cascade do |t|
t.string "name"
t.integer "length"
t.integer "width"
t.text "adress"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.integer "height"
t.integer "kg"
end
add_index "shipments", ["user_id"], name: "index_shipments_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.integer "phone", limit: 30
t.string "username"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
Shipment form view (new):
<%= form_for(shipment, html: { multipart: true }) do |f| %>
<p>Choose a friend from your friendlist or fill the address field manually:</p>
<%= f.select :friend_id, user.friendships.map{ |friendship|
[friendship.friend.name, friendship.id] } %>
<%= f.text_field :adress, placeholder: "Address and index" %>
<%= f.submit "Submit", class: "button" %>
<% end %>
With ActiveRecord::Base, you could use eager loading and nested form to solve your problem.
Eager load the object related to the main object and use nested form to display the related object.
I am newbie to rails ,
I am using devise from ryan bates video tutorial , and , i am stuck at one point
I have created user , role relationship
and in the sign up page , i need to provide select option group for existing roles ,
in my sign up view page i am writing
<%= collection_select(:user,:roles,Role.find(:all),:id,:name) %>
i dont precisely understand collection_select method , kindly help what i might be doing wrong
my models 1 : user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me , :roles
has_and_belongs_to_many :roles
def role?(role)
return !!self.roles.find_by_name(role.to_s.camelize)
end
end
my model 2 : role.rb
class Role < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
my user migration file
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
end
end
my role migration file
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name
t.timestamps
end
end
end
my join table migration file
class UsersHaveAndBelongToManyRoles < ActiveRecord::Migration
def up
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
end
def down
drop_table :roles_users
end
end
the ERROR COMING is
undefined method `each' for "2":String
2 being the id of the role selected
In a has_and_belongs_to_many relationship like you have between User and Role, there is no role_id on the User object.
The second parameter of the collection_select is the attribute you're updating with the selection(s), in your case it's not role_id, it's role_ids and seeing as it's a has_and_belongs_to_many relationship you probably want to allow the user to select multiple options, so try something like this:
<%= collection_select(:user, :role_ids, Role.all, :id, :name, {}, { selected: #user.role_ids, multiple: true }) %>
If you attach it to a form_for on your #user object you can use:
<%= f.collection_select(:role_ids, Role.all, :id, :name, {}, multiple: true) %>