Low-level caching error when storing database query - ruby-on-rails

I'm trying to increase performance in my application by caching database queries. These are simple queries since I need to load and cache all objects.
Here's a shortened version of my application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
def show_all
load_models
respond_to do |format|
format.json { render :json => {"items" => #items}
}
end
end
protected
def load_models
#items = Rails.cache.fetch "items", :expires_in => 5.minutes do
Item.all
end
end
end
But when I try and load this page I get this error:
ArgumentError in ApplicationController#show_all
undefined class/module Item
I've been following low-level caching guides by Heroku posted here: https://devcenter.heroku.com/articles/caching-strategies#low-level-caching
Any ideas what I can do here to get caching working? Is there a better way to accomplish this?

I fixed this issue by storing encoded JSON in Rails.cache.fetch instead of the raw ActiveRecord objects. I then retrieve the stored JSON, decode it, and render it for the view. The finished code looks like this:
def show_all
json = Rails.cache.fetch "Application/all", :expires_in => 5.minutes do
load_models
obj = { "items" => #items }
ActiveSupport::JSON.encode(obj)
end
respond_to do |format|
format.json { render :json => ActiveSupport::JSON.decode(json) }
end
end
def load_models
#items = Item.all
end

Related

Render simple JSON array with RABL in Rails

I've been trying to figure out how to make RABL render a very simple JSON array of string, such as: ["my,"array","of","strings"]
The controller would be
class StringsController < ApplicationController
responds_to :json
def index
#string = ['my','array','of','strings']
respond_with #strings
end
end
And the RABL view must surely start with:
collection #strings, root_object: false
but I cannot figure out how to just output the strings without a node name or within an object...
There is obviously a much more straightforward solution to actually serving up the JSON array, much like what I've put below, but I'm specifically interested in why this seems so complicated in RABL!
class StringsController < ApplicationController
def index
render json: ['my','array','of','strings']
end
end
Neill,
This should work for any arrays, but for your particular array the exact answer is...
In the index.json.rabl use the below code
collection #string, object_root: false
node do |s|
s
en
In your controller use...
def index
#string = ["my","array","of","strings"]
end
To build on top of Mark's answer, I think it works with this:
collection #string, object_root: false
node :value do |s|
s
end
Note I added :value which is the label of the JSON record.
You can do it like that and that need not the rabl view for showing response.
def index
#string = ['my','array','of','strings']
respond_to do |format|
format.json { render :json => {:message => "Success", :my_string => #string } }
format.xml
end
end

Wrong number of arguments in as_json [`timeout` method is reserved!]

I get an error in Rails that is related to one of my models, let's call it Unit. The following code produces an error:
format.json { render :json => #units.as_json }
The error is about wrong number of parameters (0 of 1).
I believe what you want is;
def scheme
#object = MonitoringObject.restricted_find params[:id], session[:client]
#units = Unit.where("object_id = ?", #object.id)
respond_to do |format|
format.html
format.json { render :json => #units[0] }
end
end
Or, if you have your relationships set up between the MonitoringObject and Unit models correctly,
def scheme
#object = MonitoringObject.restricted_find params[:id], session[:client]
#units = #object.units
respond_to do |format|
format.html
format.json { render :json => #units[0] }
end
end
You're supplying it with an :except parameter that's empty.
You'll use an :except condition if there are some attributes you don't want included in the JSON response.
According to the rails guide on views and rendering, you don't need to specify .to_json -- it will be called for you automatically.
It looks to me that the problem may lie in your .restricted_find method somewhere. Can you post the entire stack trace )or link to a github gist that contains it?
I solved the problem. From the Rails source:
module Serialization
def serializable_hash(options = nil)
options ||= {}
attribute_names = attributes.keys.sort
if only = options[:only]
attribute_names &= Array.wrap(only).map(&:to_s)
elsif except = options[:except]
attribute_names -= Array.wrap(except).map(&:to_s)
end
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } # exception here
# ...
end
alias :read_attribute_for_serialization :send
# ...
end
# ...
end
So the real error is that one of the methods returned by calling e.g. Unit.first.attributes.keys.sort (["dev_id", "flags", "id", "inote", "ip", "location_id", "model_id", "name", "period", "phone", "port", "snote", "timeout"]) expects me to pass an argument to it. This method is timeout, it is some private method Rails monkey patches Object with. So the correct solution to the problem is to rename this attribute to something else.

How to DRY up Rails 3 controllers by overriding methods like respond_with?

I'm trying to create a JSONP API for my Rails 3 application. Right now in my controllers, I have a lot of actions which follow this pattern:
# This is from my users_controller.rb, as an example
def index
#users = User.all
respond_with(#users, :callback => params[:callback])
end
While this works as is, I would like to DRY it up by not having to repeat the :callback => params[:callback] in every action's call to respond_with. How can I do this?
Update: One thing I've realized that is ugly about my above code is that the :callback => params[:callback] option will be passed for any response format, not just JSON. The following code is probably more correct:
def index
#users = User.all
respond_with(#users) do |format|
format.json { render :json => #users, :callback => params[:callback]}
end
end
There are a couple ways I've considered to address this problem, but I can't figure out how to make them work:
Override render (perhaps in the application controller) so that it accepts a :jsonp option that automatically includes the :callback => params[:callback] parameter. This way I could change the above code to the following, which is somewhat shorter:
def index
#users = User.all
respond_with(#users) do |format|
format.json { render :jsonp => #users}
end
end
Create a responder that overrides to_json in order to solve my problem. That way I could leave out the block and just call respond_with(#users, :responder => 'MyResponder') to solve the issue. Or perhaps I could include this code in an application responder using plataformatec's responders gem so that respond_with(#users) by itself would be sufficient.
Note that technically, it is incorrect to render JSON with a callback parameter, since you get a JavaScript response (a function call to the JSON-P callback) rather than a JSON result.
So if you have
render :json => my_object, :callback => params[:callback]
and a request for /users?callback=func comes in, Rails would answer
func({…})
with content type application/json, which is incorrect, since the above response is clearly not JSON but JavaScript.
The solution I use is
def respond_with_json(item)
respond_with do |format|
format.json { render :json => item }
format.js { render :json => item, :callback => params[:callback] }
end
end
which responds correctly with or without callback. Applying this to the aforementioned solution, we get:
def custom_respond_with(*resources, &block)
options = resources.extract_options!
if params[:callback]
old_block = block
block = lambda do |format|
old_block.call(format) if block_given?
format.js { render :json => resources[0], :callback => params[:callback] }
end
end
respond_with(*(resources << options), &block)
end
Also note the correction to resources[0], otherwise you end up wrapping resources in an extra array as a result of the splat operator.
THere's a gem that can do this to: rack-jsonp-middleware.
The setup instructions are pretty scant on the site, but I did create a little Rails project that uses it - which you can take a look at the commits and see what I did to get the middleware up and running.
https://github.com/rwilcox/rack_jsonp_example
This is bit 'low-tech' compared to the reponder solution, but what about just creating a private method in your appliation_controller.rb to handle this. The params variable will be available to it and you could pass the #users object to it.
#application_controller.rb
private
def jsonp(my_object)
render :json => my_object, :callback => params[:callback]
end
#controller
def index
#users = User.all
respond_with(#users) do |format|
format.json { jsonp(#users)}
end
end
Thanks to samuelkadolph for helping me in the #rubyonrails IRC channel today. He provided a solution in this gist, copied below for convenience:
def custom_respond_with(*resources, &block)
options = resources.extract_options!
if options[:callback]
old_block = block
block = lambda do |format|
old_block.call(format) if block_given?
format.json { render :json => [] }
end
end
respond_with(*(resources << options), &block)
end
I haven't tried this in my application yet, but I can see that it should work. He also confirmed that I could similarly override the respond_with method itself simply by changing the name of this method and changing the last line of the definition to super(*(resources << options), &block).
I think this will work for me. However, I'm still interested in knowing how to write a custom responder to do the job. (It would be a more elegant solution, IMHO.)
Update: I tried this in my application and it works with some minor changes. Here is the version I'm using now in the private section of my ApplicationController, designed to automatically provide the :callback => params[:callback] option to JSON requests:
def custom_respond_with(*resources, &block)
options = resources.extract_options!
if params[:callback]
old_block = block
block = lambda do |format|
old_block.call(format) if block_given?
format.json { render :json => resources, :callback => params[:callback] }
end
end
respond_with(*(resources << options), &block)
end
Note that I had to change if options[:callback] to if params[:callback] in order to get it working.
You can also check out this answer. basically you can create a "default" respond_to for your controller so you can just make your all your actions default to responding to json.
was that what you were asking?

How do I use respond_with with custom classes in Rails 3?

I am making a JSON API with Rails and it seemed to work fine except for when I use respond_with custom classes (not an ActiveRecord inherited one).
Here is my class:
class JsonResponse
def initialize(data, status)
#data = data
#status = status
end
def as_json(options={})
{
:data => #data,
:status => #status
}
end
end
which is a simple response wrapper. When I try doing this:
def create
unless(Match.find_by_user_id(params[:user_id]))
Match.create(:user_id => params[:user_id])
end
time_response = JsonResponse.new("5", "success")
respond_with(time_response)
end
I get this error:
NoMethodError (undefined method `model_name' for JsonResponse:Class):
app/controllers/matches_controller.rb:9:in `create'
Any ideas? respond_with is driving me crazy.
Your class should response to to_json method
Obviously set :location option in respond_with method. Rails try to create restful route from the object you pass to the method, but because your object is not resource, the error is raised.
I am not sure if this helps but I do not see respond_to...
respond_with works together with respond_to...
respond_to :html, :xml, :json
This can be defined on Controller level
example:
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
def index
respond_with(#users = User.all)
end
def create
#user = User.create(params[:user])
respond_with(#user, :location => users_url)
end
end
and then you can define your json template... don't know if you leave the json template empty if it takes your "JSONResponse" class...
just a thought...

Excluding some ActiveRecord properties from xml rendering in rails

I have an ActiveRecord model that I would like to convert to xml, but I do not want all the properties rendered in xml. Is there a parameter I can pass into the render method to keep a property from being rendered in xml?
Below is an example of what I am talking about.
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :xml => #person }
end
end
produces the following xml
<person>
<name>Paul</name>
<age>25</age>
<phone>555.555.5555</phone>
</person>
However, I do not want the phone property to be shown. Is there some parameter in the render method that excludes properties from being rendered in xml? Kind of like the following example
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :xml => #person, :exclude_attribute => :phone }
end
end
which would render the following xml
<person>
<name>Paul</name>
<age>25</age>
</person>
You can pass an array of model attribute names to the :only and :except options, so for your example it would be:
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :text => #person.to_xml, :except => [:phone] }
end
end
to_xml documentation
I just was wondering this same thing, I made the change at the model level so I wouldn't have to do it in the controller, just another option if you are interested.
model
class Person < ActiveRecord::Base
def to_xml
super(:except => [:phone])
end
def to_json
super(:except => [:phone])
end
end
controller
class PeopleController < ApplicationController
# GET /people
# GET /people.xml
def index
#people = Person.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #people }
format.json { render :json => #people }
end
end
end
I set one of them up for json and xml on every object, kinda convenient when I want to filter things out of every alternative formatted response. The cool thing about this method is that even when you get a collection back, it will call this method and return the filtered results.
The "render :xml" did not work, but the to_xml did work. Below is an example
def show
#person = Person.find(params[:id])
respond_to do |format|
format.xml { render :text => #person.to_xml(:except => [:phone]) }
end
end
The except is good, but you have to remember to put it everywhere. If you're putting this in a controller, every method needs to have an except clause. I overwrite the serializable_hash method in my models to exclude what I don't want to show up. This has the benefits of not having t put it every place you're going to return as well as also applying to JSON responses.

Resources