How do you pass a method with arguments to #to_xml? - ruby-on-rails

How can I pass a method with an argument to #to_xml?
#object.to_xml(:methods => [:a_method_with_args] )
Is there a way to do this? What is the correct syntax?
Thanks.

to_xml is supposed to express your model's state. and as such it shouldn't need any external 'location' arguments. if this is really what you need it looks like you need a 'give me an xml representation of my model when on location X'. I guess you can just add a 'set_default_location' to your model and change the price_points_for_location to have a default value for the argument:
attr_writer :default_location
def price_points_for_location(location = #default_location)
...
end

You could try redefining the to_xml method like
def to_xml(location)
# do your stuff
super()
end
But not sure it would work that well. Other option would be to create some new XML view method for your model, like:
def as_xml(location)
self.price_points_for_location(location)
self.to_xml
end

Thanks for the answers, they look like good options. What I actually ended up doing is using a proc. I was aware that I could use procs with to_xml, but it seems that you can't access the current object in the array when iterating over multiple objects. To get around this I did something like this:
price_points = #items.map { |item| item.price_points_for_location(location) }
price_point = Proc.new {|options| options[:builder].tag!('price_points', price_points.shift) }
#items.to_xml(:procs => [price_point])

Related

How to modify the response of as_json in ruby on rails?

I have a model called Event, which in turn has_one association with EventType
event = Event.last
event.as_json(include: {event_type: {only: :name}}, only: :event_type)
This is giving me output
{ :event_type=> { "name"=>"Chat" } }
But my desired response is
{ :event_type => "Chat" }
How can I achieve this?
You could create an instance method on event and include it in the serialized response, using the :methods key (see the documentation for as_json).
But you don't want to create an event_type method, because the association has_one has created one for you. Maybe you can do event_method_name? If you must get exactly the response you wanted, it seems something like JBuilder or Active Model Serializers will give you a better control on the output.
You can do this in event.rb.
def as_json
options = {event_type: event_type[:name]}
super
end
You might also consider creating and calling a new method to avoid overriding the method in case you need the full object elsewhere.
def event_type_as_json
{event_type: event_type[:name]}.as_json
end

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 to get the parent objects class name from an attribute

Is it possible to return the parent object of a given attribute?
Example
a = User.birthdate
a.parent_object ... should return the user record that is the parent of the birthdate attribute
A better example?
Helper
def item_grade(subject, obj)
obj.scale.grades.find(subject.grade_id).name # would return something like "Pass", "Fail", "Good Job"
end
In the view
item_grade(#course.subject, #course)
This approach requires two options to be passed to the helper. It seems I should be able to pass #course.subject and then get the parent object from that
Helper
def item_grade(subject)
a = subject.parent_object.scale
a.grades.find(subject.grade_id).name
end
View
item_grade(#course.subject)
This approach requires two options to be passed to the helper.
You can remove some duplication by doing this, for example.
def item_grade(obj, property)
obj.scale.grades.find(obj.send(property).grade_id).name
end
item_grade(#course, :subject)
Now you don't have to repeat #course in the call.
Having to pass two parameters is much less harmful than any sort of hackery you can come up with (thanks #muistooshort). There's no built-in way to do this.

How to add new attribute to ActiveRecord

After getting all values from model, I want to add another custom attribute to the ActiveRecord class (this attribute is not a column in db) so that I could use it in view, but rails does not allow me to add one. What should I add in its model class?
#test.all
#test.each do |elm|
elm[:newatt] = 'added string'
end
error:
can't write unknown attribute `newatt'
try this
class Test < ActiveRecord::Base
attr_accessor :newattr
end
you can access it like
#test = Test.new
#test.newattr = "value"
As you may notice this a property, not a hash. so it uses . syntax. however, if you need it to behave like an hash you can do this without defining a new attribute
#test.all
#test.each do |elm|
new_elm = {}
new_elm[:newatt] = 'added string'
end
Lastly, I am not exactly sure what you are trying to do. if this doesn't make sense to you, kindly rephrase your question so we can understand the problem better.
Define virtual attributes as instance variables:
attr_accessor :newattr
If you want this only for your views and do not have any other purpose then you need not to add attr_accessor
#test.all.select('tests.*, "added string" as newattr')
here you are adding newattr attribute for query output of ActiveRecord with a value 'added string'
I think you mean to assign #test to the ActiveRecord query, correct? Try:
#test = MyARClass.select("*, NULL as newatt")
#test.each {|t| t[:newatt] = some_value}
Another related solution is to make it a singleton class method, though you'd have to jump though more hoops to make it writeable and I intuitively feel like this probably incurs more overhead
#test = MyARClass.all
#test.each do t
def t.newatt
some_value
end
end
Using the second method, of course you'd access it via #test.first.newatt, rather than #test.first[:newatt]. You could try redefining t.[] and t.[]=, but this is starting to get really messy.
If it's really just temporary it doesn't have to be in the object:
#test.all
#test_temp = []
#test.each do |elm|
#test_temp << {:elm => elm, :newatt => 'added string'}
end
Otherwise, there are also good answers here.
If it temporary, you can try this:
#test.all.map{ |t| t.attributes.merge({ newatt: "added string" }) }
#test.all
#test.each do |elm|
write_attribute(:newatt, "added string")
end
I met the same issue. and successfully bypass using instance_eval
#test.all
#test.each do |elm|
elm.instance_eval { #newatt = 'added string' }
end
normally it doesn't run into issue, when use attr_accessor. it appears when other DSL override "newattr=" which cause the issue. In my case, it's money-rails "monetize :newatt"
Explicitly use write_attribute doesn't work as it is the reason to raise exception in rails 4.x

How to transform a string into a variable/field?

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'

Resources