Virtual attribute not moved to the model hash inside params
Asked Answered
A

3

6

I'm having a problem in my Rails 3.2 app where a virtual attribute sent restfully via JSON is not in the right place in the params hash. Well, it isn't where I expect. It remains to be seen if my expectations are correct. :)

I have a model using the standard virtual attribute pattern, like this:

class Track < ActiveRecord::Base
  def rating
    # get logic removed for brevity
  end

  def rating=(value)
    # set logic
  end

  def as_json(options={}) # so my method is in the JSON when I use respond_with/to_json
    super(options.merge(methods: [:rating]))
  end
end

The JSON sent to my controller looks like this:

{"id":1,"name":"Icarus - Main Theme 2","rating":2}

To be clear, name and id are not virtual, rating is.

I end up with this in the params hash, after rails does its magic:

{"id"=>"1", "name"=>"Icarus - Main Theme 2", "rating"=>2, "track"=>{"id"=>"1", "name"=>"Icarus - Main Theme 2"}}

As you can see, id and name make it to the nested :track hash, but rating does not. Is this expected behavior? It breaks the (somewhat) standard practice of using the nested hash in the controller because the nested hash does not contain all the parameters I need.

Track.update(params[:id], params[:track]) # :track is missing rating

Thanks for your help!

Attested answered 19/2, 2012 at 22:54 Comment(1)
Can you paste the form that is making the submission, it looks like its not properly scoped to trackCovariance
A
7

I recently ran into this gotcha as well. The problem is, the params wrapper is looking at your model Track.attribute_names to determine how to map the data into a :track => {params} hash. If you don't have a model associated, the default will be to wrap the params based on the controller name, and include all of the values:

class SinglesController < ApplicationController
  def create
    #params[:single] will contain all of your attributes as it doesn't 
    # have an activerecord model to look at.
    @track_single = Track.new(params[:single]) 
  end
end

You can call wrap_parameters in your controller to tell action controller what attributes to include when its wrapping your params, like so:

class TracksController < ApplicationController
  wrap_parameters :track, :include => :rating
  #other controller stuff below
end

See more here: http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html

Arvell answered 3/10, 2012 at 20:23 Comment(1)
I am having similar problem, when using wrap_parameters, if I have many other non-virtual attributes, I had to write all of them to the include array. otherwise they won't be included in the request hash,how to do this in a simple way? since I just have one virtual attribute that I want to include in the request hash? ThanksCampion
D
0

Maybe if you assign the rating virtual attribute inside the nested hash like this:

def as_json(options={})
  super(options.merge(:track => {:methods => @rating}))
end

It would behave the way you expected.

Detector answered 25/7, 2012 at 12:37 Comment(0)
C
0

Just ran across this problem and figured out a pretty decent solution. Add the following to your ApplicationController

wrap_parameters exclude: [:controller, :action, :format] + ActionController::ParamsWrapper::EXCLUDE_PARAMETERS

This way, everything is nested under your resource (except for stuff Rails adds to the params hash) and you won't ever have to append to a controller specific call of wrap_parameters again. :D

Cletacleti answered 9/5, 2016 at 21:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.