How to persist values of delegated fields in rails - ruby-on-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)

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

fail to manage nested attributes

I've stuck managing nested attributes. I'm a new guy to rails and would be very grateful for any advise or pointing on my mistakes. So I have a model Ticket like this( it also has enum integer field status, references stuff_id body and so on )
class Ticket < ActiveRecord::Base
include Generator
has_paper_trail only:[:stuff_id, :status], on:[:update]
has_many :replies
accepts_nested_attributes_for :replies
end
Also I have Reply model
class Reply < ActiveRecord::Base
belongs_to :ticket
validates :ticket_id, :body, presence: true
end
My target is to give an opportunity for stuff to create a reply for a ticket and optionally change status for a ticket. Currently I try to manage it this way:
routes.rb
resources :tickets do
patch 'stuff_update', on: :member
resources :replies
end
dashboards_controller.rb
class DashboardsController < ApplicationController
#before_action :authorize
before_action :method
def opened
end
protected
def method
#tickets = Ticket.send(action_name.to_sym).includes(:replies)
end
def ticket_params
params.require(:ticket).permit(:status, replies_attributes: [:body])
end
end
tickets_controller.rb
class TicketsController < ApplicationController
#before_action :authorize, only:[:stuff_update]
before_action :load_ticket, only:[:show, :update, :stuff_update]
respond_to :js
def stuff_update
#ticket.update(ticket_params)
end
protected
def ticket_params
params.require(:ticket).permit(:name, :subject, :email, :status, :body, :department, :stuff_id, replies_attributes: [:id, :body])
end
def load_ticket
#ticket = Ticket.find(params[:id])
end
end
And finally view
-#tickets.each do |t|
.panel.panel-default
p=t.subject
=form_for t,{ url: "/tickets/#{t.id}/stuff_update"}, {method: :patch} do |f|
= f.label :status, class: 'label_hidden'
= f.select :status, Ticket.statuses.keys, {}, {class:'form-control'}
= f.fields_for t.replies.build do |ff|
= ff.label :body
= ff.text_field :body
= f.submit "Submit"
I've got an error for unpermitted parameter reply. Also it seems to me there are plenty mistakes in my code besides this error. I desperately need help. Please help me to get through this.
Ok, the solution I've found is pretty simple
-#tickets.each do |t|
-t.replies.build
.panel.panel-default
p=t.subject
=form_for t, url: "/tickets/#{t.id}/stuff_update", method: :patch do |f|
p
= f.label :status, class: 'label_hidden'
= f.select :status, Ticket.statuses.keys, {}, {class:'form-control'}
p
= f.fields_for :replies do |ff|
= ff.label :body
= ff.text_field :body
= f.submit "Submit"
everything else is pretty identic.

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

Rails 4, Devise, Nested attributes not saving to database

I am using devise for user authentication in a rails 4 app.
After the user registers, I am redirecting the user to a page which has some additional fields they can choose to populate. I have the form appearing correctly, but it is not saving the nested attribute to the database.
I have a model called "seeker_skill" which has this relation to user:
user has_many seeker_skills
seeker_skills belongs to user
user.rb
class User < ActiveRecord::Base
has_many :seeker_skills, dependent: :destroy
accepts_nested_attributes_for :seeker_skills, :reject_if => lambda { |a| a[:skill].blank? }, :allow_destroy => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
users_controller.rb
class UsersController< ApplicationController
def job_seeker_additional_fields
#user = current_user
#user.seeker_skills.build
#seeker_skill = current_user.seeker_skills.build
end
end
seeker_skill.rb
class SeekerSkill < ActiveRecord::Base
belongs_to :user
validates :skill, presence: true
end
seeker_skills_controller.rb
class SeekerSkillsController < ApplicationController
def create
#seeker_skill = current_user.seeker_skills.build(seeker_skill_params)
if #seeker_skill.save
redirect_to root_url
else
flash[:error] = "Invalid Input"
redirect_to myskills_path
end
end
def destroy
end
def new
#seeker_skill = current_user.seeker_skills.build
#user = current_user
end
private
def seeker_skill_params
params.require(:seeker_skill).permit(:skill)
end
end
I believe I have the permitted parameters set up correctly in the application controller.
application_controller.rb
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :role, :email,
:company, :password, :password_confirmation, :remember_me,
seeker_skills_attributes: [:id, :skill, :user_id, :_destroy]) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login,
:username, :role, :email, :company, :password, :remember_me) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :bio,
:min_salary, :location, :radius, :role, :email, :company, :password, :password_confirmation,
:current_password, seeker_skills_attributes: [:id, :skill, :user_id, :_destroy]) }
end
end
Finally there is the form in the view: Eventually I will add option to add multiple skills at once.
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= f.fields_for(#seeker_skill) do |f| %>
<%= f.text_field :skill, placeholder: "Add skill" %>
<% end %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
What am I missing? I have set this up with a custom user authentication system but never with devise.
Change your nested fields call to:
<%= f.fields_for(:seeker_skill) do |f| %>
with a symbol, not the object. When created with object, it names the field from the object class name, so in result you got params[:user][:seeker_skill], which are then filtered by strong params. While run with symbol, it tries to execute method with given name, treats it as an object and if the form object defines <name>_attributes sets the subobject name to <name>_attributes.

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