Association belongs_to vs nested attributes in the form - ruby-on-rails

I have a situation like below:
class User < ActiveRecord::Base
belongs_to :user_group
accepts_nested_attributes_for :user_group
end
class UserGroup < ActiveRecord::Base
has_many :users
end
Controller:
UsersController < ApplicationControler
def new
#user = User.new
#user.build_user_group
end
def create
#user = User.new(user_params)
if #user.save
# do something
else
# do something
end
end
private
def user_params
params.require(:user).permit(:email, :username, user_group_attributes: [:name])
end
end
Form:
= simple_form_for #user do |f|
= f.input :username
= f.simple_fields_for :user_group do |builder|
= builder.input :name, collection: UserGroup.all.map(&:name), prompt: "Choose one"
= f.input :email
= f.button :submit, 'Create', class: 'btn btn-success'
But it doesn't create an user with the association between the user and the user_group. UserGroup table is just a list of user groups, e.g. moderator, user and so on. So I need to select a group in the form and create a new user with association. What am I doing wrong? Do I need to find a group in create action and pass it as #user.user_group = the_chosen_group?
P.S.
Is it a proper name convention of UserGroup? Maybe should I call it as Group?
Regards.

Nested attributes should only be used when you want to allow editing of associated object via object itself. In short, every time you submit the form you created, rails will receive params like:
{ username: 'sth', user_group_attributes: { name: 'Group name' }
When you assign attributes like this, rails will create new attribute group, as it has no idea it is to search for such a group.
Since you only want to assign given user to usergrooup, you do not need nested_attributes at all. All you need is:
= simple_form_for #user do |f|
= f.input :username
= f.input :user_group_id, collection: UserGroup.all.pluck(:name, :id), prompt: 'Choose one'
= f.input :email
= f.button :submit, 'Create', class: 'btn btn-success'
And in the controller:
def user_params
params.require(:user).permit(:email, :username, :user_group_id)
end

Related

Rails nested fields when associating User to Roles model

I'm having some issues with the nested field here. I've used nested fields in other views/controllers without issue.
I'm trying to associate a role to the user table from the roles table.
My role model looks like this:
class Role < ApplicationRecord
has_many :users
end
My user model has this:
class User < ApplicationRecord
belongs_to :role, optional: true
accepts_nested_attributes_for :role
...
The reason why it's set to optional is because current users don't yet have a role, and I need to apply it to those first (there are only two users in production at the moment so that's fine)
My user controller is like this for the permitted attributes and update
class Admin::UsersController < ApplicationController
before_action :set_user, only: [:edit, :update, :destroy]
...
def edit
end
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to admin_users_url, notice: 'User Account was successfully updated.' }
else
format.html { render :edit }
end
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, roles_attributes: [:name])
end
def set_user
#user = User.find(params[:id])
end
end
And the form to update the user roles:
.container.p-4
%h1 Edit User Information
= form_for([:admin, #user]) do |f|
- if #user.errors.any?
#error_explanation
%h2
= pluralize(#user.errors.count, "error")
prohibited this event from being saved:
%ul
- #user.errors.each do |error|
%li= error.full_message
.row.mb-4
.col
= f.label :first_name, "First Name"
= f.text_field :first_name, class: "form-control border border-dark"
.col
= f.label :last_name, "Last Name"
= f.text_field :last_name, class: "form-control border border-dark"
.form-group.mb-4
= f.label :email, "Email Address"
= f.email_field :email, class: "form-control border border-dark"
%h2 User Role
.form-group.mb-4
= f.fields_for :roles do |f|
= f.check_box :name, checked: false, value: "admin"
= f.label :name, "Admin"
.form-group.p-4.text-center
= f.submit
When I hit update after checking "Admin", the terminal readout is that :roles is unpermitted.
I have a seperate Role controller that allows me to define the roles to associate users to. the Roles table only has name:string and user:references.
So I'm not sure why it's not being permitted.
What you actually want here is a join table to avoid denormalization:
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
accepts_nested_attributes_for :roles
end
class UserRole < ApplicationRecord
belongs_to :user
belongs_to :role
end
class Role < ApplicationRecord
validates :name, uniqueness: true
has_many :user_roles
has_many :users, through: :user_roles
end
This will let you assign multiple users to a role without duplicating the string "admin" for example for each row and risking the denormalization and bugs that can occur if one row for example contains "Admin" instead. You would assign roles from an existing list to users with:
<% form_for([:admin, #user]) do |form| %>
<div class="field">
<%= form.label :role_ids, 'Roles' %>
<%= form.collection_select(:role_ids, Role.all, :id, :name, multiple: true) %>
</div>
# ...
<% end %>
def user_params
params.require(:user)
.permit(
:first_name, :last_name, :email, :password,
role_ids: []
)
end
If you REALLY want to be able to create new roles on the fly while creating users you can use nested attributes. I would really just use AJAX instead through as it lets you handle the authorization logic in a seperate controller. You might want to consider that you might want to let some users assign roles but not invent new role definitions.
sticking with role ids and using bootstrap selectpicker the following worked as well and even preselects already set role
= f.select :role_ids, options_for_select(Role.all.map{|role| [role.name, role.id]}, #user.role_ids), {}, {:multiple => true, inlcude_blank: false, class: "form-control input-sm selectpicker"}
and controller:
module Backend
class UsersController < ApplicationController
before_action :set_user, only: %i[edit update]
def index
#users = User.all
end
def edit
#user.roles.build unless #user.roles.any?
end
def update
if #user.update user_params
redirect_to backend_users_path(#user), notice: 'Rollen erfolgreich aktualisiert'
else
render :edit
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:id, role_ids: [])
end
end
end

How to persist values of delegated fields in rails

I currently have the following models:
user.rb
class User < ApplicationRecord
has_one :profile, dependent: :destroy
before_create :build_profile
end
profile.rb
class Profile < ApplicationRecord
belongs_to :user
delegate :name, :email, :name=, :email=, to: :user
end
For the profile, I have the following controller:
profile_controller.rb
class ProfileController < ApplicationController
before_action :set_user
before_action :authenticate_user!
def edit
#user = current_user
#profile = #user.profile
end
def update
#user = current_user
#profile = #user.profile
if #profile.update(profile_params)
redirect_to profile_path(current_user.username)
else
render :edit
end
end
private
def profile_params
params.require(:profile).permit(:name, :email, :user_id, :bio, :avatar, :remove_avatar)
end
end
My "Edit Profile" form is as follows:
edit.html.haml
= simple_form_for #profile do |f|
= f.error_notification
.form-inputs
.row
.col-md-6.col-sm-12
= f.input :name, required: true, placeholder: "Name"
.col-md-6.col-sm-12
= f.input :email, required: true, placeholder: "Email"
= f.input :bio, required: true, hint: "Write a short bio about yourself", placeholder: "PHP Developer developing cool apps in Tokyo."
= f.input :avatar, as: :attachment, direct: true, presigned: true
.form-actions
= f.button :submit, "Update", class: "btn ban-info"
I am trying to change the delegated values in the profile form. However, they do not persist to the database. How do I go about doing this?
Thanks
Instead of delegate, which is normally reserved for exposing public methods that do not involve persistence, try adding the following line to your profile model:
accepts_nested_attributes_for :user #This will allow you to handle user attributes via a profile object
Also, in your update action you need to specify the relationship of profiles and users such as:
if #user.profile.update_attributes(profile_params)

Type error when creating record with strong parameters

I have the following controller action
def create
#user= user.new(user_params)
...
end
private
def user_params
params.require(:user).permit(:username, :device)
end
the user model holds a foreign key to the devices table as such
class user < ActiveRecord::Base
belongs_to :device
end
device has a has_many attribute on user also.
when I attempt to create a new user with form details
user[username] = 'test' and user[device] = 1
I get a type error ActiveRecord::AssociationTypeMismatch Device(#37560060) expected, got String(#15955200)
Use form_for in your new user template and pass the new #user object to it and also define input attributes like below
<%= form_for #user, create_user_path do |f| %>
<%= f.text_field :username %>
<%= f.text_field :device_id %>
<%= f.submit %>
<% end %>
and also change your strong parameter :device to :device_id as pragma told above
Change 'device' to 'device_id'
def user_params
params.require(:user).permit(:username, :device_id)
end
...
user[device_id] = 1

Multi-model nested form, can't add users to current account

I've searched everywhere for a solution but haven't come up with any.
The part that works: My app allows customers to create an account using a nested form. The data collected creates records in four models - accounts, users, accounts_users (because a user can be associated with many accounts), and profile (to store the user's fname, lname, phone, etc).
That part that doesn't work: Once logged in, I want the users to be able to add more users to their account using the form below. I don't receive any errors upon submit but I am brought back to the same form with no additional records created. Any help would be awesome!
Here is the nested form...
<%= form_for #user, :validate => true do |f| %>
<fieldset>
<%= f.fields_for :profile do |p| %>
<div class="field">
<%= p.label :first_name %>
<%= p.text_field :first_name %>
</div>
<div class="field">
<%= p.label :last_name %>
<%= p.text_field :last_name %>
</div>
<div class="field">
<%= p.label :phone %>
<%= p.text_field :phone %>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Create New User', :class => "btn btn-large btn-success" %>
<%= cancel %>
</div>
</fieldset>
The ApplicationController scopes everything to the current_account like so:
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain) if request.subdomain
end
The UsersController
def new
#user = User.new
#user.build_profile()
#current_account.accounts_users.build() #Edit2: This line was removed
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
def create
#user = User.new(params[:user])
#user.accounts_users.build(:account_id => current_account.id) #Edit2: This line was added
if #user.save
# Send Email and show 'success' message
flash[:success] = 'An email has been sent to the user'
else
# Render form again
render 'new'
end
end
Models look like this:
class Account < ActiveRecord::Base
attr_accessible :name, :subdomain, :users_attributes
has_many :accounts_users
has_many :users, :through => :accounts_users
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :profile_attributes
has_many :accounts_users
has_many :accounts, :through => :accounts_users
has_one :profile
accepts_nested_attributes_for :profile
end
class AccountsUser < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
class Profile < ActiveRecord::Base
belongs_to :user
attr_accessible :first_name, :last_name, :phone
end
Edit2: It turns out that I had required a password + password_comfirmation validation in the User model which prevented me from adding another user without these fields. I commented out these validations plus removed the line: current_account.accounts_users.build() in the 'new' action and added the line: #user.accounts_users.build(:account_id => current_account.id) in the 'create' action.
"I want the users to be able to add more users to their account using the form below." I assume you mean profiles (since your nested form is on profiles)?
If that's the case, I think your UsersController's create action isn't associating the profiles with users by using new.
Try this...
def new
#user = User.build
#profile = #user.profiles.build #build adds the profile to user's associated collection of profiles, but new doesn't
...
end
def create
#user = User.build(params[:user])
if #user.save
....
end
end
If you want the user to be associated with account, then you need to put the new and create actions in the AccountsController and do something similar to nest association of the users and profiles records.
Btw, the reason that it went back to new is because you render new at the end of the create, in case that's also part of the question. Hope that helps!

Extra variables in form - Rails

I think Im taking the wrong approach to this and have tried to find the best approach on the web but so far no luck.
I have a projects model, which has many messages and users. The messages belong to both projects and users (as displayed below). So I need to pass in both the project id and user id into the message form. I know this should be pretty straightforward, but Im obviously messing it up. Not sure at this stage wether using http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-hidden_field_tag is necessarily the best idea either.
Any help would be awesome.
Project Model:
class Project < ActiveRecord::Base
belongs_to :user
has_many :users
has_many :messages, :dependent => :destroy
end
User Model:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :first_name, :last_name, :username, :email, :password, :password_confirmation
has_many :projects
belongs_to :projects
has_many :messages
end
Message Model:
class Message < ActiveRecord::Base
attr_accessible :title, :message
belongs_to :project
validates :title, :presence => true
validates :message, :presence => true
end
Projects show:
def show
#project = Project.find(params[:id])
#title = #project.title
#curent_user = current_user
#message = Message.new
begin
#messages = #project.messages
rescue ActiveRecord::RecordNotFound
end
end
/shared/_message.html.erb
<%= form_for #message do |f| %>
<%= f.label :title %>:
<%= f.text_field :title %><br>
<%= f.label :message %>
<%= f.text_area :message %>
<%= f.submit %>
<% end %>
Message create action
def create
#message = #project.messages.build(params[:message])
if #message.save
flash[:success] = "Message created!"
redirect_to root_path
else
render 'pages/home'
end
end
Appreciate your time, just trying to identify how I transfer the user_id/project_id into the from field so it's passed in at message creation.
Set the project_id/user_id in the controller so they can't be modified by end users when submitting the forms.
As you're using #project.messages.build in the message controller create action the project_id should automatically be set.
You can then set the user with #message.user = #current_user

Resources