Rails nested fields when associating User to Roles model - ruby-on-rails

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

Related

Rails call custom validation before .new or .create

I make objects in controller's loop.
I need to check pet_name array before loop starts.
(because i got undefined method 'each' for nil:NilClass when
params[:pet_name].each do |pid|) runs)
But my validation always called after User.new or User.create.
I want to change to validate as when i push submit button and check validation, and redirects back when pet_name array is nil.
Ho can i change my code?
Controller
def create
user_name = params[:user_name]
params[:pet_name].each do |pid|
#user = User.new
#user.name = user_name
#user.pet_name = pid
render :new unless #user.save
end
redirect_to users_path
end
User.rb
class User < ApplicationRecord
has_many :pet
validates :name, presence: true
validates :pet_name, presence: true
validate :check_pet
def check_pet
if pet_name.nil?
errors.add(:pet_name, 'at least one pet id')
end
end
end
Prams structure
{ name: 'blabla', pet_name: ['blabla', 'blablabla', 'bla'] }
Sorry but that isn't even close to how you approach the problem in Rails.
If you want a user to have many pets and accept input for the pets when creating users you need to create a working assocation to a Pet model and have the User accept nested attributes:
class User < ApplicationRecord
has_many :pets # has_many assocations should always be plural!
validates :name, presence: true
validates :pets, presence: true
accepts_nested_attributes_for :pets
end
# rails g model pet name user:reference
class Pet < ApplicationRecord
belongs_to :user
validates :name, presence: true
end
class UsersController < ApplicationController
def new
#user = User.new(user_params)
3.times { #user.pets.new } # seeds the form with blank inputs
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user,
success: 'User created',
status: :created
else
render :new,
status: :unprocessable_entity
end
end
private
def user_params
params.require(:user)
.permit(:name, pets_attributes: [:name])
end
end
<%= form_with(model: #user) do |form| %>
<div class="field">
<%= form.label :name %>
<%= form.text_input :name %>
</div>
<fieldset>
<legend>Pets</legend>
<%= form.fields_for(:pets) do |pet_fields| %>
<div class="nested-fieldset">
<div class="field">
<%= pet_fields.label :name %>
<%= pet_fields.text_input :name %>
</div>
</div>
<% end %>
</fieldset>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
This is a pretty advanced topic and you should have your Rails CRUD basics well figured out before you attempt it. You should also consider if you instead want to use a separate controller to create the pets one by one as a nested resource after creating the user.

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)

Simple_form: simple_fields_for not showing the form

I'm trying to create two model in just one form. I have user and unit model both are associated with has_many :through association.
user.rb
class User < ActiveRecord::Base
has_many :units, :through => :user_unit_assocs
has_many :user_unit_assocs
end
unit.rb
class Unit < ActiveRecord::Base
has_many :users, :through => :user_unit_assocs
has_many :user_unit_assocs
end
users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new(user_params)
#user.units.build
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
else
format.html { render :new }
end
end
end
private
def user_params
params.require(:user).permit(:username, :email, units_attributes:[:unitname])
end
end
This is my form, users new
<%= simple_form_for(#user) do |f| %>
<%= f.simple_fields_for :units do |unit| %>
<%= unit.input :unitname, required: true %>
<% end %>
<%= f.input :name, required: true %>
<%= f.input :email, required: true %>
<%= f.button :submit %>
<% end %>
When I run the code, the form only showing a input-field for :name and :email and there is no input-field for units:[:unitname]. How can I show the input-field in the form? Thanks in advance.
References:
1. Rails 4: accepts_nested_attributes_for and mass assignment
2. https://github.com/plataformatec/simple_form/wiki/Nested-Models
Add
def new
#user = User.new
#user.units.build
end
on your controller
You need to build the association in the new action, not the create action.

Association belongs_to vs nested attributes in the form

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

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