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.
Related
I have a form_for which calls to a create method.
The create method is fairly typical, it calls a neighbouring method which houses the strong params:
Params.require(:blog).permit(:title, :body, :user_id, :access)
I create the record then with the following:
Blog.create(blog_params)
Though before I do that, I run a helper method which determines the value of :access - I assign the helper method directly to params[:access] as shown:
params[:access] = myHelper(val1, val2)
But this doesn't work, it just stays as a nil value. Currently, I'm having to do the following:
Blog.create(blog_params).update(access: params[:access])
Which is rather ugly and even dysfunctional when it comes to updating a record. What can I do to keep it tidy?
Your :access param actually lives in params[:blog][:access] before calling params.require(:blog)
So use:
params[:blog][:access] = myHelper(val1, val2)
Or:
bparams = blog_params
bparams[:access] = blog_params
Blog.create(bparams)
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
I'm new to Ruby and I would like to find out what the best way of doing things is.
Assume the following scenario:
I have a text field where the user can input strings. Based on what the user inputs (after validation) I would like to access different fields of an instance variable.
Example: #zoo is an instance variable. The user inputs "monkey" and I would like to access #zoo.monkey. How can I do that in Ruby?
One idea that crossed my mind is to have a hash:
zoo_hash = { "monkey" => #zoo.monkey, ... }
but I was wondering if there is a better way to do this?
Thanks!
#zoo.attributes gives you a hash of the object attributes. So you can access them like
#zoo.attributes['monkey']
This will give nil if the attribute is not present. Calling a method which doesn't exist will throw NoMethodError
In your controller you could use the public_send (or even send) method like this:
def your_action
#zoo.public_send(params[:your_field])
end
Obviously this is no good, since someone can post somehing like delete_all as the method name, so you must sanitize the value you get from the form. As a simple example:
ALLOWED_METHODS = [:monkey, :tiger]
def your_action
raise unless ALLOWED_METHODS.include?(params[:your_field])
#zoo.public_send(params[:your_field])
end
There is much better way to do this - you should use Object#send or (even better, because it raises error if you try to call private or protected method) Object#public_send, like this:
message = 'monkey'
#zoo.public_send( message )
You could implement method_missing in your class and have it interrogate #zoo for a matching method. Documentation: http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-method_missing
require 'ostruct' # only necessary for my example
class ZooKeeper
def initialize
#zoo = OpenStruct.new(monkey: 'chimp')
end
def method_missing(method, *args)
if #zoo.respond_to?(method)
return #zoo.send(method)
else
super
end
end
end
keeper = ZooKeeper.new
keeper.monkey #=> "chimp"
keeper.lion #=> NoMethodError: undefined method `lion'
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.
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