I currently have the following setup in my Rails 5 Application:
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses
end
class Address < ApplicationRecord
belongs_to :user
end
The corresponding controller looks like this:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to users_path
else
render 'new', notice: "User could not be created"
end
end
private
def user_params
params.require(:user).permit(
:first_name, :last_name, :email, ...
)
end
end
Each User is supposed to have multiple Addresses (e.g. for billing and shipping) that I need to set during user creation. The form (made with formtastic) looks like this (outtake):
= semantic_form_for #user do |user|
= user.input :first_name
= user.semantic_fields_for Address.new do |shipping_address|
= shipping_address.input :city
= shipping_address.input :usage,
as: :hidden,
input_html: { value: 'shipping' }
= user.semantic_fields_for Address.new do |billing_address|
= billing_address.input :city
= billing_address.input :usage,
as: :hidden,
input_html: { value: 'billing' }
The problem is ofc that only the latter address is present in the sites parameters, so only one will be created.
How can I create multiple address for the same user?
To create nested records you should and seed the record and pass the association to fields_for.
For example you would do:
class UsersController < ApplicationController
# ...
def new
#user = User.new
# if we skipped this step there would be no address inputs on the form.
#user.addresses.new(value: 'shipping')
#user.addresses.new(value: 'billing')
end
end
<%= semantic_form_for(#user) do |f| %>
# ...
# fields_for iterates through #user.addresses creating
# inputs for each
<% f.semantic_fields_for :addresses do |a| %>
<%= a.inputs :street %>
<%= a.input :value, as: :hidden %>
<% end %>
<% end %>
Which would give the following params hash:
{
user: {
addresses_attributes: [
{
street: 'Wall Street',
value: 'shipping'
},
{
street: 'Broadway',
value: 'billing'
},
]
}
}
Note that proper pluralization is extremely important here!
You can then pass this to your user model and accepts_nested_attributes will do the rest of the work.
class UsersController < ApplicationController
# ...
def create
#user = User.new(user_params)
if #user.save
#...
else
# ...
end
end
private
def user_params
params.require(:user)
.permit(:email, :password, addresses_attributes: [:value, :street, :city]) # etc
end
end
Related
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.
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
Upon clicking submit only the Duel attributes are passing - not Dueler.
duels_controller.rb
def new
#duel = Duel.new
#user = User.find(params[:challenge_daddy]) # This pulls in the ID for Challenged User
# Current User
#duel.duelers << Dueler.new(user_id: current_user.id, user_name: current_user.name, user_last_name: current_user.last_name)
#current_user_challenges = current_user.challenges.order(:created_at)
# Challenged User
#duel.duelers << Dueler.new(user_id: #user.id, user_name: #user.name, user_last_name: #user.last_name)
#challenged_user_challenges = #user.challenges.order(:created_at)
respond_with(#duel)
end
I think I have to submerge the dueler info (i.e. full_name and collection_select) within something like <%= simple_form_for(#dueler) do |f| %>, but then I don't want two separate submit buttons. When the user clicks submit the dueler and duel information should both submit since they go hand-in-hand. Right now only the duel information submits and the duelers are never created.
duels/_form.html.erb
<%= simple_form_for(#duel) do |f| %>
<%= current_user.full_name %> WILL <%= collection_select(:dueler, :challenge_id, #current_user_challenges, :id, :full_challenge, include_blank: true) %>
<%= #user.full_name %> WILL <%= collection_select(:dueler, :challenge_id, #challenged_user_challenges, :id, :full_challenge, include_blank: true) %>
THE LOSER WILL <%= f.text_field :consequence %>.
<%= f.submit %>
<% end %>
UPDATE
Originally I had this in the _form:
<%= f.fields_for :duelers do |dueler| %>
<%= render 'dueler_fields', :f => dueler %>
<% end %>
But I took it out because the duels_controller new logic wasn't passing into it so I moved the code directly into the _form, but now I'm not sure what should take the place of <%= f.fields_for :duelers do |dueler| %>
class Dueler < ActiveRecord::Base
belongs_to :user
belongs_to :challenge
belongs_to :duel
end
class Duel < ActiveRecord::Base
belongs_to :user
belongs_to :challenge
has_many :duelers
accepts_nested_attributes_for :duelers, :reject_if => :all_blank, :allow_destroy => true #correct
end
class DuelsController < ApplicationController
before_action :set_duel, only: [:show, :edit, :update, :destroy, :duel_request]
respond_to :html
def index
#duels = Duel.joins(:duelers).all
redirect_to duel(#duel)
end
def duel_request
#dueler = #duel.duelers.where(user_id: current_user)
end
def show
#dueler = Dueler.find_by(user_id: current_user.id)
respond_with(#duel)
end
def user_challenges
#user = User.find_by_name(params[:name])
#challenges = #user.challenges.order(:created_at)
end
def new
#duel = Duel.new
#user = User.find(params[:challenge_daddy])
#duel.duelers << Dueler.new(user_id: current_user.id, user_name: current_user.name, user_last_name: current_user.last_name)
#current_user_challenges = current_user.challenges.order(:created_at)
#duel.duelers << Dueler.new(user_id: #user.id, user_name: #user.name, user_last_name: #user.last_name)
#challenged_user_challenges = #user.challenges.order(:created_at)
respond_with(#duel)
end
def edit
end
def create
#duel = Duel.new(duel_params)
#duel.save
#redirect_to duel_request_url(#duel)
respond_with(#duel)
end
def update
#duel.update(duel_params[:duelers_attributes])
respond_with(#duel)
end
def destroy
#duel.destroy
respond_with(#duel)
end
private
def set_duel
#duel = Duel.find(params[:id])
end
def duel_params
params.require(:duel).permit(:consequence, :reward, duelers_attributes: [:id, :user_id, :challenge_id, :accept])
end
end
If you are using has_many and belongs_to with accepts_nested_attributes you will need to use inverse_of to prevent Rails from attempting to lookup records (which of course don't exist because you haven't yet created them)
Change your Duel model has_many declaration to:
has_many :duelers, inverse_of: :duel
For further details on this and an example of a nested form with has_many relationship using Simple Forms check out:
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through
I am making a form for sign up and I have a class named ManagedCompany, that have an attribute named company of the class Company. The class Company have an attribute from another class named Address. So, there are compositions in those classes.
The classes are presented below:
app/models/managed_company.rb
class ManagedCompany
include ActiveModel::Model
def initialize
#company = Company.new
end
attr_accessor :name, :string
attr_accessor :company, :Company
end
app/models/company.rb
class Company
include ActiveModel::Model
def initialize
#address = Address.new
end
attr_accessor :category, :string
attr_accessor :name, :string
attr_accessor :address, :Address
validates_presence_of :category
validates_presence_of :name
end
app/models/address.rb
class Address
include ActiveModel::Model
attr_accessor :street, :string
attr_accessor :number, :string
validates_presence_of :street
validates_presence_of :number
end
My form:
<%= simple_form_for #managed_company do |form| %>
<%= form.input :company.address.street, autofocus: true %>
<% end %>
But this way did not work. The error message:
undefined method `address' for :company:Symbol
So, in the form (simple_form of ruby on rails), how can I access the attribute street from the address object?
Edit: add controllers code
app/controllers/managed_companies_controller.rb
class ManagedCompaniesController < ApplicationController
def new
#managed_companies = ManagedCompanies.new
end
def create
#managed_companies = ManagedCompanies.new(secure_params)
if #managed_companies.valid?
else
render :new
end
end
private
def secure_params
params.require(:managed_companies).permit(:name)
end
end
app/controllers/companies_controller.rb
class CompaniesController < ApplicationController
def new
#company = Company.new
end
def create
#company = Company.new(secure_params)
if #company.valid?
else
render :new
end
end
private
def secure_params
params.require(:company).permit(:name)
end
end
app/controllers/addresses_controller.rb
class AddressesController < ApplicationController
def new
#address = Address.new
end
def create
#address = Address.new(secure_params)
if #address.valid?
else
render :new
end
end
private
def secure_params
params.require(:address).permit(:street)
end
end
<%= form.input #company.address.street, autofocus: true %>
I'm building a rails app, using devise for authentication and cancan for authorization. I have a form where you can create a "post" and each post has_and_belongs_to many "tags".
I want to create a system for creating tags similar to stack overflow, where the tags are simply inputed via a single text box and then converted to the appropriate tag objects on the server side. Initially I simply had a text box where I could type in a string and the string would be parsed as such in the controller
#post.tags << params[:post][:tags].split(' ').map{ |name| Tag.createOrReturnExisting name}
and that worked perfectly.. until I added cancan authorization, which required me to add
def post_params
params.require(:post).permit(:title,:description,:tags,:content,:types_id)
end
to my controller , which now causes the following error to be thrown upon trying to create a Post undefined method each for "Tag1 Tag2 Tag3\r\n":String I'm assuming this is because its trying to treat the string from the textbox like an array of tags before I've had a chance to format it.
So my question is, how must I format my controller, model, or view to be able to parse the string before it gets to the post_params method?
here's my models, view, and controller
Tag Model
class Tag < ActiveRecord::Base
has_and_belongs_to_many :post, join_table: 'tag_posts'
def self.createOrReturnExisting title
if Tag.any? {|tag| tag.title == title}
logger.debug "Tag: #{title} already exists"
Tag.find_by title: title
else
logger.debug "Tag: #{title} created"
Tag.new title: title
end
end
end
Post Model
class Post < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :tags, join_table: 'tags_posts'
has_one :type
validates :title, presence: true, length: { maximum: 50 }
validates :description, presence: true, length: { maximum: 255 }
validates :types_id, presence: true, length: { maximum: 255 }
end
new.html.erb
<h1>Post#new</h1>
<p>Find me in app/views/post/new.html.erb</p>
<%= form_for #post do |f| %>
Title: <%= f.text_field :title %>
Description: <%= f.text_area :description %>
Type: <%= f.collection_select( :types_id, Type.all, :id, :title ) %>
Content: <%= f.text_area :content%>
Tags: <%= f.text_area :tags%>
<%= f.submit %>
<% end %>
PostController
class PostsController < ApplicationController
load_and_authorize_resource
def miniList
render 'miniList'
end
def create
#post = Post.new
#post.title = params[:post][:title]
#post.description = params[:post][:description]
#post.content = params[:post][:content]
#tagStrings = params[:post][:tags].split(' ')
puts #tagStrings
#tagStrings.map do |name|
#tags << Tag.createOrReturnExisting name
end
#post.tags = #tags
#post.types_id = params[:post][:types_id]
if #post.save!
flash[:success] = "Post Saved Successfully"
else
flash[:error] = "Post not saved"
end
current_user.posts << #post
redirect_to :root
end
def new
#post = Post.new
render 'new'
end
def edit
end
def update
end
def delete
end
private
def post_params
params.require(:post).permit(:title,:description,:tags,:content,:types_id)
end
end
I figured it out, what I needed to do was change load_and_authorize_resource to authorize_resource since I don't want cancan messing with my parameters, this will just have it check authorization for my controller actions, and then leave the rest alone. I still wish there was a more intuitive way to do it, but this accomplishes what I need