Spencer K.
Spencer K.

Reputation: 469

Rails foreign key assignment failing

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:

  1. What is the 'correct' way to set the 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)?
  2. If I am generally approaching it the correct way (see below), how am I supposed to pass the publisher_id and what am I doing wrong?

The workflow is as follows:

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

Answers (1)

max
max

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

Related Questions