Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby

Naomi Freeman
STAFF
Naomi Freeman
Treehouse Guest Teacher

Nested params - Rails 4

Hi there,

I've figured out I was asking the wrong question in my still-unanswered question earlier this week.

The question now is: How do you create nested params in Rails 4?

Rails 3: @document.update_attributes(params[:status][:document_attributes])

Rails 4: a singular param would be (status_params)

with

private
  def status_params
    params.require( ).permit(  )
  end

And what is the new way to do .update_attributes? This would access attr_accessible, which is what params replace.

I realized update_attributes isn't calling anything.

Any thoughts/leads would be much appreciated. Thanks!

8 Answers

Nick Fuller
Nick Fuller
9,027 Points

Alright! Thanks for the github link, it helped a lot to dive in.

The good news is, you were 100% correct with what the issue was! I'll give you the answer, but it's going to open the door to another error, but I think you will figure it out pretty quickly.

    # Never trust parameters from the scary internet, only allow the white list through.
    def status_params
      params.require(:status).permit(:content, :profile_name, :full_name, :user_id, :first_name, :last_name, document_attributes: [:attachment, :document, :attachment_file_name, :document_fields, :build_document, :remove_attachment])
    end

A side note.. I like to use two gems, one called awesome_print and another called quiet_assets. Just add them to your gemfile and run bundle install. Awesome print gem gives you access to the 'ap' method. When you pass an array or a hash to the method as an argument it will format everything really nicely for you. You mainly use it in console and in log files. Quiet assets will remove all the noise from your logs pertaining to images, stylesheets and javascripts. Most of the time you don't need to read all of that. Although it may be beneficial to use quiet_assets only in development mode

Here are two examples of awesome_print

In console (this is your application!)

2.1.0p0 :012 > s = Status.last; ap s; 0
#<Status:0x000001069fe118> {
             :id => 3,
        :content => "fsdafsda",
     :created_at => Thu, 20 Feb 2014 16:32:09 UTC +00:00,
     :updated_at => Thu, 20 Feb 2014 16:32:09 UTC +00:00,
        :user_id => 1,
    :document_id => 1
}

You can also use it in your controller and look at your log files (which is what I do all the dang time)

This is your create method

  def create
    ap params
    @status = current_user.statuses.new(status_params)

    respond_to do |format|
      if @status.save
        format.html { redirect_to @status, notice: 'Status was successfully created.' }
        format.json { render action: 'show', status: :created, location: @status }
      else
        format.html { render action: 'new' }
        format.json { render json: @status.errors, status: :unprocessable_entity }
      end
    end
  end

and in the logs I can see the attributes for the document_attributes hash

{
                  "utf8" => "?",
    "authenticity_token" => "q2ceq5QUNJ5jV7iIMuB1VxjMxvB/H5NQWt4bBadOwss=",
                "status" => {
                    "content" => "I found this cool website to buy t-shirts!",
        "document_attributes" => {
            "attachment" => #<ActionDispatch::Http::UploadedFile:0x0000010bf153b8 @tempfile=#<Tempfile:/var/folders/1x/f2p9pq_j5zz2_816j67ns6y40000gn/T/RackMultipart20140220-28158-mfbn31>, @original_filename="shirts4mike-homepage-image.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"status[document_attributes][attachment]\"; filename=\"shirts4mike-homepage-image.png\"\r\nContent-Type: image/png\r\n">
        }
    },
                "commit" => "Create Status",
                "action" => "create",
            "controller" => "statuses"
}
Nick Fuller
Nick Fuller
9,027 Points

Hi Naomi!

When in doubt read the README for the strong parameters gem on github!

https://github.com/rails/strong_parameters#nested-parameters

params.permit(:name, {:emails => []}, :friends => [ :name, { :family => [ :name ], :hobbies => [] }])

Rails 4 replaced the update_attributes method with one of my favorite new methods... update!

Simple enough right? Just pass a hash to the update method and bam! You win!

user_attributes = { first_name: 'Nick', last_name: 'Fuller', profile_name: 'nfuller52' }
user = User.find(1)
user.update(user_attributes)

Done!

Naomi Freeman
STAFF
Naomi Freeman
Treehouse Guest Teacher

Thanks Nick!

I did go to the docs :) And I found these things. I think I need to customize it a bit for the bit I'm working on.

I'm in the final Ruby project, doing the file upload/paperclip gem work.

I think what I'm maybe missing is the equivalent of "user_attributes". I know there is "document_attributes" already in my project, but they don't have anything defined. I think it may be pulled from paperclip, but I'm not entirely sure ...

https://github.com/summerspirit/laughing-wookie

If you run it locally, you can see that everything is "operational", in that it doesn't throw an error. However, it's not yet maintaining the document, it's not saving it or passing it through or something, so all the "if document" statements are not true and you can't see the document or change it.

I was also curious if it was the difference in how the fields_for was configured, so if you end up on the edit page of a status, it will have a file upload, but this is one I hardcoded in, not the "if document" one. When posting a status, the one that will trigger the "if document" is the top one of the two.

Any thoughts on how I can finally bring this around? Is it just the document_attributes?

Please see the statuses controller.

Thanks!

Naomi Freeman
STAFF
Naomi Freeman
Treehouse Guest Teacher

Awesome! Thanks again!

The attachment is working! It shows up places :D

The error I get is when I edit. Oddly enough, remove attachment works, but throws an error. The attachment is removed when I hit "back" from the error though. It will also update the attachment, but throws that error and then is the new attachment if I hit "back".

The error is "unknown attribute: content" highlighting @document.update(status_params)

I tried adding relationships to the documents model, changing the params content, adding a migration that added a document column to user. Still throwing the error.

In my terminal I can see it was telling me "Unpermitted parameters: id" but I don't know whose id or what it belongs to. I added :id into the .permit under document_attributes, and it stopped telling me about unpermitted parameters, but still gave me the error "unknown attribute: content".

Is it the relationships?

Why doesn't my document know about content? I pushed the new things I played with: https://github.com/summerspirit/laughing-wookie

Thanks for all your help.

Nick Fuller
Nick Fuller
9,027 Points

Well you're passing in the status params into a document object. So strong parameters is looking for a :content attribute on a Document and it's unable to find one.

You can try this...

  def update
    @status = current_user.statuses.find(params[:id])

    if status_params && status_params.has_key?(:user_id)
      status_params.delete(:user_id)
    end

    if params[:status][:document_attributes][:remove_attachment] == '1'
      @status.document.attachment = nil
    end

    respond_to do |format|
      if @status.update(status_params)
        format.html { redirect_to @status, notice: 'Status was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @status.errors, status: :unprocessable_entity }
      end
    end
  end

But I'm not a fan of the implementation! There are better ways to handle removing the document!

Naomi Freeman
STAFF
Naomi Freeman
Treehouse Guest Teacher

Yay :D You're amazing. Thank you so much!

It does seem a little ... verbose? But maybe that's just because I've been doing a lot of code interviews lately and am always being asked to refactor things.

I might take it to a project night and see if we can play with it a bit.

Thanks again! Hopefully it's smooth sailing from here.

Nick Fuller
Nick Fuller
9,027 Points

I would refactor it down into an instance method on the Status model. Maybe create a new method like... update_with_document?

At least it's working though! Best wishes

Todd Nestor
Todd Nestor
10,689 Points

I had a similar problem, I am using Rails 4 doing the treebook app tutorials. Mine would delete the attachment any time I updated the status (regardless of checking the box or not), I finally figured out that I had to add :id to the document_attributes: part in the status_params function, so now it looks like this:

def status_params
      params.require(:status).permit(:content, :profile_name, :full_name, :user_id, :first_name, :last_name, document_attributes: [:id, :attachment, :document, :attachment_file_name, :document_fields, :build_document, :remove_attachment])
end

and that made it finally work after a few hours of trying different things...