I have my app schema defaulting the User.role attribute to "new". My goal for my before_save callback is for the User model to change the user.role attribute to "admin" if the user being created is the first one, otherwise it leaves it alone.
User.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
ROLES = %w[admin moderator member new]
after_save :assign_role
def assign_role
if self == User.first
self.role = "admin"
self.touch
end
end
def is?(requested_role)
self.role == requested_role.to_s
end
end
Rails console
vagrant#vagrant-ubuntu-trusty-64:/vagrant$ rails c
Loading development environment (Rails 4.2.1)
2.1.5 :001 > User.any?
(0.3ms) SELECT COUNT(*) FROM "users"
=> false
2.1.5 :002 > User.last
User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC
LIMIT 1
=> #<User id: 1, email: "**************", encrypted_password: "$
2a$10$FEOjKX1UAZwiEVPbXXW.TOCyh2d7iwIuIRKn8YmYWk....", reset_password_token: nil
, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, curre
nt_sign_in_at: "2015-08-20 14:23:31", last_sign_in_at: "2015-08-20 14:23:31", cu
rrent_sign_in_ip: #<IPAddr: IPv4:10.0.2.2/255.255.255.255>, last_sign_in_ip: #<I
PAddr: IPv4:10.0.2.2/255.255.255.255>, created_at: "2015-08-20 14:23:31", update
d_at: "2015-08-20 14:23:31", confirmation_token: nil, confirmed_at: nil, confirm
ation_sent_at: nil, role: "new">
2.1.5 :003 > User.last
User Load (1.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC
LIMIT 1
=> #<User id: 2, email: "***********.com", encrypted_password: "$2a$10$OtjtlW
BAcrqlws1q5N/ICe6Hjytb0GhKGrVmdcin.Op...", reset_password_token: nil, reset_pass
word_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_a
t: "2015-08-20 14:24:44", last_sign_in_at: "2015-08-20 14:24:44", current_sign_i
n_ip: #<IPAddr: IPv4:10.0.2.2/255.255.255.255>, last_sign_in_ip: #<IPAddr: IPv4:
10.0.2.2/255.255.255.255>, created_at: "2015-08-20 14:24:44", updated_at: "2015-
08-20 14:24:44", confirmation_token: nil, confirmed_at: nil, confirmation_sent_a
t: nil, role: "new">
2.1.5 :004 >
The first user created in the console should have role:"admin" instead of role:"new"
EDIT:
save new user form
<div class="border-form-div">
<h2 class="text-center">Create a new account</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f.email_field :email, :autofocus => true, :placeholder => 'Email address' %>
<%= f.hidden_field :role, :value => "new" %>
<%= f.password_field :password, :placeholder => 'Password' %>
<%= f.password_field :password_confirmation, :placeholder => 'Password confirmation' %>
<%= f.submit "Sign up", :class => 'btn btn-primary center-block' %>
<% end %>
</div>
user_controller.rb
class UserController < ApplicationController
before_action :authenticate_user!
params.require(:user).permit(:username, :email, :password, :encrypted_password, :role)
end
The controller is fairly empty because I'm using devise to handle all the account management
before_save {|user| user.role = "admin"}, unless: :user_exists?
def user_exists?
User.any?
end
Related
Goal
I would like to let a user.admin invite a user to only one of its hotels with the devise_invite gem.
=> Users and Hotels are connected via a Join_table UserHotel.
Issue
I am not able to create the user_hotel Join_table and/ord add the specific hotel params to the invited user.
controller
>> #user.hotels => #<ActiveRecord::Associations::CollectionProxy []>
console:
pry(main)> User.invitation_not_accepted.last.hotels
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."invitation_token" IS NOT NULL AND "users"."invitation_accepted_at" IS NULL ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
Hotel Load (0.4ms) SELECT "hotels".* FROM "hotels" INNER JOIN "user_hotels" ON "hotels"."id" = "user_hotels"."hotel_id" WHERE "user_hotels"."user_id" = $1 [["user_id", 49]]
=> []
UPDATE
The issue seems to be in the many-to-many relationship between user and hotel. When I break my controller 'new' action after hotel.user.new and test it I het the following:
>> #user.hotels => #<ActiveRecord::Associations::CollectionProxy []>
>> #hotel.users => #<ActiveRecord::Associations::CollectionProxy [#<User id: 2, email: "test#hotmail.com", created_at: "2019-11-05 14:17:46", updated_at: "2019-11-05 15:04:22", role: "admin">, #<User id: nil, email: "", created_at: nil, updated_at: nil, role: "admin">]>
Note
I set up users with devise, such that my users controller is build up as:
users/confirmations_controller.rb
users/invitations_controller.rb
users/omniauth_callbacks_controller.rb
users/password_controller.rb
users/registrations_controller.rb
users/sessions_controller.rb
users/unlocks_controller.rb
Code
routes
Rails.application.routes.draw do
devise_for :users
resources :hotels do
devise_for :users, :controllers => { :invitations => 'users/invitations' }
end
end
models
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
has_many :user_hotels, dependent: :destroy
has_many :hotels, through: :user_hotels
accepts_nested_attributes_for :user_hotels
enum role: [:owner, :admin, :employee]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
class UserHotel < ApplicationRecord
belongs_to :hotel
belongs_to :user
end
class Hotel < ApplicationRecord
has_many :user_hotels, dependent: :destroy
has_many :users, through: :user_hotels
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
views/hotels/show
<%= link_to "invite new user", new_user_hotel_invitation_path(#hotel)%>
controllers/users/invitations_controller.rb
class Users::InvitationsController < Devise::InvitationsController
def new
#hotel = Hotel.find(params[:hotel_id])
#user = #hotel.users.new
end
def create
#hotel = Hotel.find(params[:hotel_id])
#user = #hotel.users.new(hotel_user_params)
#user.invite!
end
private
def hotel_user_params
params.require(:user).permit(:email, :role,
hotel_users_attributes: [:hotel_id])
end
end
views/invitations/new.html.erb
<h2><%= t "devise.invitations.new.header" %></h2>
<%= simple_form_for(resource, as: resource_name, url: user_hotel_invitation_path(#hotel), html: { method: :post }) do |f| %> <%= f.error_notification %>
<% resource.class.invite_key_fields.each do |field| -%>
<div class="form-inputs">
<%= f.input field %>
</div>
<% end -%>
<%= f.input :role, collection: [:owner, :admin, :employee] %>
<div class="form-actions">
<%= f.button :submit, t("devise.invitations.new.submit_button") %>
</div>
<% end %>
I set my user model up to build the user_profile before being created. The user_profile itself is being shown in the console when I load the user object. #user = User.find(1) -> #user.user_profile -> UserProfile Load. The relationship works correctly. The issue is I am unable to make changes to the user profile with the edit user_profile action. The edit page is empty, even though the attributes for the user_profile exist. How can I get the form to load in the edit view, so that the user is able to make changes to his/her profile after logging into their account? Side note: The user does not edit their normal profile during registration.
user.rb
class User < ApplicationRecord
# Include default devise modules.
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable
include DeviseTokenAuth::Concerns::User
before_create :build_user_profile
extend FriendlyId
after_validation :geocode
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
friendly_id :username, use: :slugged
has_one :user_profile
accepts_nested_attributes_for :user_profile
protected
def confirmation_required?
false
end
end
user_profile.rb
class UserProfile < ApplicationRecord
mount_uploader :avatar, UserAvatarUploader
belongs_to :user, :dependent => :destroy
validates_integrity_of :avatar
validates_processing_of :avatar
end
_form.html.erb
<%= simple_form_for(#user_profile, :html => {:multipart => true}) do |f| %>
<%= f.error_notification %>
<div class="form-group">
<% f.simple_fields_for :user_profile do |user_profile| %>
<div class="col-md-4">
<% if current_user.user_profile.avatar.url.present? %>
<%= image_tag(current_user.user_profile.avatar.url) %>
<%= user_profile.label :remove_avatar do %>
<%= user_profile.input :remove_avatar, as: :boolean, checked_value: true, unchecked_value: false %>
<% end %>
<%= user_profile.input :avatar, as: :file, class: 'form-control' %>
<%= user_profile.input :birthday, label: false, id: 'datepicker', class: 'form-control' %>
<% end %>
<div class="form-group">
<div class="col-md-4">
<%= f.button :submit %>
</div>
</div>
<% end %>
<% end %>
schema.rb
create_table "user_profiles", force: :cascade do |t|
t.date "birthday"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar"
t.index ["user_id"], name: "index_user_profiles_on_user_id"
end
user_profiles_controller.rb
class UserProfilesController < ApplicationController
#before_action :set_user_profile, only: [:show, :edit, :update]
before_action :authenticate_user!
before_action :load_profile, only: [:edit, :update]
def show
end
# GET /user_profiles/1/edit
def edit
end
# PATCH/PUT /user_profiles/1
# PATCH/PUT /user_profiles/1.json
def update
respond_to do |format|
if #user_profile.update(user_profile_params)
format.html {redirect_to #user_profile, notice: 'User profile was successfully updated.'}
format.json {render :show, status: :ok, location: #user_profile}
else
format.html {render :edit}
format.json {render json: #user_profile.errors, status: :unprocessable_entity}
end
end
end
private
def load_profile
#user_profile = current_user.user_profile
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_profile_params
params.require(:user).permit(user_profile: [:birthday, :avatar, :user_id])
end
end
Rails Console
#user = User.find(5)
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
#<User id: 5, email: "colejohn#hotmail.com", username: "johnny", first_name: "John", last_name: "Woo">
>> #user.user_profile
UserProfile Load (2.0ms) SELECT "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = $1 LIMIT $2 [["user_id", 5], ["LIMIT", 1]]
#<UserProfile id: 2, birthday: nil, user_id: 5, created_at: "2017-08-17 20:47:12", updated_at: "2017-08-17 20:47:12", avatar: nil>
The edit page is empty, even though the attributes for the
user_profile exist.
This line
<% f.simple_fields_for :user_profile do |user_profile| %>
should be
<%= f.simple_fields_for :user_profile do |user_profile| %>
You are missing =, which is the reason for the form being empty.
Misplaced if statement termination <% end %> in simple_form
<% if current_user.user_profile.avatar.url.present? %>
<%= image_tag(current_user.user_profile.avatar.url) %>
<%= user_profile.label :remove_avatar do %>
<%= user_profile.input :remove_avatar, as: :boolean, checked_value: true, unchecked_value: false %>
<% end %>
<% end %>
I am using Devise Invitable in my rails 4 app, I have some extra fields that the user needs to fill out when they set their password.
I have created the invitation controller and added the extra fields to the update_sanitized_params method in the controller. When I fill out the form the server out put gives me:
Started PUT "/users/invitation" for 127.0.0.1 at 2016-10-11 12:57:34 -0600
Processing by InvitationsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"c0c75noldd9hB6pHjLh1A74KWsWGrIc/K4s7PUJkmtnCG9Uz3YMEJMiBKSpC8Fk+ObC67oBx6E5AxS0R/xZlUA==", "user"=>{"f_name"=>"Steve", "l_name"=>"Jenkinson", "date_of_birth"=>"1975-01-01", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Accept Invitation"}
Account Load (1.0ms) SELECT "public"."accounts".* FROM "public"."accounts" WHERE "public"."accounts"."subdomain" = $1 LIMIT $2 [["subdomain", "10-106security"], ["LIMIT", 1]]
Rendering devise/invitations/edit.html.erb within layouts/application
Rendered devise/invitations/edit.html.erb within layouts/application (2.9ms)
Rendered shared/_signed_out_nav.html.erb (1.4ms)
Completed 200 OK in 60ms (Views: 53.1ms | ActiveRecord: 2.0ms)
However the user attributes are not being saved. This was verified in rails console.
Console Output after submitting the Invitation Edit form:
User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 3, email: "test1#test.com", f_name: nil, l_name: nil, date_of_birth: nil, employee_number: nil, system_id: nil, created_at: "2016-10-11 19:33:33", updated_at: "2016-10-11 19:33:33", invitation_token: "d7024926f142aeeec1d23781f1832f74317b592f5bcdd5e6a3...", invitation_created_at: "2016-10-11 19:33:33", invitation_sent_at: "2016-10-11 19:33:33", invitation_accepted_at: nil, invitation_limit: nil, invited_by_type: "User", invited_by_id: 1, invitations_count: 0>
my application controller:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# Before Actions
before_filter :configure_permitted_parameters, if: :devise_controller?
before_action :load_schema, :authenticate_user!, :set_mailer_host
# Custom Methods
private
def load_schema
Apartment::Tenant.switch!('public')
return unless request.subdomain.present?
if current_account
Apartment::Tenant.switch!(current_account.subdomain)
else
redirect_to root_url(subdomain: false)
end
end
def current_account
#current_account ||= Account.find_by(subdomain: request.subdomain)
end
helper_method :current_account
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
end
def set_mailer_host
subdomain = current_account ? "#{current_account.subdomain}." : ""
ActionMailer::Base.default_url_options[:host] = "#{subdomain}lvh.me:3000"
end
def after_invite_path_for(resource)
users_path
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:f_name, :l_name, :date_of_birth, :password, :password_confirmation, :invitation_token])
end
end
and here is the form:
<%= bootstrap_form_for resource, :as => resource_name, :url => invitation_path(resource_name), method: :put do |f| %>
<%= f.text_field :f_name, label: 'First Name' %>
<%= f.text_field :l_name, label: 'Last Name' %>
<%= f.date_field :date_of_birth, label: 'Date Of Birth' %>
<%= f.password_field :password, label: 'Password' %>
<%= f.password_field :password_confirmation, label: 'Confirm Password' %>
<%= f.submit "Accept Invitation", :class => 'btn btn-primary' %>
<% end %>
here is the User model:
class User < ApplicationRecord
# Before Actions
# Devise Modules
devise :invitable, :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable, :timeoutable
# Relationships
# Validations
validates :f_name, presence: true
validates :l_name, presence: true
validates :date_of_birth, presence: true
# Custom Methods
def full_name
l_name.upcase + ", " + f_name
end
end
any assistance here would be greatly appreciated!
the problem here was that upon editing the devise_invitable form the user would fill out, I did not add the authentication token.
I've got a user signup form with standard stuff. It was working fine, but now it's totally broken, and the error messages are completely unhelpful. I hopped into this existing app several weeks ago. And even if I checkout a commit from before I put ANY code into this project, the form still doesn't work. By 'not work', I mean it throws errors as if the forms are completely blank ('Please enter an e-mail', 'Please enter a password'), when they most certainly were not blank. Here's the relevant code...
MODEL:
include FormsHelper
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :gender, :time_zone, :state,
:birthday, :oauth_id, :oauth_provider_display_name,
:admin, :vagrant, :favorite_shoe, :race_manager, :auto_submission,
:country
CONTROLLER:
def create
build_resource
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
Rails.logger.info(resource.errors.inspect)
flash.now[:error] = I18n.t('activerecord.template.body')
respond_with resource
end
end
VIEW
<%= f.input :email, :required => true,
:autofocus => true,
:input_html => { :autocomplete => 'off' },
:as => 'email' %>
<% if resource.oauth_id.present? %>
<%#
Check if we're coming here from an OAuth-typ registration. The value here doesn't really matter
because it'l be auto-generated again on create. This is just for form validation purposes.
%>
<%= f.hidden_field :password %>
<%= f.hidden_field :password_confirmation,
:value => f.object.password %>
<% else %>
<%= f.input :password,
:required => true,
:hint => controller.controller_name == 'account' ? "Leave blank if you don't want to update your password." : nil,
:input_html => { :value => f.object.password, :autocomplete => 'off' } %>
<%= f.input :password_confirmation,
:label => "Confirm password",
:required => true,
:input_html => { :value => f.object.password_confirmation, :autocomplete => 'off' } %>
<% end %>
<%= f.input :first_name, :required => true %>
<%= f.input :last_name, :required => true %>
Here's the Log output
Started POST "/users" for 127.0.0.1 at 2013-12-31 16:20:43 -0800
Processing by RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"IR6YzpsgwrfLwP0snZ7l1c6RTjhtv/tOtDtHp/I1Agc=", "user"=>{"oauth_id"=>"", "oauth_provider_display_name"=>"", "email"=>"blakester99999#gmail.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "first_name"=>"Hey", "last_name"=>"There", "country"=>"United States", "state"=>"CA", "gender"=>"m", "birthday(2i)"=>"1", "birthday(3i)"=>"1", "birthday(1i)"=>"2003", "time_zone"=>"Eastern Time (US & Canada)", "favorite_shoe"=>"Asics"}, "commit"=>"Complete Registration"}
(0.2ms) BEGIN
(0.3ms) ROLLBACK
#<ActiveModel::Errors:0x007ffdaf5babd0 #base=#<User id: nil, email: "", encrypted_password: "", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: nil, updated_at: nil, admin: false, first_name: nil, last_name: nil, gender: nil, state: nil, birthday: nil, time_zone: nil, vagrant: nil, favorite_shoe: nil, shipping_address_id: nil, race_manager: false, auto_submission: true, confirmation_token: nil, confirmed_at: nil, confirmation_sent_at: nil, unconfirmed_email: nil, referral_credit: #<BigDecimal:7ffdaf4ff920,'0.0',9(18)>, country: nil>, #messages={:email=>["Enter your email address."], :password=>["Enter a password."]}>
What's confusing here is that the params clearly show up in the correct place. But then it just rolls back, and the model has nothing on it. I've even turned off all validations, and I still get back errors saying the email and password need to be filled out (when they clearly have been, as shown in the params).
Any ideas are much appreciated! This one's driving me bonkers. Thanks!
UPDATE: From the users suggestions, I checked on gem issues. I rolled back to commits prior to me doing anything, and ran bundle install. The only change was an update to 'Paperclip' from 3.0 -> 3.5.2. This didn't fix anything though. Same problem as before. With the validations back in however, it did produce this log output...
(0.3ms) BEGIN
User Exists (0.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY '' LIMIT 1
(0.7ms) ROLLBACK
Notice that the email is BINARY ''. Normally the actual email address should show up where the '' are. And again, the params are definitely showing up in the logs exactly as typed. Yet, it appears when it goes to do the SQL checks, the fields are all empty. Any ideas?
Taking a wild guess that you're using Devise, since the controller code is nearly identical to Devise::RegistrationsController. (current code here)
Nearly - except for the part where build_resource is passed the incoming parameters.
You may want to check the Devise version that a working copy of the app (other devs? production?) is using, as some older versions had a build_resource method that didn't require an argument.
I have isolated the problem in the second block of code below (if you don't want all the details): I can create new users from the account model. I can't assign those users roles from the accounts model. I am using fields_for, this method does not work when I attempt to assign the role_ids to the roles model. My db is set up in the following way:
Account model has_many :users
User model has_and_belongs_to_many :roles,
belongs_to :accounts
Roles model has_and_belongs_to_many :users
views/accounts/new.html.erb
<% for user in #account.users %>
<% fields_for "account[user_attributes][]", user do |account_fields| %>
<p>Login : <%= account_fields.text_field :login %>
<p>Email : <%= account_fields.text_field :email %>
<p>Password : <%= account_fields.password_field :password %>
<p>Confirm Password : <%= account_fields.password_field :password_confirmation %>
<%= account_fields.hidden_field :account_id, :value => :id %>
<% end %>
<% end %>
<% for role in #account.users.first.roles %>
<% fields_for "account[role_attributes]]", role do |role_fields| %>
<%= role_fields.hidden_field :role_ids, :value => '[1]' %>
<% end %>
<% end%>
Associated setter methods in the account.rb model: using the stacktrace I have isolated the problem to "undefined method `roles' for #" on line 34, marked below:
def user_attributes=(user_attributes)
user_attributes.each do |attributes|
users.build(attributes)
end
end
def role_attributes=(role_attributes)
role_attributes.each do |attributes|
users.roles.build(attributes) #error here (stacktrace)
end
end
finally, within the accounts controller I build the users and roles in memory:
def new
#account = Account.new
1.times { #account.users.build }
1.times { #account.users.first.roles.build }
The #accounts.users.first.roles.build proof from consol:
>> account
=> #<Account id: 1, subdomain: "justinzollars", created_at: "2010-02-08 14:41:13", updated_at: "2010-02-08 14:41:13">
>> account.users
=> [#<User id: 13, login: "jayz", email: "jz#jz.com", crypted_password: "f9a3d618fc650d285a90f9775508c13784891b97", salt: "f497a7dd909b695caff1f6310e710245615d55b6", created_at: "2010-02-08 20:25:48", updated_at: "2010-02-08 20:25:48", remember_token: nil, remember_token_expires_at: nil, account_id: 1>, #<User id: 16, login: "jasonwade23", email: "jasonwade#gmail.com", crypted_password: "06581b47cfac7a529773d61dc0b1d5d6c0da6c08", salt: "93f8b99cd9da60b904d553fcc7843bfb66352c3e", created_at: "2010-02-13 07:46:14", updated_at: "2010-02-13 07:46:14", remember_token: nil, remember_token_expires_at: nil, account_id: 1>]
>> account.users.first
=> #<User id: 13, login: "jayz", email: "jz#jz.com", crypted_password: "f9a3d618fc650d285a90f9775508c13784891b97", salt: "f497a7dd909b695caff1f6310e710245615d55b6", created_at: "2010-02-08 20:25:48", updated_at: "2010-02-08 20:25:48", remember_token: nil, remember_token_expires_at: nil, account_id: 1>
>> account.users.first.roles
=> [#<Role id: 1, name: "admin">, #<Role id: 2, name: "alt">]
>>
You should use accepts_nested_attributes_for, so in models:
# Account model
accepts_nested_attributes_for :users
# User model
accepts_nested_attributes_for :roles
Then you should remove user_attributes= and role_attributes= methods.
Your form should look like this:
<% form_for #account do |f| %>
<% fields_for :users do |u| %>
... # user fields
<% fields_for :roles do |r| %>
... # role fields
<% end %>
<% end %>
<%= f.submit 'Save' %>
<% end %>
It will automaticaly iterate over all users associated with account and all roles associated with user.
For more details read here.
EDIT:
You can assign roles to user, in controller:
role = Roles.find(some_id)
#user.roles << role
or
#user.role_ids = [2, 4, 6]
However I'm not sure how to add roles with has_and_belongs_to_many association. You are using #user.roles.build method which will create new role and associate it with user. If you want to add a role you should do it with one of two examples above. But how to create a from for it? I don't know. I would add a model for users_roles table and add to user model:
has_many :users_roles
accepts_nested_attributes_for :users_roles # you should add here also some :reject_if
Then insted of fields_for :roles I would add in form:
<% fields_for :users_roles do |ur| %>
<%= ur.select :role_id, Role.all.collect {|r| [r.name, r.id] }, {:include_blank => true} %>
<% end %>
Then in your controller you should add something like 3.times { #user.users_roles.build }. If you want to have nice "Add" and "Remove" links that create for you new role with javascript, take a look here - it is really nice example how to handle it.