Reputation: 469
I am creating a simple workflow where, after signing up, a publisher
can create a newsletter
. This newsletter needs three pieces of information: title
, description
, and publisher_id
(i.e. the creator). My question is two-fold:
publisher_id
, given that newsletters
will have posts
nested inside them and Rails recommends not nesting resources more than one level deep (i.e. I shouldn't nest newsletter inside publisher)?publisher_id
and what am I doing wrong?The workflow is as follows:
session[:id]
to @publisher.id
.new
view.publisher_id
to the session[:id]
.Upon navigating to to '/newsletters/new', I'm seeing the following error:
Started GET "/newsletters/new" for ::1 at 2020-05-04 15:53:22 -0700
Processing by NewslettersController#new as HTML
"<ActionController::Parameters {\"controller\"=>\"newsletters\", \"action\"=>\"new\"} permitted: false>"
Rendering newsletters/new.html.erb within layouts/application
Rendered newsletters/new.html.erb within layouts/application (Duration: 2.3ms | Allocations: 738)
And upon submitting 'Create Newsletter', I'm seeing the following error:
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/newsletters_controller.rb:21:in `create'
Started POST "/newsletters" for ::1 at 2020-05-04 15:58:34 -0700
(0.0ms) SELECT sqlite_version(*)
Processing by NewslettersController#create as JS
Parameters: {"authenticity_token"=>"XXX", "newsletter"=>{"title"=>"Newsletter 1", "description"=>"Description content"}, "commit"=>"Create Newsletter"}
Completed 500 Internal Server Error in 11ms (ActiveRecord: 1.0ms | Allocations: 7085)
publishers_controller.rb
class PublishersController < ApplicationController
def create
@publisher = Publisher.new(publisher_params)
if @publisher.save!
session[:id] = @publisher.id
redirect_to new_newsletter_path
else
render 'new'
end
end
private
def publisher_params
params.require(:publisher).permit(:email, :password)
end
end
newsletters_controller.rb
class NewslettersController < ApplicationController
def new
@newsletter = Newsletter.new
end
def create
@newsletter = Newsletter.new(newsletter_params)
if @newsletter.save!
redirect_to @newsletter
else
render 'new'
end
end
private
def newsletter_params
params.require(:newsletter).permit(:title, :description).merge(publisher_id: session[:id])
end
end
/newsletters/new.html.erb
<%= form_with model: @newsletter, url: newsletters_path do |form| %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :description %><br>
<%= form.text_area :description %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Upvotes: 0
Views: 29
Reputation: 102368
You have misunderstood what the rails guides meant by "nesting resources more than one level deep" - whats really meant is that this is OK:
/publishers/1/newsletters/new
Which is one level of nesting and the nesting provides very useful contextual information. While these are kind of fishy:
/publishers/1/newsletters/2
/publishers/1/newsletters/3/replies/new
In both cases we have two levels of nesting should be able to reach the nested resource without going though publishers
.
/newsletters/2
/newsletters/3/replies/new
Also if you want to add values from the session or somewhere else then the params hash when creating a record use a block or create the record off the association instead:
class NewslettersController < ApplicationController
def create
@newsletter = Newsletter.new(newsletter_params) do |newletter|
newsletter.publisher = current_publisher
end
# or
# @newsletter = current_publisher.newsletters(newsletter_params)
# save! will raise an exception if the record is not valid
# that is NOT what you want here
if @newsletter.save
redirect_to @newsletter
else
render 'new'
end
end
end
This makes it much more apparent what is coming from where.
Upvotes: 2