I had to add the pen attributes to the paper model to stop the error "can't mass assign :pen", even thought I had the attr_accessible for pen_attributes.
Now, I'm getting a "unknown attribute: pen" error. It's pointing me to the second line of the create action. I can't figure it out.
I basically want to have to have the Paper New action create the pen and assign it to the paper.
Paper model
attr_accessible :name, :size, :line,
:pen_attributes,
:pen, :colour, :style
has_many :pens
accepts_nested_attributes_for :pens
Pens model
attr_accessible :name, :size, :line, :paper_attributes, :paper_id
belongs_to :paper
<%= simple_nested_form_for #paper do |f| %>
<%= f.input :name %>
<%= f.input :size, :placeholder => "text" %>
<%= f.input :line %>
<%= f.fields_for #pen do |h| %>
<%= h.input :pen, %>
<%= h.input :colour %>
<%= h.button :submit, :label => "create" %>
<% end %>
<% end %>
Paper Controller
def new
#user = current_user
#paper = #user.paper.build(params[:paper])
#pen = Pen.new(params[:pen])
end
def create
#user = current_user
#paper = #user.papers.build(params[:paper])
#pen = #paper.pens.build(params[:pen])
if #paper.save
flash[:notice] = "#{#paper.name} Created"
redirect_to(:action => "index")
else
flash.now[:notice] = "Error"
render 'new'
end
end
{"utf8"=>"✓",
"authenticity_token"=>"Z8vncB9ewDM1bWiKfsPHOGlkxcGpfhPjv0xpamudIIs=",
"paper"=>{"name"=>"three",
"size"=>"three",
"colour"=>"red",
"pen"=>{"colour"=>"test",
"pen"=>"test"}},
"commit"=>"Create"}
It looks like you have some minor discrepancies in your singular/plural naming.
I think you need to adjust the following:
<%= f.fields_for :pens, #pen do |h| %>
and probably:
attr_accessible :pens_attributes
as well as (possibly):
params[:pens]
Hope this helps, good luck!
Related
In a rails 5.2.3 app, I have a model Post, which uses active_storage to attach a file and has fields for duration and place. The duration must be present.
class Post
has_one_attached :video
validates :duration, presence: true
end
Using simple_form
the fields in the form are declared as
<%= f.input :duration %>
<%= f.input :place %>
<%= f.input :video %>
The controller has the following logic for the create
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = 'Post has been saved'
redirect_to root_path
else
#title = 'New Post'
#user = current_user
render :new
end
end
private
def post_params
params.require(:post).permit(:duration, :video)
end
If the validation fails, the form shows value of place, but I lose the file name for the video. This means the user has to choose the file again. How do I fix this?
Following Thanh's suggestion, I did check this SO question, and tried changing the simple_form field to
<%= f.hidden_field :video, value: f.object.image.signed_id if f.object.video.attached? %>
<%= f.file_field :video %>
This remembered the file name, but did not display it. So I did the following work around:
<% if f.object.video.attached? %>
<span><strong>Video File Name:</strong> <%= f.object.video.blob.filename.to_s %>. To change, choose different file below:</span>
<% end %>
<%= f.hidden_field :video, value: f.object.image.signed_id if f.object.video.attached? %>
<%= f.file_field :video %>
The purpose of the enroll model is to group three other models gymsite, user and role.
class Gymsite
has_many :users
class User
has_many :roles, through: :assigments
has_many :assigments
class Assigment
belongs_to :user
belongs_to :role
class Role
has many :assigments
has_many :users, through: :assigments
From the form at app/views/user/new.html.erb, input values are to be send to the app/controllers/user_controller.rb to perform the action 'enroll'. The problem is that all of the enroll_params are sent as is show in the browser, but at the controller, one of them become nil, in a recurrent way.
I have try by different means, including different form fields as select and check_boxes to try to solve this issue, with no result. I'm curios to learn what is the origin of the problem and how to solve it.Thanks
Browser processing information:
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"0z2B2t7H5wYJjtJMn8zBIPJ7sJaFk706EPh5JVZsC76x GWeggrJi7RSxAu8GJJmsyrNP7NzVv6rNvmumNDyhsg==", "enroll"=> {"user_first_name"=>"Lorena", "user_last_name"=>"Cueva", "user_email"=>"lcueva#example.com", "user_password"=>"[FILTERED]", "user_password_confirmation"=>"[FILTERED]", "gymsite_id"=>"1", "u_rol"=>["", "2", "4"]}, "commit"=>"Enroll User"}
app/views/users/new.html.erb
<%= simple_form_for #enroll, url: users_path do |form| %>
<div class='row'>
<div class='columns medium-2'></div>
<div class='columns medium-4'>
<%= form.input :user_first_name %>
<%= form.input :user_last_name %>
<%= form.input :user_email %>
<%= form.input :user_password %>
<%= form.input :user_password_confirmation %>
</div>
<div class='columns medium-4'>
<%= form.input :u_rol, as: :check_boxes, collection: Role.all.pluck(:name, :id), input_html: {multiple: true}%>
<%= form.submit 'Enroll User'%>
<% end %>
</div>
</div>
<%= link_to 'Back', users_path %>
at app/controllers/users_controller.rb
def new
#enroll = Enroll.new
#roles = Role.all
end
def create
#enroll = Enroll.new(enroll_params)
if #enroll.save
redirect_to root_path
else
render :new
end
end
def enroll_params
params.require(:enroll).permit(:user_email, :user_password, :user_password_confirmation, :gymsite_id, :u_rol, :user_first_name, :user_last_name)
end
at the app/models/enroll.rb all the parameters perform correct, except u_rol that becomes nil.
class Enroll
include ActiveModel::Model
attr_accessor :user_email, :user_password, :user_password_confirmation, :gymsite_id, :u_rol, :user_first_name, :user_last_name, :user, :role
def save
if valid?
#find gymsite by gymsite_id
gym = Gymsite.find(gymsite_id)
#save user for a gymsite
#user = gym.users.create(first_name: user_first_name, last_name: user_last_name, email: user_email, password: user_password, password_confirmation: user_password_confirmation )
#assign roles to the user
u_rol.each do |n|
rol = Role.find(n)
#user.roles << rol
end
end
end
I solve this issue changing enroll_parms to
def enroll_params
params.require(:enroll).permit(:user_first_name, :user_last_name,:user_email, :user_password, :user_password_confirmation, :gymsite_id, u_rol: [])
end
in the app/views/users/new.html.erb, added 'include_hidden:
false' to get rid of the blank value:
<%= form.input :u_rol, as: :check_boxes, collection: Role.all.pluck(:name, :id), include_hidden: false, input_html: {multiple: true}%>
Now it works!. Thks #jvillian for the tip!
I've already looked through every other stackoverflow for this issue, but none of the solutions have fixed this. My elements in a nested_form are not being saved in the database. I've also made sure that all model associations are correct. I've been trying to fix this for nearly 8 hours now, and would really appreciate some help, especially considering every other solution hasn't worked.
Basically, I have a Playlist model that contains multiple Song models. I'm trying to use a nested_form to add the Song models to the Playlist. However, none of the Songs are ever being saved. I apologize if my methods are misguides, as I'm still fairly new to Rails.
GitHub Repo:https://github.com/nsalesky/Ultra-Music
playlists_controller.rb
def index
#user = current_user
#playlists = #user.playlists
end
def show
#user = current_user
#playlist = #user.playlists.find(params[:id])
end
def new
#playlist = Playlist.new
#I was told to do this
#playlist.songs.build
end
def create
#user = current_user
#playlist = #user.playlists.create(playlist_params)
if #playlist.save
redirect_to #playlist
else
render :action => 'new'
end
end
def edit
#playlist = current_user.playlists.find(params[:id])
end
def update
#user = current_user
#playlist = #user.playlists.find(params[:id])
if #playlist.update_attributes(playlist_params)
redirect_to #playlist
else
render :action => 'edit'
end
end
def destroy
#user = current_user
#playlist = #user.playlists.find(params[:id])
#playlist.destroy
redirect_to playlists_path(#user.playlists)
end
private
def playlist_params
params.require(:playlist).permit(:name, :description, songs_attributes: [:id, :name, :link, :_destroy])
end
playlist.rb
belongs_to :user
has_many :songs, dependent: :destroy
accepts_nested_attributes_for :songs, :allow_destroy => true, :reject_if => lambda { |a| a[:content].blank? }
validates :name, presence: true
validates_associated :songs, presence: true
_form.html.erb
<%= nested_form_for #playlist do |f| %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<!--<div>
<button type="button" id="addsong">Add Song</button><br>
<button type="button" id="removesong">Remove Song</button><br>
</div> !-->
<div>
<%= f.fields_for :songs do |song_form| %>
<%= song_form.text_field :name %>
<%= song_form.text_field :link %>
<%= song_form.link_to_remove "Remove Song" %>
<% end %>
<p><%= f.link_to_add "Add Song", :songs %></p>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
In your playlist.rb, you wrote:
:reject_if => lambda { |a| a[:content].blank? }
Here the block parameter |a| stands for attributes of a specific song. So a[:attribute] relates to a single attribute. The problem is your Song doesn't have a :content attribute. So this a[:content].blank? will always be true, means you would be rejected building a song.
Just change a[:content] to a valid attribute such as a[:name]
Followed this guide by railscasts to setup autocompletion on collection_select.
https://www.youtube.com/watch?v=M7yhPlIehFA
In my example I'm trying to create a chatroom with a game related.
MODEL
belongs_to :game
validates :game, presence: true
def game_name
game.try(:name)
end
def game_name=(name)
self.game = Game.where(name: name).first_or_create if name.present?
end
CONTROLLER
def create
#room = current_user.chatrooms.build(room_params)
if #room.save
redirect_to #room
else
render 'new'
end
end
def room_params
params.require(:chatroom).permit(:title, :description, :game_id)
end
HTML
<%= simple_form_for #room do |f| %>
<p class="ftitle">Chatroom title</p>
<%= f.input :title, label: false %>
<p class="ftitle">Chatroom description</p>
<%= f.input :description, label: false %>
<p class="ftitle">Select related game</p>
<%= f.text_field :game_name, data: { autocomplete_source: Game.order(:name).map(&:name) } %>
<%= f.button :submit %>
<% end %>
It works fine until I try to create a chatroom with a game attached. It won't attach the game_id as a game. Not sure why.
Thanks.
Trying to link the game based on name seems quite brittle and open to future abuse.
However if that's really what you want to do, add :game_name to the .permit method in room_params.
It would be more robust to pass through an ID here, rather than plain text.
I have an address column, but I'm not using :address in my form, instead, I have
:street, :state, :city, :zip columns:
<%= form_for #user, :html => {:multipart => true} do |f| %>
<%= f.label :street %>
<%= f.text_field :street %>
<%= f.label :state %>
<%= f.text_field :state %>
<%= f.label :city %>
<%= f.text_field :city %>
<%= f.label :zip %>
<%= f.text_field :zip %>
<%= f.submit %>
<% end %>
Because I don't have a field for :address, but I would like to compile the information in the form and still insert it into my database. For example
(12345 Maple St, Made City CA 90017 or maybe when I get more advanced, I'll use some gem to compile the given information and put into a correct address format.)
How do I do such a thing?
My controller looks something like this:
def create
#user = User.new(params[:user])
if #user.save
redirect_to #user, notice: "Successfully created."
else
render :action => 'edit'
end
end
def create
address = [params[:user].delete(:street), params[:user].delete(:state), params[:user].delete(:city), params[:user].delete(:zip)].join(", ")
#user = User.new(params[:user].merge(:address => address))
if #user.save
redirect_to #user, notice: "Successfully created."
else
render :action => 'edit'
end
end
Another option would be to do it in model
attr_accessor :street, :city, :state, :zip
before_create :concate_address_attrs
Edit:-
I feel before_save is better to use over before_create
before_save :concate_address_attrs
def concat_address_attrs
self.address = [street, city, state, zip].join(", ")
end
Unless processing overhead is a major concern, you're breaking Rails' DRY principles by saving concatenated model attributes as a separate attribute.
The Rails Way of accomplishing this would be to create a model convenience method that concatenates existing attributes into a pseudo-address attribute without requiring any additional attributes be committed to the database:
# my_model.rb
def address
[street, city, state, zip].join(', ')
end
# my_view.html.erb
<%= #user.address %>
Alternatively, if you only need the full address for display in your views, you might consider using a view helper:
# app/helpers/my_model_helper.rb
module MyModelHelper
def formatted_address(address)
[address.street, address.city, address.state, address.zip].join(', ')
end
end
# my_view.html.erb
<%= formatted_address(#address) %>