I'm trying to set up a Rails 3 app to handle user roles with Devise and CanCan.
My relationships are as follows
class User < ActiveRecord::Base
has_many :users_roles
has_many :roles, :through => :users_roles
end
class Role < ActiveRecord::Base
has_many :users_roles
has_many :users, :through => :users_roles
end
class UsersRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
Actually everything is working fine. Devise is handling the authentication perfectly. CanCan is restricting user behaviour based on ability.rb.
But I have a ridiculous problem with setting a UsersRole.
I have defined checkboxes on the User edit page like so.
<% for role in Role.all %>
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%=h role.name.camelize %>
<% end %>
<%= hidden_field_tag "user[role_ids][]", "" %>
If I create a UserRole via the console, then these checkboxes are checked according to the users role.
But I cannot set or change roles using these checkboxes!
I've been all around the houses with this — variations of syntax, switched to a HABTM and roles_mask approach, rebuilt my models and controllers several times — all to no effect.
Actually the title of my question is not entirely correct - the checkboxes are putting
_method put
authenticity_token XGl6s1iyXJfahdgftc3df8q1ZeehMVzs3LxiQH98jGw=
commit Update
user[current_password] password
user[email] user#example.com
user[name] User Name
user[password]
user[password_confirmatio...
user[role_ids][] 1
user[role_ids][] 4
user[role_ids][]
utf8 ✓
But these values are not being set in the database.
What am I doing wrong??!!!
My guess is that you have specified attr_accesible in your User model and that role_ids is not listed there (and it should not be)
If that is combined with an update_attributes call in your Controller then role_ids will never be set properly.
If this is the case then you should manually be able to set the role_ids in your Controller like this before you update or save:
#user.role_ids = params[:user][:role_ids]
Of course, I'm not certain this is the case since you did not include your controller code or any details with the User model.
Edit:
Instead if doing the #user.update_attributes in the Controller, you could change it to the following instead:
#The same as update_attributes, but without the save
#user.attributes = params[:user]
# Here the role_ids get assigned manually so it does not need to be in attr_accesible
#user.role_ids = params[:user][:role_ids] if params[:user]
# And here it gets checked if the save was successfull
if #user.save
render :action => :show
else
render :action => :edit
end
Related
I have users and each user has addresses and each address belong to a case
how can i add the result of case new to show in case show
in case new
<%= form_for #case,:url => {:action => :create, :id => #user.id} do |f| %>
Address:<br>
<%= f.select :address_ids, options_for_select(#user.addresses.all.pluck(:name,:id)) %><br><br>
<% end %>
in case show (this gives me all the addresses for the user not the address that I choose from case new.html.erb
<strong>Address: </strong><% #user.addresses.each do |s| %>
<%= s.name %>
<% end %>
case model
class Case < ActiveRecord::Base
belongs_to :user
has_many :addresses, dependent: :destroy
in user model
class User < ActiveRecord::Base
has_many :cases
has_many :addresses, dependent: :destroy
in address model
class Address < ActiveRecord::Base
belongs_to :user
belongs_to :case
end
It retrieves all the addresses because you query the address from the user himself not from the case object that you supposedly created from the new view.
So, first, I would add create action to create the case first (to which we will assign the chosen address):
def create
# #user can be retrieved from the passed id in the url
#user = User.last
#case = Case.create(user: #user)
#case.addresses << Address.where(id: params.require(:case)[:address_ids])
end
and change your controller show action to something like this:
def show
# #case can be retrieved any way you like, this is just an example
#case = Case.last
end
and make your show view like this:
<strong>Address: </strong>
<% #case.addresses.each do |s| %>
<%= s.name %>
<% end %>
This would solve your issue.
Some notes to consider later:
So far this new view only allows you to choose one address (not multiple) so it won't be needed to use has_many association (if you want to use it, then you have to figure out a way to choose multiple addresses).
The #user.id can be passed through the url instead of explicitly defining it in the form. Look at Nested Resources for more info.
The url attribute in the form can be replaced by _path helpers, the same above link would give you a hint as well.
Try to stick to Ruby styling, like indentation and naming. For example, the |s| is not descriptive .. would be better to be named address or addr
As I can see, you can try using through instead of duplicating the associations. E.g. Case belongs_to User & Case has_many Addresses, so User can has_many cases & User has_many addresses, through: :cases .. like this you can retrieve user's addresses specific to that case. Like this, when you try to create an address and retrieve it from #user.addresses, you will get an error because an address already needs a user and a case to be created in the first place, so the associations are not set perfectly and as a result you will need workarounds to prevent these errors.
I have a one-to-one models for user and profile.
The following is the code for the User model created by device.
class User < ActiveRecord::Base
has_one :profile , dependent: :destroy
end
Code for Profile model.
class Profile < ActiveRecord::Base
belongs_to :user
end
Now I have basic Profile for a User which was created at sign up. Now how do I go about editing user profile using the helper device provides current_user with form_for?
We did something like this:
#app/models/user.rb
Class Model < ActiveRecord::Base
before_create :build_profile
end
This populates the profile record in the database, allowing you to update it later on
Once you've saved the user, you will be best using accepts_nested_attributes_for to populate & save profile options through a form for the user, like this:
#config/routes.rb
resources :users do
resources :profiles
end
#app/views/profiles/edit.html.erb
<%= form_for #profile do |f| %>
<%= f.hidden_field :user_id, current_user.id %>
<%= f.text_field :your_profile_options %>
<% end %>
This is a very basic way to do it, but hopefully will give you more ideas. If you want to see this in the wild, check out http://video-conference-demo.herokuapp.com (it uses Devise to handle users & has extra "profile" model too)
I've read through many other topics here (1, 2, 3...) but none really solved my problem.
Here are my 3 models.
User
has_many :memberships
has_many :accounts, :through => :memberships
accepts_nested_attributes_for :memberships
end
Account
has_many :memberships
has_many :users, :through => :memberships
accepts_nested_attributes_for :memberships
end
Membership
attr_accessible :account_id, :url, :user_id
belongs_to :account
belongs_to :user
end
As you can see, my join model Membership has an additional attribute: :url.
In my Accounts table, I store names of online services, such as GitHub, Stack Overflow, Twitter, Facebook, LinkedIn.. I have 9 in total. It's a fixed amount of accounts that I don't tend to update very often.
In my User form, I'd like to create this:
The value entered in any of these field should be submitted in the Memberships table only, using 3 values:
url (the value entered in the text field)
user_id (the id of the current user form)
account_id (the id of the related account, e.g. LinkedIn is '5')
I have tried 3 options. They all work but only partially.
Option #1
<% for account in #accounts %>
<%= f.fields_for :memberships do |m| %>
<div class="field">
<%= m.label account.name %><br>
<%= m.text_field :url %>
</div>
<% end %>
<% end %>
I want to have 9 text field, one for each account. So I loop through my accounts, and create a url field related to my memberships model.
It shows my fields correctly on the first time, but the next time it'll display 81 fields:
Option #2
<% #accounts.each do |account| %>
<p>
<%= label_tag(account.name) %><br>
<%= text_field_tag("user[memberships_attributes][][url]") %>
<%= hidden_field_tag("user[memberships_attributes][][account_id]", account.id) %>
<%= hidden_field_tag("user[memberships_attributes][][user_id]", #user.id) %>
</p>
<% end %>
I'm trying to manually enter the 3 values in each column of my Memberships tables.
It works but :
displaying both account and user id's doesn't seem very secure (no?)
it will reset the fields everytime I edit my user
it will duplicate the values on each submit
Option #3 (best one yet)
<%= f.fields_for :memberships do |m| %>
<div class="field">
<%= m.label m.object.account.name %><br>
<%= m.text_field :url %>
</div>
<% end %>
I'm creating a nested form in my User form, for my Membership model.
It works almost perfectly:
exactly 9 fields, one for each account
no duplicates
But, it only works if my Memberships table is already populated! (Using Option #2 for example).
So I tried building some instances using the UsersController:
if (#user.memberships.empty?)
#user.memberships.build
end
But I still get this error for my m.label m.object.account.name line.
undefined method `name' for nil:NilClass
Anyway, I'm probably missing something here about has_many through models. I've managed to create has_and_belongs_to_many associations but here, I want to work on that join model (Membership), through the first model (User), using information about the third model (Account).
I'd appreciate your help. Thank you.
in the controller, fetch the list of memberships for a particular user
# controller
# no need to make this an instance variable since you're using fields_for in the view
# and we're building additional memberships later
memberships = #user.memberships
then loop through each account and build a membership if the user has no membership for an account yet.
# still in the controller
Account.find_each do |account|
unless memberships.detect { |m| m.account_id == account.id }
#user.memberships.build account_id: account.id
end
end
then in your view, you change nothing :)
I would use the following data-design approach. All users in your system should have the
memebership entries for all possible accounts. The active configurations will have a value for the url field.
User
has_many :memberships
has_many :accounts, :through => :memberships
has_many :active_accounts, :through => :memberships,
:source => :account, :conditions => "memberships.url IS NOT NULL"
accepts_nested_attributes_for :memberships
end
Now
curent_user.active_accounts # will return the accounts with configuration
curent_user.accounts # will return all possible accounts
Add a before_filter to initialize all the memberships that a user can have.
class UsersController
before_filter :initialize_memberships, :only => [:new, :edit]
private
def initialize_memberships
accounts = if #user.accounts.present?
Account.where("id NOT IN (?)", #user.account_ids)
else
Account.scoped
end
accounts.each do |account|
#user.memberships.build(:account_id => account.id)
end
end
end
In this scenario you need to initialize the memeberships before the new action and all the memberships should
be saved in the create action ( even the ones without url).
Your edit action doesn't need to perform any additional data massaging.
Note:
I am suggesting this approach as it makes the management of the form/data straight forward. It should only
be used if the number of Account's being associated is handful.
Ok, am still a newbie in ruby on rails trying to learn my way around. I have two models (User model and Comment model). Basically a user has a simple profile with an 'about me' section and a photo's section on the same page. Users must be signed in to comment on other users profiles.
My User Model
class User < ActiveRecord::Base
attr_accessible :email, :name, :username, :gender, :password, :password_confirmation
has_secure_password
has_many :comments
.
.
end
My Comment Model
class Comment < ActiveRecord::Base
belongs_to :user
attr_accessible :content
.
.
end
In my comments table, I have a user_id column that stores the id of the user whose profile has been commented on and a commenter_id column that stores the id of the user commenting on the profile.
Comment Form
<%= form_for([#user, #user.comments.build]) do |f| %>
<%= f.text_area :content, cols: "45", rows: "3", class: "btn-block comment-box" %>
<%= f.submit "Comment", class: "btn" %>
<% end %>
My comments Controller
class CommentsController < ApplicationController
def create
#user = User.find(params[:user_id])
#comment = #user.comments.build(params[:comment])
#comment.commenter_id = current_user.id
if #comment.save
.........
else
.........
end
end
end
This works fine storing both user_id and commenter_id in the database. My problem comes when displaying the user comments on the show page. I want to get the name of the user who commented on a specific profile.
In my user controller
def show
#user = User.find(params[:id])
#comments = #user.comments
end
I want to get the name of the user from the commenter_id but it keeps throwing errors undefined method 'commenter' for #<Comment:0x007f32b8c37430> when I try something like comment.commenter.name. However, comment.user.name works fine but it doesn't return what I want. Am guessing am not getting the associations right.
I need help getting the correct associations in the models so as to get the name from the commenter_id.
My last question, how do I catch errors in the comments form? Its not the usual form_for(#user) where you do like #user.errors.any?.
routes.rb
resources :users do
resources :comments, only: [:create, :destroy]
end
Try something like this in your models
class User < ActiveRecord::Base
has_many :received_comments, :class_name => "Comment", :foreign_key => "user_id"
has_many :given_comments, :class_name => "Comment", :foreign_key => "commenter_id"
end
class Comment < ActiveRecord::Base
belongs_to :user # comment about profile
belongs_to :commenter, :class_name => "User", :foreign_key => "commenter_id"
end
check out: http://guides.rubyonrails.org/association_basics.html
you can probably come up with better naming on the has_many collections, received and given were the best I could do on short notice :)
Note: foreign_key is option in many cases, left it in above - i think it helps with clarity
has_many fk refers to the the column in the many table (other table)
belongs_to fk refers to the column in the many table (this table)
I'm having trouble with the following code:
User < AR
acts_as_authentic
belongs_to :owner, :polymorphic => true
end
Worker < AR
has_one :user, :as => :owner
accepts_nested_attributes_for :user
end
Employer < AR
has_one :user, :as => :owner
accepts_nested_attributes_for :user
end
I'd like to create registration forms based on user types, and to include authentication fields such as username and password. I currently do this:
UserRegistrationController < AC
#i.e. a new Employer
def new
#employer = Employer.new
#employer.build_user
end
...
end
I then include User fields with fields_for. All views render fine, but here's the catch: I cannot build a User, it tells me :password is a wrong method, so I guess the authentication logic has been bypassed.
What should I do? Am I doing it wrong altogether? Should I drop polymorphic associations in favor of Single Table Inheritance? Whatever I do, I have to make sure it plays nicely with Authlogic.
I'd approach the building of new users of either type in the opposite direction. ie:
#controller
#employer = Employer.new
#user = #employer.build_user
#view
form_for #user |f|
f.text_field :login
f.password_field :password
fields_for :owner, #employer |f_e|
f_e.some_field :some_value
#controller
def create
#owner = params[:owner][:some_employer_field_or_virtual_attribute] ? Employer.new params[:owner] : Worker.new params[:owner]
#owner.save
#user = User.new(params[:user].merge!(:owner => #owner)
if #user.save
...
re. mentioned virtual attribute - if there's no field in the model, and thus in the form, which distinguishes user type as employer or worker then set an virtual attribute within each which you can put as a hidden boolean field in the form