I am upgrading from Rails 3 to 4, and so I am adopting strong parameters. It seems that nested attributes are not being passed successfully. I have read several related SO questions and blog posts, and I'm still stumped.
An Event has many Occurrences. When I submit the form to create a new Event and one or more Occurrences, I get "1 error prohibited this class from being saved: Occurrences start can't be blank." However, start is not blank, as I confirmed by looking at the Parameters that are posted:
{"utf8"=>"✓",
"authenticity_token"=>"aPRLtUbjW7EMxO2kWSzCEctHYZgvBvwuk2QUymfiwkM=",
"event"=>{"name"=>"some name",
"photo"=>#<ActionDispatch::Http::UploadedFile:0x007f9e9bc95310 #tempfile=# <File:/var/folders/rz/1p7tmbmj2t5fbfv2wjhvwcsh0000gn/T/RackMultipart20140927-52743-10pcxtg>,
#original_filename="10435871_671176211140_3488560836686101357_n.jpg",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"event[photo]\"; filename=\"10435871_671176211140_3488560836686101357_n.jpg\"\r\nContent-Type: image/jpeg\r\n">,
"summary"=>"",
"facebook_event_link"=>"",
"occurrences_attributes"=>{"0"=>{"start"=>"09/30/2014",
"_destroy"=>"0"}},
"special"=>"0",
"prose"=>""},
"commit"=>"Create Event"}
Here are the relevant sections of the models and controller.
app/models/event.rb:
class Event < ActiveRecord::Base
has_many :occurrences, :dependent => :destroy
accepts_nested_attributes_for :occurrences, allow_destroy: true, reject_if: proc {|o| o[:start].blank? }
app/models/occurrence.rb:
class Occurrence < ActiveRecord::Base
belongs_to :event
validates_presence_of :start
app/controllers/events_controller.rb:
class EventsController < ApplicationController
def new
#event = Event.new
#event.occurrences.build
#event.passes.build
end
def create
#event = Event.create(event_params)
if #event.save
redirect_to #event, notice: 'Event was successfully created.'
else
render :action => "new"
end
end
...
private
def event_params
params.require(:event).permit(
:name, :expiration, :prose, :special, :summary, :photo, :link, :price, :student_price, :registration_switch, :facebook_event_link,
:occurrences_attributes => [:id, :start, :end, :_destroy])
end
end
Why isn't the information about Occurrences being passed correctly?
I'm not sure, but I think that strong parameters requires you permit the foreign key on associated models. So, perhaps you are missing event_id in your occurrences_attributes?
Not even close to 100% sure, but this could be it:
def event_params
params.require(:event).permit(
:name, :expiration, :prose, :special, :summary, :photo, :link, :price, :student_price, :registration_switch, :facebook_event_link,
:occurrences_attributes => [:id, :start, :end, :_destroy, :event_id])
end
Perhaps you need to ensure that your form has multipart: true
Related
I'm trying to learn Ruby on Rails, an I'm kinda stuck with associaton.
My project is to create a simple blog with three table. User, Post, and Comment.
In my understanding, after associationg several table with foreign key, rails would automatcily find user_id and post_id. But everytime I try to build comments, the user_id is nil.
Here's my model:
class User < ApplicationRecord
has_many :posts
has_many :comments
validates :name, presence: true, length: { minimum: 5 }, uniqueness: true
validates :password, presence: true, length: { minimum: 5 }
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
validates :title, presence: true
validates :body, presence: true, length: {minimum: 10}
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
validates :body, presence: true
validates :user_id, presence: true
validates :post_id, presence: true
end
Here is the screenshot when I try to create a comment:
As you can see, the post_id is not nil but the user_id is nil.
I try to input user_id manualy and it work as intended. But I can't find out how to create comment with automatic user_id and post_id.
In my understanding, after associationg several table with foreign key, rails would automatcily find user_id and post_id. But everytime I try to build comments, the user_id is nil.
There is no truth to that assumption. Rails will not automatically assign your assocations - how should it even know what user/post you want to associate the comment with?
Typically the way you would construct this is to have a nested route:
resources :posts do
resources :comments,
only: [:create]
shallow: true
end
This creates the route /posts/:post_id/comments so that we know which post the user wants to comment on - you would then adjust your forms so that it posts to the nested route:
# app/views/comments/_form.html.erb
<%= form_with(model: [post, comment]) do |f| %>
# ...
<% end %>
# app/views/posts/show.html.erb
# ....
<h2>Leave a comment</h2>
<%= render partial: 'comments/form',
locals: {
post: #post,
comment: #comment || #post.comments.new
}
%>
Getting the user who's commenting would typically be done by getting it from the session through your authentication system - in this example the authenticate_user! callback from Devise would authenticate the user and otherwise redirect to the sign in if no user is signed in.
You then simply assign the whitelisted parameters from the request body (from the form) and the user from the session:
class CommentsController
before_action :authenticate_user!
# POST /posts/1/comments
def create
# This gets the post from our nested route
#post = Post.find(params[:post_id])
#comment = #post.comments.new(comment_params) do |c|
c.user = current_user
end
if #comment.save
redirect_to #post,
status: :created
notice: 'Comment created'
else
render 'comments/show',
status: :unprocessable_entity,
notice: 'Could not create comment'
end
end
private
def comment_params
params.require(:comment)
.permit(:foo, :bar, :baz)
end
end
This is typically the part that Rails beginners struggle the most with in "Blorgh" tutorials as it introduces a resource thats "embedded" in another resource and its views and several advanced concepts. If you haven't already I read it would really recommend the Getting Started With Rails Guide.
you can create a comments as below:
user = User.find 2
post = user.posts.where(id: 2).first
comment = post.comments.build({comment_params}.merge(user_id: user.id))
Hope this will help you.
I'm building an application where I have used nested attributes to store different option records under a question record. There is a form where the user can dynamically add and remove options. Everything works fine in my create action, but in the update action, if I remove an existing option and submit the form, it is not deleted from the database.
When updating the question record, is there any way to completely overwrite the existing nested parameters and replace it with the ones we pass in the update request?
I understand that adding _destroy to the attributes and passing it as a parameter would satisfy my requirement here. Since I'm deleting the option information from my frontend state on press of a "remove" button in the UI, I'm not sending it along with the params. Is there any other method in Rails to completely overwrite nested attributes and delete those nested records which are not passed in the update request, from the update action in the controller itself?
question.rb
class Question < ApplicationRecord
belongs_to :quiz
has_many :options
validates :body, presence: true
accepts_nested_attributes_for :options
end
option.rb
class Option < ApplicationRecord
belongs_to :question
validates :body, presence: true
validates :is_correct, inclusion: { in: [ true, false ], message: "must be true or false" }
end
questions_controller.rb
class QuestionsController < ApplicationController
...
def update
#question = Question.find_by(id: params[:id])
if #question.update(question_params)
render status: :ok, json: { notice: t("question.successfully_updated") }
else
render status: :unprocessable_entity, json: { error: #question.errors.full_messages.to_sentence }
end
end
...
private
def question_params
params.require(:question).permit(:body, :quiz_id, options_attributes: [:id, :body, :is_correct])
end
Relevant question
If I understand you correctly you're deleting the options one by one by clicking a button next to the option. Thats not actually something you need or want to use nested attributes for. Nested attributes is only relevant when you're creating/editing multiple records at once.
While you can destroy a single nested record by updating the parent:
patch '/questions/1', params: {
question: { options_attributes: [{ id: 1, _destroy: true }] }
}
Its very clunky and not really a good RESTful design.
Instead you can just setup a standard destroy action:
# config/routes.rb
resources :options, only: :destroy
<%= button_to 'Destroy option', option, method: :delete %>
class OptionsController < ApplicationController
# #todo authenticate the user and
# authorize that they should be allowed to destroy the option
# DELETE /options/1
def destroy
#option = Option.find(params[:id])
#option.destroy
respond_to do |format|
format.html { redirect_to #option.question, notice: 'Option destroyed' }
format.json { head :no_content }
end
end
end
This uses the correct HTTP verb (DELETE instead of PATCH) and clearly conveys what you're doing.
I can share my recent project work which is a bit similar to your where I am using shrine gem for upload images and I can update/destroy images which is associated with a Product model
product.rb
.
.
has_many :product_images, dependent: :destroy
accepts_nested_attributes_for :product_images, allow_destroy: true
product_image.rb
.
belongs_to :product
.
_form.html.erb for update
<%= f.hidden_field(:id, value: f.object.id) %>
<%= image_tag f.object.image_url unless f.object.image_url.nil? %>
<%= f.check_box :_destroy %>
and in products controller,I have whitelisted this
product_images_attributes: [:_destroy,:image, :id]
Hope this helps you to solve on your case
Issue
I'm encountering a problem when editing a form with a belongs_to relationship (extra_guest belongs_to age_table).
I am able to create a new extra_guest and assign it to an age_table, but I cannot get the edit/update to work as my update function returns a falseClass.--> #extra_guest.update(extra_guest_params).errors.full_messages returns undefined method `errors' for false:FalseClass
Code
models
class ExtraGuest < ApplicationRecord
belongs_to :age_table
validates :age_table, presence: true
end
class AgeTable < ApplicationRecord
belongs_to :park
has_many :extra_guests, dependent: :destroy
validates :name, :age_from, :age_to, presence: true
validates_associated :extra_guests
end
class Attraction < ApplicationRecord
belongs_to :park
has_many :extra_guests, dependent: :destroy
accepts_nested_attributes_for :extra_guests, allow_destroy: true
validates :name, presence: true
end
class Park < ApplicationRecord
has_many :attractions, dependent: :destroy
has_many :age_tables, dependent: :destroy
validates :name, :currency, presence: true
end
extra_guests_controller
def edit
#extra_guest = ExtraGuest.find(params[:id])
#age_table = #extra_guest.age_table
#age_table_list = AgeTable.where(park: #attraction.park)
end
def update
#extra_guest = #attraction.extra_guests.find(params[:id])
#age_table = AgeTable.find(params[:age_table])
authorize #extra_guest
if #extra_guest = #extra_guest.update(extra_guest_params)
redirect_to root_path
else
#attraction = Attraction.find(params[:attraction_id])
#extra_guest = ExtraGuest.find(params[:id])
#age_table_list = #attraction.park.age_tables
render 'edit'
end
end
private
def extra_guest_params
params.require(:extra_guest).permit(:name, :age_table_id,
extra_guest_prices_attributes: [:id, :name, :price_type, :start_date, :end_date, :price, :duration, :duration_min, :duration_max, :backend_only, :weekend_extra, :_destroy])
end
views/extra_guests/form
<%= simple_form_for [#attraction, #extra_guest] do |f|%>
<%= f.input :age_table, :as => :select, :selected => #age_table.id, :collection => #age_table_list.map {|u| [u.name, u.id]}, :include_blank => false %>
<% f.button :submit %>
Error message + params
Couldn't find AgeTable without an ID
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"l8HMnVIRybZg==",
"extra_guest"=>
{"age_table"=>"104",
"extra_guest_prices_attributes"=>
{"0"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"20-09-2019", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"42"},
"1"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"2019-10-16", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"43"}}},
"commit"=>"Save new option",
"attraction_id"=>"185",
"id"=>"55"}
First of all, you say you have an error with this code #extra_guest.update(extra_guest_params).errors.full_messages but the code you show does not have that line.
Now, the update method returns false if it fails https://apidock.com/rails/ActiveRecord/Persistence/update
This line:
#extra_guest = #extra_guest.update(extra_guest_params)
will set #extra_guest to false if it fails, you don't need to set #extra_guest, just use if #extra_guest.update(extra_guest_params)
Using the line of code you name but it's not on the code you showed,#extra_guest.update(extra_guest_params).errors.full_messages, if there are errors then #extra_guest.update(extra_guest_params) will be false, so no .errors method is found.
you have to split it in two lines:
#extra_guest.update(extra_guest_params) # after this, #extra_guest will have the errors hash set
#extra_guest.errors.full_messages # call it on the object and not on the result value from the update method
EDIT: you are permitting age_table_id but the parameter is age_table, fix the name of the parameter to be age_table_id too
It looks to me like you tried to use #attraction before defining it. You could fix this by moving your definition of #attraction further up in the method, but I would move it into its own method like so:
private
def attraction
#attraction ||= Attraction.find(params[:attraction_id])
end
Then you use the method name, which is now defined for the whole controller and invoked when you use it (as opposed to an instance variable which will just be 'nil' if you invoke it without defining it). The ||= allows the method to return the existing value of the instance variable if it is defined, rather than running the query every time the method is called. So first line of your update action would be
#extra_guest = attraction.extra_guests.find(params[:id])
I would do something similar for the other instance variables you have there (#extra_guest, #age_table, and #age_table_list should be defined in private methods separately). Incidentally, using a lot of instance variables for a single controller (you have 4 in this controller, which is a lot) is considered a bit of a code smell, but you should make something that works first and then refactor. Reference for later: https://thoughtbot.com/blog/sandi-metz-rules-for-developers
I've a Rails API and I've two models:
class Event < ActiveRecord::Base
belongs_to :category
has_many :event_categories
has_many :events, through: :event_categories
attr_accessible :title, :description, :event_categories_attributes
accepts_nested_attributes_for :event_categories
end
and
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
attr_accessible :category_id, :event_id, :principal
validates :event, :presence => true
validates :category, :presence => true
validates_uniqueness_of :event_id, :scope => :category_id
end
In a first moment, EventCategory didn't exist so I created Event resources sending params like event[title]='event1', event[description] = 'blablbla' thought POST REST request.
My API EventsController was like this (I haven't a new method because I don't need views):
def create
#event = Event.create(params[:event])
if #event
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
This way worked correctly for me. Now, with the new EventCategory model I don't know how I could create EventCategories models at the same time.
I've trying this... but it doesn't work:
def create
#event = Event.new(params[:event])
#event.event_categories.build
if #event.save
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
Rails told me:
{
"event_categories.event": [
"can't be blank"
],
"event_categories.category": [
"can't be blank"
]
}
I send the category_id like this:
event[event_categories_attributes][0][category_id] = 2
Any ideas?
In your create action, instead of this:
#event.event_categories.build
Try this:
#event.event_categories = EventCategory.new do |ec|
ec.event = #event
ec.category = the_cattegory_you_want_to_specify
# You need both of these as you are validating the presence of event AND category
end
So here is my issue. I have been working with users I created at the beginning of my project for a month now. Today I switched from sqllite to sqlserver to meet client requirements and when I went to use my registration form to create a new user I got the following error:
can't convert Symbol into Integer
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"51nF50CYGNqz3N4o7TUYSyWeTadulXojQBPqERjvlcY=",
"user"=>{
"email"=>"test#blizzardlabs.com",
"login"=>"bgarrison",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"profile_attributes"=>{
"prefix"=>"",
"first_name"=>"Bill",
"last_name"=>"Garrison",
"suffix"=>"",
"birthday"=>"1983-06-01",
"phone_numbers_attributes"=>{
"0"=>{
"info"=>"1234567890",
"label"=>"Cell"
}
}
}
},
"commit"=>"Register"}
I have a feeling that at some point I messed up the registration process but I can't for the life of me figure out where. User-> has_one profile-> has_many phone_numbers.
User Controller:
def create
#user = User.new(params[:user])
if #user.save
#profile = #user.profile
flash[:notice] = "Your account has been created."
redirect_to(#user)
else
flash[:notice] = "There was a problem creating you."
render :action => :new, :layout => 'logged_out'
end
end
User Model:
class User < ActiveRecord::Base
# Accessible attributes
attr_accessible :login,
:email,
:password,
:password_confirmation,
:profile_attributes,
:active
# Associations
has_one :profile, dependent: :destroy, autosave: true
# Allows for a profile hash in user creation (stored in :profile_attributes)
accepts_nested_attributes_for :profile
Profile Model:
class Profile < ActiveRecord::Base
# Accessible Attributes
attr_accessible :birthday,
:company_id,
:first_name,
:last_name,
:prefix,
:suffix,
:phone_numbers_attributes,
:addresses_attributes
# Model Associations
has_many :phone_numbers, :as => :contactable, :class_name => "PhoneNumber", autosave: true
accepts_nested_attributes_for :phone_numbers, allow_destroy: true, reject_if: :all_blan
Any help would be appreciated. Thanks!
Update:1 Also, I have tested some and realized if I leave out the phone number then it works.....if I then update using the same form and add a phone number everything works fine.
Nested attributes should be passed in as Array:
"user"=>{
"email"=>"test#blizzardlabs.com",
"login"=>"bgarrison",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"profile_attributes"=>[
{
"prefix"=>"",
"first_name"=>"Bill",
"last_name"=>"Garrison",
"suffix"=>"",
"birthday"=>"1983-06-01",
"phone_numbers_attributes"=>{
"0"=>{
"info"=>"1234567890",
"label"=>"Cell"
}
}
}
]
}
So, after a couple days of banging my head against the wall I have finally figured this out. However to understand it I need to explain my model's a bit better.
Basically, from above you can see that a User has a profile which has many phone_numbers and addresses through a polymorphic association (:as => :contactable ). However, contactable is actually a base class called ContactInformation which uses STI to contain contactable information of all types.
At one point I decided that having 4 extra fields for addresses was cluttering up the STI relationship but I still wanted to keep it. My solution was to serialize all those fields into the "info" field of ContactInformation. Right now, phone numbers only have "number" as a field that is serialized and stored into "info" but if I ever want to seperate it out into "area code" "extension" etc the implementation will be simple.
This leads to the problem. On my registration form I was using label / info for my phone_number fields instead of label / number. I had edited my edit form but not my new form (yes i know they should be the same one but I have a special ajax form for editing).
Here is the code for ContactInformation / PhoneNumber / Address
class ContactInformation < ActiveRecord::Base
attr_accessible :contactable_id, :contactable_type, :info, :label, :type
belongs_to :contactable, :polymorphic => true
end
class PhoneNumber < ContactInformation
attr_accessible :number
stash :number, in: :info
#----------------------------------Validations--Start-------------------------
validates :number, presence: true
#----------------------------------Validations--End---------------------------
end
class Address < ContactInformation
attr_accessible :street_address, :city, :state, :postal
stash :street_address, :city, :state, :postal, in: :info
#----------------------------------Validations--Start-------------------------
validates :street_address, :city, :state, :postal, presence: true
#----------------------------------Validations--End---------------------------
end