Ruby to_json :methods arguments - ruby-on-rails

I am using the to_json method on an object, and trying to get the :methods argument to work. I have a method on my model (Drop) called is_favorited_by_user?. This method takes an argument of the current_user, and then checks to see if the drop is favorited by the user. How can I pass this argument through the to_json method.
render :json => #drops.to_json(:methods => :is_favorited_by_user?(current_user))

You can add current_user attribute to your model and set it before executing to_json
attr_accessor :current_user
def is_favorited_by_user?(user=nil)
user ||= current_user
# rest of your code
end
#drops.current_user = current_user
render :json => #drops.to_json(:methods => :is_favorited_by_user?)

The methods to_json takes aren't intended to be ones that take arguments, but just "getter" methods. (Typically the ones created based on your db attributes.)
See examples here, none pass arguments:
http://apidock.com/rails/ActiveRecord/Serialization/to_json
If you want to do something custom, you'll need to create a method that builds up the hash of info you want, then convert that hash to json.

Related

Rails using hash in controller

I am learning rails 4 and i am bit confused in some notation in the tutorial i am following. I am following Lynda Ruby on Rails 4 Essential Training.
I have a simple controller with crud actions. In New action i am assing the instance variable the parameter as this (with curly braces)
#subject = Subject.new({:name=>'default'})
But in Create actions I am doing this:
Subject.new(params[:subject])
redirect_to(:action=>'index')
Shouldn't this params[:subject] and :action=>'index' should also be inside the curly braces?
How can i know when to use curly braces and not?
params[:subject] will most probably return a value like
{:attr1 => 'value1', :attr2 => 'value2'}
Enclosing this in curly braces will result in
{{:attr1 => 'value1', :attr2 => 'value2'}} # Not a valid Hash/Syntax
But, render({:action => 'index'}) is same as without the curly braces. Ruby is intelligent enough to identify that it is a Hash without the curlies.
It is a common scenario to have a Hash as the last argument to a method.
def my_method(arg1, arg2, options={})
..
In this case, it is sometimes prefered to drop the {} as it could te mistaken for a block
my_method 1, 2, :opt1 => 'val1'
Strong Params
Something to add to Santosh's answer - you really need to consider the strong_params method when creating new ActiveRecord objects (if you want to save them):
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def new
#model = Model.new
end
def create
#model = Model.new(model_params)
end
private
def model_params
params.require(:model).permit(:attributes, :for, :model)
end
end
--
Options
In terms of your redirect_to, I think Santosh covered the bases very well; however, you may wish to use just a symbol to denote loading an action:
redirect_to :index
By default, Rails will use the same controller you're on, allowing you to point to various actions within it.
--
Update
For strong_params, you have to remember what this is doing exactly.
Strong Params is just a method which allows you to send certain parameters to the model. This means if someone tries to mass-assign, it won't pass the un-permitted params through.
When you mention that I'm calling the model twice - I'm only calling it for different actions. The new action is there to create a new instance of the ActiveRecord object, the create action is there to save that instance (you have to recreate it with the params from your form)
You'll want to read up on strong params here

Ruby 2 Keyword Arguments and ActionController::Parameters

I have a rails 4 application that is running on ruby 2.1. I have a User model that looks something like
class User < ActiveModel::Base
def self.search(query: false, active: true, **extra)
# ...
end
end
As you can see in the search method I am attempting to use the new keyword arguments feature of ruby 2.
The problem is that when I call this code from in my controller all values get dumped into query.
params
{"action"=>"search", "controller"=>"users", query: "foobar" }
Please note that this is a ActionController::Parameters object and not a hash as it looks
UsersController
def search
#users = User.search(params)
end
I feel that this is because params is a ActionController::Parameters object and not a hash. However even calling to_h on params when passing it in dumps everything into query instead of the expected behavior. I think this is because the keys are now strings instead of symbols.
I know that I could build a new hash w/ symbols as the keys but this seems to be more trouble than it's worth. Ideas? Suggestions?
Keywords arguments must be passed as hash with symbols, not strings:
class Something
def initialize(one: nil)
end
end
irb(main):019:0> Something.new("one" => 1)
ArgumentError: wrong number of arguments (1 for 0)
ActionController::Parameters inherits from ActiveSupport::HashWithIndifferentAccess which defaults to string keys:
a = HashWithIndifferentAccess.new(one: 1)
=> {"one"=>1}
To make it symbols you can call symbolize_keys method. In your case: User.search(params.symbolize_keys)
I agree with Morgoth, however, with rails ~5 you will get a Deprecation Warning because ActionController::Parameters no longer inherits from hash. So instead you can do:
params.to_unsafe_hash.symbolize_keys
or if you have nested params as is often the case when building api endpoints:
params.to_unsafe_hash.deep_symbolize_keys
You might add a method to ApplicationController that looks something like this:
def unsafe_keyworded_params
#_unsafe_keyworded_params ||= params.to_unsafe_hash.deep_symbolized_keys
end
You most likely do need them to be symbols. Try this:
def search
#users = User.search(params.inject({}){|para,(k,v)| para[k.to_sym] = v; para}
end
I know it's not the ideal solution, but it is a one liner.
In this particular instance I think you're better off passing the params object and treating it as such rather than trying to be clever with the new functionality in Ruby 2.
For one thing, reading this is a lot clearer about where the variables are coming from and why they might be missing/incorrect/whatever:
def search(params)
raise ArgumentError, 'Required arguments are missing' unless params[:query].present?
# ... do stuff ...
end
What you're trying to do (in my opinion) only clouds the issue and confuses things when trying to debug problems:
def self.search(query: false, active: true, **extra)
# ...
end
# Method explicitly asks for particular arguments, but then you call it like this:
User.search(params)
Personally, I think that code is a bit smelly.
However ... personal opinion aside, how I would fix it would be to monkey-patch the ActionController::Parameters class and add a #to_h method which structured the data as you need it to pass to a method like this.
Using to_unsafe_hash is unsafe because it includes params that are not permitted. (See ActionController::Parameters#permit) A better approach is to use to_hash:
params.to_hash.symbolize_keys
or if you have nested params:
params.to_hash.deep_symbolize_keys
Reference: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-to_hash

How do I accept these params in a ruby on rails?

Ok so i want to declare a method that will accept params like this
Lets say my method is named custom_method
#variable.custom_method(Profile, :as => #user)
My method should catch the variable #user and call the #user.profile
but how do i catch the second param which is a hash
The simpler solution is probably:
def custom_method(association, args)
obj = args[:as] or fail("Missing argument :as => obj")
obj.send(association.name.underscore)
...
end
Richer calls to functions, but the downside is clear: the method signature loses information.

How to override to_json in Rails?

Update:
This issue was not properly explored. The real issue lies within render :json.
The first code paste in the original question will yield the expected result. However, there is still a caveat. See this example:
render :json => current_user
is NOT the same as
render :json => current_user.to_json
That is, render :json will not automatically call the to_json method associated with the User object. In fact, if to_json is being overridden on the User model, render :json => #user will generate the ArgumentError described below.
summary
# works if User#to_json is not overridden
render :json => current_user
# If User#to_json is overridden, User requires explicit call
render :json => current_user.to_json
This all seems silly to me. This seems to be telling me that render is not actually calling Model#to_json when type :json is specified. Can someone explain what's really going on here?
Any genii that can help me with this can likely answer my other question: How to build a JSON response by combining #foo.to_json(options) and #bars.to_json(options) in Rails
Original Question:
I've seen some other examples on SO, but I none do what I'm looking for.
I'm trying:
class User < ActiveRecord::Base
# this actually works! (see update summary above)
def to_json
super(:only => :username, :methods => [:foo, :bar])
end
end
I'm getting ArgumentError: wrong number of arguments (1 for 0) in
/usr/lib/ruby/gems/1.9.1/gems/activesupport-2.3.5/lib/active_support/json/encoders/object.rb:4:in `to_json
Any ideas?
You are getting ArgumentError: wrong number of arguments (1 for 0) because to_json needs to be overridden with one parameter, the options hash.
def to_json(options)
...
end
Longer explanation of to_json, as_json, and rendering:
In ActiveSupport 2.3.3, as_json was added to address issues like the one you have encountered. The creation of the json should be separate from the rendering of the json.
Now, anytime to_json is called on an object, as_json is invoked to create the data structure, and then that hash is encoded as a JSON string using ActiveSupport::json.encode. This happens for all types: object, numeric, date, string, etc (see the ActiveSupport code).
ActiveRecord objects behave the same way. There is a default as_json implementation that creates a hash that includes all the model's attributes. You should override as_json in your Model to create the JSON structure you want. as_json, just like the old to_json, takes an option hash where you can specify attributes and methods to include declaratively.
def as_json(options)
# this example ignores the user's options
super(:only => [:email, :handle])
end
In your controller, render :json => o can accept a string or an object. If it's a string, it's passed through as the response body, if it's an object, to_json is called, which triggers as_json as explained above.
So, as long as your models are properly represented with as_json overrides (or not), your controller code to display one model should look like this:
format.json { render :json => #user }
The moral of the story is: Avoid calling to_json directly, allow render to do that for you. If you need to tweak the JSON output, call as_json.
format.json { render :json =>
#user.as_json(:only => [:username], :methods => [:avatar]) }
If you're having issues with this in Rails 3, override serializable_hash instead of as_json. This will get your XML formatting for free too :)
For people who don't want to ignore users options but also add their's:
def as_json(options)
# this example DOES NOT ignore the user's options
super({:only => [:email, :handle]}.merge(options))
end
Hope this helps anyone :)
Override not to_json, but as_json.
And from as_json call what you want:
Try this:
def as_json
{ :username => username, :foo => foo, :bar => bar }
end

Pass method chains to to_json

I know you can pass methods the values of which you want to be available to json objects like so:
# user.rb
def name
first_name + last_name
end
# some controller
render :json => #user.to_json(:methods => :name)
But if I want to massage the value returned from the method a bit (with a text helper say) is there a way to do that? I guess another way to ask this is does #to_json support arbitrary attributes? If not, why not? Has anyone else ran into this before?
You can use "render :json" to specify arbitrary attributes in the JSON output. Here is an example:
render :json => { :arbitraryAttribute => arbitrary_method_to_call(), :user => #user.to_json }
The above code would generate JSON like the following:
{
"arbitraryAttribute":"returnValueOfMethodCall",
"user":{ the result of #user.to_json }
}

Resources