Rails3-amf and bringing an association to amf - ruby-on-rails

i am changing from WebORB to Warhammerkids Rails3-amf - great choice, even tehre are some open issues. One those issues is, how can I get records from an association to the array, which is sent back to the Flex-Application.
In WebORB the method of the controller looks like:
def getClients(clientFilter,myMandant)
clients = Client.find(:all, :conditions => {:account_id => myMandant}, :include => [:addresses, :contacts, :proofs])
end
in Rails3-amf I have a similar construction:
def getClients()
#clients = Client.find(:all, :conditions => {:account_id => params[1]}, :include => [:addresses, :contacts, :proofs])
respond_with(#clients) do |format|
format.amf { render :amf => #clients}
end
With this code I get back all correctly typed Client Objects as an Array, but without the records from the ":include" argument.
How can I handle this ??
I also tried another way with:
....
respond_with(#clients) do |format|
format.amf { render :amf => #clients.to_amf(:include => [:addresses, :contacts, :proofs])}
....
With this try I got an error message" undefined method to_amf for #.
Thanks for any help.

I don't know about rail3-amf, but you might find it worthwhile having a look at restfulx - https://github.com/dima/restfulx/tree/rails3
It consists of a library for rails, and a library for flex. It supports data transfer through json, xml, or amf.
The actionscript api for working with models is very nice too:
var user:User = new User();
user.first_name = "Ed";
user.create();
It can also keep track of rails associations etc.:
trace(user.account.title);
See more usage here - https://github.com/dima/restfulx_framework/wiki/Working-with-RestfulX-Models

Related

Rails to_json including association with limit

I am rendering one record with with association like below
render :json => Scheme.where("id=?", params[:id]).first
.to_json(:include => { :navs => { :only => [:schemeCode,:navDate,:navValue] }})
Associations
Scheme has_many navs
Nav belongs_to scheme
I need to render only last record in Nav , but above will print all the navs since its one to many. I tried :limit => 1 and ordering it in desc , buts limit itself not working.Any help will be appreciated.
render :json => Scheme.where("id=?", params[:id]).first
.to_json(:include => { :navs => { :only => [:schemeCode,:navDate,:navValue], :limit => 1 }})
If you're creating a complicated json format, try gem 'jbuilder' introduced in RailsCast to leave the structure to view.
In your show.json.jbuilder view, make it something like:
#scheme = Scheme.find(params[:id])
json.scheme #scheme.as_json
json.extract! #scheme.navs.last, :schemeCode, :navDate, :navValue
which will render the data in json format as well as keep your controller clean & neat.
The use of Scheme.where("id=?", params[:id]).first in your code can be shortened as Scheme.find(params[:id]) if your id is unique.

Rails - Using a before_filter to run a method

I would like this before filter to run every time the page is loaded (for now) to check if an item is over 7 days old or not and if so, run some actions on it to update its attributes.
I have before_filter :update_it in the application controller. update_it is defined below that in the same controller as:
def update_it
#books = Book.all
#books.each do |book|
book.update_queue
end
end
Then update_queue is defined in the book model. Here's everything in the model that pertains to this:
scope :my_books, lambda {|user_id|
{:conditions => {:user_id => user_id}}
}
scope :reading_books, lambda {
{:conditions => {:reading => 1}}
}
scope :latest_first, lambda {
{:order => "created_at DESC"}
}
def move_from_queue_to_reading
self.update_attributes(:queued => false, :reading => 1);
end
def move_from_reading_to_list
self.update_attributes(:reading => 0);
end
def update_queue
days_gone = (Date.today - Date.parse(Book.where(:reading => 1).last.created_at.to_s)).to_i
# If been 7 days since last 'currently reading' book created
if days_gone >= 7
# If there's a queued book, move it to 'currently reading'
if Book.my_books(user_id).where(:queued => true)
new_book = Book.my_books(user_id).latest_first.where(:queued => true).last
new_book.move_from_queue_to_reading
currently_reading = Book.my_books(user_id).reading_books.last
currently_reading.move_from_reading_to_list
# Otherwise, create a new one
else
Book.my_books(user_id).create(:title => "Sample book", :reading => 1)
end
end
end
My relationship is that a book belongs_to a user and a user has_many books. I'm showing these books in the view through the user show view, not that it matters though.
So the errors I keep getting are that move_from_queue_to_reading and move_from_reading_to_list are undefined methods. How can this be? I'm clearly defining them and then calling them below. I really am at a loss and would greatly appreciate some insight into what I'm doing wrong. I'm a beginner here, so any structured criticism would be great :)
EDIT
The exact error message I get and stack trace is as follows:
NoMethodError in UsersController#show
undefined method `move_from_queue_to_reading' for nil:NilClass
app/models/book.rb:41:in `update_queue'
app/controllers/application_controller.rb:22:in `block in update_it'
app/controllers/application_controller.rb:21:in `each'
app/controllers/application_controller.rb:21:in `update_it'
I suspect that the collection returned is an empty array (which is still 'truthy' when tested). So calling .last is returning nil to the new_book and currently_reading local variables. Try changing:
if Book.my_books(user_id).where(:queued => true)
to:
if Book.my_books(user_id).where(:queued => true).exists?
Additionally, you are modifying the scope when finding currently_reading. This can potentially cause the query to again return no results. Change:
currently_reading.move_from_reading_to_list
to:
currently_reading.move_from_reading_to_list if currently_reading

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

Rails 3 respond_to json, with custom attributes/methods

In a rails app I have an action that returns a json representation of a collection of different models. It looks something like this:
respond_to :json
def index
#cars = Car.all
#vans = Van.all
respond_with({
:cars => #cars,
:vans => #vans
})
end
However, I want to customise the attributes and methods that are passed to the json object. A bit like:
respond_with({
:cars => #cars.to_json(:only => [:make, :model], :methods => [:full_name]),
:vans => #vans
})
Doing the above, causes the json representation of the "cars" to be escaped as one big string, like:
{
"cars":"[{\"car\":{\"make\":\"Ford\" ... etc
"vans": [{"van":{"make":"Citreon" ... vans not escaped
}
Obviously I'm approaching this the wrong way. Can anyone point me in the right direction?
Since you're nesting the to_json in another Hash, I think you need to use as_json (which returns a Hash instead of a String) instead:
respond_with({
:cars => #cars.as_json(:only => [:make, :model], :methods => [:full_name]),
:vans => #vans
})

Rails as_json includes with conditions

I have problems to restrict an as_json include by a dynamic attribute:
#pirates_ships = #current_account.pirates.as_json(:include => {:ships => {:only => [:id, :name]}}, :only => [:id, :last_name])
This for sure gives me all pirates with or without their ships.
But I also need to restrict the ships by e.g. ships.ocean_id
I tried resolving it by includes with conditions:
pirates.includes(:ships).where("ships.ocean_id = ?", #ocean.id).as_json(...)
The restriction works, but now all pirates without a ship are lost.
Also no luck with my own JOIN Syntax.
Any ideas?
Ahoy
UPDATE
My solution so far is to manually eager load. This way I can have my dynamic conditions:
#pirates = #current_account.pirates
#ships = #current_account.ships.where({:pirate_id.in => #pirates, :ocean_id => #ocean.id})
render :json => { :pirates => #pirates.as_json(...), :ships => #ships.as_json(...) }
My Ajax callback can now iterate over :pirates and add for each pirate his ships if any.
(I use a JS template engine clientside to generate the view from the JSON response)
Not very elegant, but performance is important in my case.
I'am still open for better ideas.
I tried dynamic has_many :ships, :conditions => ...
but that's a bit fiddly.
I think your best bet would be altering the #pirates_ships hash after generating it from as_json (I tried multiple variations of includes, etc. and couldn't get anything to work).
#pirates_ships = #current_account.pirates.as_json(:include => :ships)
#pirates_ships.each do |pirate|
pirate[:ships].delete_if{ |ship| ship.ocean_id != #ocean.id }
end
# Now, #pirates_ships should contain ALL pirates, as well as ships for #ocean

Resources