how to append data to json in ruby/rails? - ruby-on-rails

Say i have this short code:
item = Item.find(params[:id])
render :json => item.to_json
but i needed to insert/push extra information to the returned json object, how do i do that?
Lets say i need to insert this extra info:
message : "it works"
Thanks.

item = Item.find(params[:id])
item["message"] = "it works"
render :json => item.to_json

The to_json method takes an option object as parameter . So what you can do is make a method in your item class called as message and have it return the text that you want as its value .
class Item < ActiveRecord::Base
def message
"it works"
end
end
render :json => item.to_json(:methods => :message)

I found the accepted answer now throws deprecation warnings in Rails 3.2.13.
DEPRECATION WARNING: You're trying to create an attribute message'. Writing arbitrary attributes on a model is deprecated. Please just useattr_writer` etc.
Assuming you don't want to put the suggested attr_writer in your model, you can use the as_json method (returns a Hash) to tweak your JSON response object.
item = Item.find(params[:id])
render :json => item.as_json.merge(:message => 'it works')

How to append data to json in ruby/rails 5
If you use scaffold, e.g.:
rails generate scaffold MyItem
in the view folder you will see next files:
app/view/my_item/_my_item.json.jbuilder
app/view/my_item/index.json.jbuilder
so, you can add custom data to json output for an item, just add this:
json.extract! my_item, :id, :some_filed, :created_at, :updated_at
json.url my_item_url(my_item, format: :json)
json.my_data my_function(my_item)
As you can see, it's possible to modify as one item json output, as index json output.

I always use:
#item = Item.find(params[:id])
render json: { item: #item.map { |p| { id: p.id, name: p.name } }, message: "it works" }

Have you tried this ?
item = Item.find(params[:id])
item <<{ :status => "Success" }
render :json => item.to_json

Related

How to save google.maps.Data.MultiPolygon to geometry datatype column in postgres database in rails?

I am a beginner in rails framework, so please pardon my naive question. I have a google.maps.Data.MultiPolygon object on my frontend which I want to save in my database. The table searches creates a new entry everytime a user searches, contains different columns out of which I have added another column with datatype :geometry, which will be updated when the user draws a polygon at a specific search. So we need to update a search entry in the database, for which I am using put call. I cannot send the whole google.maps.Data.MultiPolygons object on the put call, since $.params() is unable to serialise the object (this problem is the same which is faced here).
var polygons = new google.maps.Data.MultiPolygon([polygon]);
var search_params = $.param({search: $.extend(this.state.search, {user_id: Request.user_id, search_type: search_type})});
Request.put('searches/'+this.state.search['id'], search_params, function(){});
Uncaught TypeError: Cannot read property 'lat' of undefined(…)
So, I would need to send an array of location objects. Is there a specific format in which this array of location object is directly converted to geometry object on the update function? Here is the update function which is called on search update in ruby:
def update
search = Search.find(params[:id])
if search.update_attributes(search_params)
render :json => {:recent => search}, :status => 200
else
render :json => {:error => "Could not update search."}, :status => 422
end
end
And the search_params is:
def search_params
params.require(:search).permit(
:name,
:user_id,
:q,
:drawn_polygons #This is the column I want to be updated with geometry object
)
end
Would something like :drawn_polygons => RGeo::Geos::CAPIMultiPolygonImpl work? If it does, what format of :drawn_polygons object do I need to provide?
OR
I need to take the :drawn_polygons as a list of coordinates, and change it to a RGeo::Geos::CAPIMultiPolygonImpl inside the update function? If so, how to do it?
def update
search = Search.find(params[:id])
search_parameters = search_params
drawn_polygons = JSON.parse(URI.decode(search_parameters[:drawn_polygons]))
# Some code to change the search_params[:drawn_polygons] to geometry object, specifically RGeo::Geos::CAPIMultiPolygonImpl
search_parameters(:drawn_polygons) = drawn_polygons
if search.update_attributes(search_parameters)
render :json => {:recent => search}, :status => 200
else
render :json => {:error => "Could not update search."}, :status => 422
end
end

How can I return custom errors to my form in Rails?

I have a simple form on a website where a user enters a mailing address.
I have a service which can validate this address and return various responses, either Success, Suspect, or Invalid, as well as return the full and most complete zip code for that address. If the response is "Success", then I will save it to the db. If the response is "Suspect", then I will update the zip field and ask them to confirm. If the response is "Invalid" then I will return an error message asking them to contact us in person.
I'm trying to set up my rails create action such that it makes a call to my service (for example http:/addresssValidator.com) and I want to inform the user if they have a valid address or not, and update the zip with the suggested zip code.
Looking for address validation in rails however, seems to only give me APIs for using the built in error and validation system in rails and not how to return my own custom results to the form.
How can I do this?
Below is my code:
def create
#valid = validate_address
#address = Address.new(address_params)
if #valid
if #address.save
redirect_to "/survey/success"
else
p #address.errors
respond_to do |format|
format.json {render json: #address.errors}
format.html {render "/survey/failure"}
end
end
else
##//Display errors
end
end
def validate_address
#api_key = "my_api_key"
HTTParty.post("http://addressValidator.com",
{
:body => [
"StreetAddress" => params[:address][:address_line_2] + " " + params[:address][:address_line_1],
"City" => params[:address][:city],
"PostalCode" => params[:address][:zip],
"State" => params[:address][:state],
"CountryCode" => params[:address][:country],
"Locale" => "en" ].to_json,
:APIKey => #api_key ,
:headers => { 'Content-Type' => 'application/json', 'Accept' => 'application/json'}
})
return false;
##//return actual results from validator
end
If you want to add a custom error to a specific field:
#address.errors[:FIELD_NAME] << "Custom Error Message"
that way the error is added to the instance that you have, and will be shown in the form itself as the other errors appear.
if you just want to display an error, you can add it to the flash[:error] like this:
flash[:error] = "Invalid address"
if you want the error to move to the next method (like if you will use redirect after it), or:
flash.now[:error] = "Invalid address"
if you want the error to be available only in current action.
as #illusionist said in comment.
and in the html code, check if it exists, and if so, print it:
<% if flash[:error] %>
<%= flash[:error] %>
<% end %>
in both ways, you can add the error from #valid.
If you want to store the new zip, add #address.zip = #valid.zip. That way it will show it in the field in the form.
Models do the work, forms are stupid
In Rails you add errors to your models. To add an error you would do something like this:
if #address.country == "Sealand"
#address.errors[:country] << ["we don't ship there"]
end
In Rails forms are just are just simple form-builders bound to a model which create HTML. They don't actually have a role in validation besides displaying errors.
Custom validations
Rails lets you create custom validator classes and HTTParty is made to extend classes.
Lets mosh them together:
class AddressValidator < ActiveModel::Validator
include HTTParty
base_uri 'addressValidator.com'
format :json
def validate(address)
response = self.post('/',
# Note that I am just guessing here.
body: {
"StreetAddress" => address.street_address,
"City" => address.city,
"PostalCode" => address.zip,
"State" => address.state,
"CountryCode" => address.country
}
)
if response.success?
# bind the errors to the model
unless response["StreetAddress"] == "valid"
record.errors[:street_address] << response["StreetAddress"]
end
else
e = response.response
logger.warn "Failed to remotely validate Address. #{ e.message }"
address.errors[:base] << "Failed to remotely validate!"
end
end
end
Of course you will need to adapt this to the actual response from the API and your model. This is just a starting point demonstrating the coarse strokes.
So how would you use it?
def Address < ActiveRecord::Base
validates_with AddressValidator
# ...
end
http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
http://blog.teamtreehouse.com/its-time-to-httparty

Correct way to handle multiparameter attributes corresponding to virtual attributes

I have a Rails app with a model containing a birthdate attribute. This corresponds to a column in my database defined using the ActiveRecord date type. With this I am able to use the date_select form helper method to render this as a three-select input in my view. The form parameters corresponding to this field are then serialized back to the controller as birthdate(1i), birthdate(2i) and birthdate(3i). Consequently, I can use the standard update_attributes method within my controller on my model to update all fields on my model.
I'm now experimenting with encrypting this field using the attr_encrypted gem. While the gem supports marshalling (this is nice), there is no longer a real column of name birthdate of type date - instead, attr_encrypted exposes the value as a virtual attribute birthdate backed by a real encrypted_birthdate column. This means that update_attributes is unable to perform the previous multiparameter attribute assignment to populate and save this column. Instead, I get a MultiparameterAssignmentErrors error resulting from the call to the internal column_for_attribute method returning nil for this column (from somewhere within execute_callstack_for_multiparameter_attributes).
I'm currently working around this as follows:
My model in app/models/person.rb:
class Person < ActiveRecord::Base
attr_encrypted :birthdate
end
My controller in app/controllers/people_controller.rb:
class PeopleController < ApplicationController
def update
# This is the bit I would like to avoid having to do.
params[:person] = munge_params(params[:person])
respond_to do |format|
if #person.update_attributes(params[:person])
format.html { redirect_to #person, notice: 'Person was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
private
def munge_params(params)
# This separates the "birthdate" parameters from the other parameters in the request.
birthdate_params, munged_params = extract_multiparameter_attribute(params, :birthdate)
# Now we place a scalar "birthdate" where the multiparameter attribute used to be.
munged_params['birthdate'] = Date.new(
birthdate_params[1],
birthdate_params[2],
birthdate_params[3]
)
munged_params
end
def extract_multiparameter_attribute(params, name)
# This is sample code for demonstration purposes only and currently
# only handles the integer "i" type.
regex = /^#{Regexp.quote(name.to_s)}\((\d+)i)\)$/
attribute_params, other_params = params.segment { |k, v| k =~ regex }
attribute_params2 = Hash[attribute_params.collect do |key, value|
key =~ regex or raise RuntimeError.new("Invalid key \"#{key}\"")
index = Integer($1)
[index, Integer(value)]
end]
[attribute_params2, other_params]
end
def segment(hash, &discriminator)
hash.to_a.partition(&discriminator).map do |a|
a.each_with_object(Hash.new) { |e, h| h[e.first] = e.last }
end
end
end
And my view app/views/people/_form.html.erb:
<%= form_for #person do |f| %>
<%= f.label :birthdate %>
<%= f.date_select :birthdate %>
<% f.submit %>
<% end %>
What's the proper way to handle this type of attribute without having to introduce ad hoc munging of the params array like this?
Update:
Looks like this might refer to a related problem. And this too.
Another update:
Here is my current solution, based on Chris Heald's answer. This code should be added to the Person model class:
class EncryptedAttributeClassWrapper
attr_reader :klass
def initialize(klass); #klass = klass; end
end
# TODO: Modify attr_encrypted to take a :class option in order
# to populate this hash.
ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS = {
:birthdate => EncryptedAttributeClassWrapper.new(Date)
}
def column_for_attribute(attribute)
attribute_sym = attribute.to_sym
if encrypted = self.class.encrypted_attributes[attribute_sym]
column_info = ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS[attribute_sym]
column_info ||= super encrypted[:attribute]
column_info
else
super
end
end
This solution works as is, but would be even better if attr_encrypted were to take a :class option that would construct the ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS hash dynamically. I'm going to look at ways I can extend/monkeypatch attr_encrypted to do this. Gist available here: https://gist.github.com/rcook/5992293.
You can monkeypatch your model to pass through column_for_attribute calls. I haven't tested this, but it should cause reflection on the birthday field to instead return the reflection for the encrypted_birthday field, which should cause multiparam attributes to properly assign (as AR will then be able to infer the field type):
def column_for_attribute(attribute)
if encrypted = encrypted_attributes[attribute.to_sym]
super encrypted[:attribute]
else
super
end
end
We're patching column_for_attribute per this line so that AR can infer the proper type for the column. It needs to figure out that parameters for "birthday" should be a DateTime type of whatnot, and can't infer that from a virtual attribute. Mapping the reflection onto the actual column should resolve that.

ActiveRecord as_json return different column names

I am trying to return a JSON representation of an ActiveRecord but instead of having the JSON string contain the model's column names for the Keys I would like to have it display something different per column. Is there a way to do this? here is my example line
record.as_json(root: false, :only => [:message, :user])
I basically want it to return the message and the user columns, but I want to call them something else when it gets them.
I think you're overcomplicating this. You only want two columns so why not just do it by hand?
def some_controller
#...
json = {
new_name_for_message: r.message,
new_name_for_user: r.user
}
render json: json, status: :ok
end
Build a two element Hash and hand it off to the JSON rendering system.
record.as_json(root: false, :only => [:user], :methods => [:message_html])
and define that method on record.

Add virtual attribute to json output

Let's say I have an app that handles a TODO list. The list has finished and unfinished items. Now I want to add two virtual attributes to the list object; the count of finished and unfinished items in the list. I also need these to be displayed in the json output.
I have two methods in my model which fetches the unfinished/finished items:
def unfinished_items
self.items.where("status = ?", false)
end
def finished_items
self.items.where("status = ?", true)
end
So, how can I get the count of these two methods in my json output?
I'm using Rails 3.1
The serialization of objects in Rails has two steps:
First, as_json is called to convert the object to a simplified Hash.
Then, to_json is called on the as_json return value to get the final JSON string.
You generally want to leave to_json alone so all you need to do is add your own as_json implementation sort of like this:
def as_json(options = { })
# just in case someone says as_json(nil) and bypasses
# our default...
super((options || { }).merge({
:methods => [:finished_items, :unfinished_items]
}))
end
You could also do it like this:
def as_json(options = { })
h = super(options)
h[:finished] = finished_items
h[:unfinished] = unfinished_items
h
end
if you wanted to use different names for the method-backed values.
If you care about XML and JSON, have a look at serializable_hash.
With Rails 4, you can do the following -
render json: #my_object.to_json(:methods => [:finished_items, :unfinished_items])
Hope this helps somebody who is on the later / latest version
Another way to do this is add this to your model:
def attributes
super.merge({'unfinished' => unfinished_items, 'finished' => finished_items})
end
This would also automatically work for xml serialization.
http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
Be aware though, you might want use strings for the keys, since the method can not deal with symbols when sorting the keys in rails 3. But it is not sorted in rails 4, so there shouldn't be a problem anymore.
just close all of your data into one hash, like
render json: {items: items, finished: finished, unfinished: unfinished}
I just thought I'd provide this answer for anyone like myself, who was trying to integrate this into an existing as_json block:
def as_json(options={})
super(:only => [:id, :longitude, :latitude],
:include => {
:users => {:only => [:id]}
}
).merge({:premium => premium?})
Just tack .merge({}) on to the end of your super()
This will do, without having to do some ugly overridings. If you got a model List for example, you can put this in your controller:
render json: list.attributes.merge({
finished_items: list.finished_items,
unfinished_items: list.unfinished_items
})
As Aswin listed above, :methods will enable you to return a specific model's method/function as a json attribute, in case you have complex assosiations this will do the trick since it will add functions to the existing model/assossiations :D it will work like a charm if you dont want to redefine as_json
Check this code, and please notice how i'm using :methods as well as :include [N+Query is not even an option ;)]
render json: #YOUR_MODEL.to_json(:methods => [:method_1, :method_2], :include => [:company, :surveys, :customer => {:include => [:user]}])
Overwritting as_json function will be way harder in this scenario (specially because you have to add the :include assossiations manually :/
def as_json(options = { })
end
If you want to render an array of objects with their virtual attributes, you can use
render json: many_users.as_json(methods: [:first_name, :last_name])
where first_name and last_name are virtual attributes defined on your model

Resources