Caching a JSON result in Rails - ruby-on-rails

I have the following controller that returns a list of tags when it receives an HTTP request to /tags
class TagsController < ApplicationController
caches_page :index
def index
respond_to do |format|
format.json {
render :json => Tag.all(:order => "name").to_json
}
end
end
end
I'm noticing that whenever a request is made to /tags, Rails is generating a cache file at /public/tags.json. However, it never seems to use this cache file. Instead, it always runs the SQL query to retrieve the tags:
Started GET "/tags" for 127.0.0.1 at 2011-06-15 08:27:29 -0700
Processing by TagsController#index as JSON
Tag Load (0.7ms) SELECT "tags".* FROM "tags" ORDER BY name
Write page <project root path>/public/tags.json (0.3ms)
Completed 200 OK in 35ms (Views: 1.1ms | ActiveRecord: 0.7ms)
Why isn't Rails using the cache file that's being generated? Is it because the request is for /tags and not /tags.json?

i think you are probably correct, you can specify the :cache_path option to tell it what to name the file so do
caches_page :index, :cache_path => '' # if not try 'tags'
you can also pass a proc, if you want to include params
caches_page :index , :cache_path => Proc.new {|controller| controller.params }
or anything else

Your cache directory could be in the wrong place. I had this issue before where I was trying to place the cache in a directory other than public.

Related

Wrong number of arguments 0 for 1 unable to serialize model using devise

I am trying to create an api for my rails application in the Controllers folder I have created the following folder structure
controllers > api > v1
my routes look something like this
require 'api_constraints'
MyApp::Application.routes.draw do
devise_for :users
resources :users
......
other resources and matching for the standard application
......
# Api definition
namespace :api, defaults: { format: :json },constraints: { subdomain: 'api' }, path: '/' do
scope module: :v1,constraints: ApiConstraints.new(version: 1, default: true) do
resources :sessions, :only => [:create]
resources :users, :only => [:show]
end
end
end
I get the same error in both my sessions and users controllers. I will just post the User controller because it's shorter
class Api::V1::UsersController < ApiController
respond_to :json
def show
respond_with User.find(params[:id])
end
end
Then my tests are
require 'spec_helper'
describe Api::V1::UsersController do
describe "GET #show" do
before(:each) do
#user = FactoryGirl.create :user
get :show, id: #user.id, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql #user.email
end
it { should respond_with 200 }
end
end
And the output from the test is
4) Api::V1::UsersController GET #show
Failure/Error: get :show, id: #user.id, format: :json
ArgumentError:
wrong number of arguments (0 for 1)
Two problems are 1) for some reason the id isn't getting sent to the api action
2) I'm not sure how to get to the api. I thought it should be api.localhost:3000/users/1
Thanks in advance for any help
Update
This is the output for rake routes
api_sessions POST /sessions(.:format) api/v1/sessions#create {:format=>:json, :subdomain=>"api"}
api_user GET /users/:id(.:format) api/v1/users#show {:format=>:json, :subdomain=>"api"}
Update 2
This looks like a duplicate for wrong number of arguments (0 for 1) while create my user
Unfortunately the solution to this post isn't an option for me. I can't remove Devise because the User model is shared in a standard Web rails application and the api portion of the application
Update 3
I was looking at other ways of having a API with a standard app, and using devise with doorkeeper seems like a better solution than token authentication. After getting it setup I am back in the same situation of
wrong number of arguments (0 for 1)
In the server output I see the following output. This is with a valid user id.
Started GET "/api/v1/users/1" for ::1 at 2015-07-27 20:52:09 +0100
Processing by Api::V1::UsersController#show as */*
Parameters: {"id"=>"1"}
Geokit is using the domain: localhost
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Completed 500 Internal Server Error in 21ms
ArgumentError (wrong number of arguments (0 for 1)): app/controllers/api/v1/users_controller.rb:5:in `show'
With an invalid id I get this output
Started GET "/api/v1/users/134" for ::1 at 2015-07-27 20:55:36 +0100
Processing by Api::V1::UsersController#show as */*
Parameters: {"id"=>"134"}
Geokit is using the domain: localhost
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 134]]
Completed 500 Internal Server Error in 6ms
NoMethodError (undefined method `api_error' for
#<Api::V1::UsersController:0x007fc5a17bf098>): app/controllers/api_controller.rb:23:in `not_found'
Update 4
After inserting some debug statements the ID is being passed through to the controller action and the user is being retrieved from the database.
The issue is with
respond_with User.find(params[:id])
rails is unable to serialize the user. I have tried replacing User with another model that does not have Devise enabled and it can serialize the model. I'm not sure why devise is causing an issue here.
1: Verify in console that FactoryGirl is able to properly return a user object created from your user factory so that isn't nil in your get request.
2: Run rake routes to verify what the API routes its generating look like. I'm assuming if you haven't previously set this up, you will need to edit your hosts file or use something like pow on mac or nginx w/ dnsmasq on Linux to enable subdomain support in your local development environment. Then manually test your API controller by whatever subdomain you configured like http://api.myappname.dev/api/v1/users/1.json to make sure you can see it returing a valid JSON response from that URL.
A little bit cleaner example of API namespacing in routes:
namespace :api, :path => "", :constraints => {:subdomain => "api"} do
namespace :v1, defaults: { format: 'json' } do
...
end
end
This took a while for me to find out the solution and you can trace what I was thinking from the post above.
In the end, I traced the issue to that a model using devise cannot be serialized like a normal model. You can't just use
respont_with User.first
or anything like that. Devise disables the default to_json method for activerecord.
A lot of solutions talked about overwriting the to_json method or using JBuilder in the model. I didn't like this because it would restrict the possible attributes you could return.
After looking at the documentation for to_json I decided that simply passing the attributes I wanted in the options was exactly what I wanted.
def show
respond_with User.find(params[:id]).as_json(only: [:id, :name])
end

respond_to :json ActionController::UnknownFormat

I am adding an api to an existing rails app that is not working like I want it to. I had the error below that I fixed with the fix below. I am looking for a proper explanation of why my original code was not working (was it working in older versions of rails? I'm on 4.2). I understand why the fix is working but does it have any drawbacks to the original code.
My route:
namespace :api do
namespace :v1 do
resources :users, :only => [:show]
end
end
My controller:
class Api::V1::UsersController < ApplicationController
respond_to :json
def show
respond_with User.find(params[:id])
end
end
My error:
ActionController::UnknownFormat
Processing by Api::V1::UsersController#show as HTML
Parameters: {"id"=>"1"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
The fix:
def show
#user = User.find(params[:id])
render :json => #user
end
My guess is that it was not clear from the request url and header parameters which format your application should return.
In your first version the request url should end with .json or the request should have a HTTP Accept header with the value application/json. Otherwise there is no way to tell that this was a request that should return JSON data.
In your second version you just say: Hey, return this as JSON, no matter what format the request has.
there was no bug you requested /api/v1/users/1 which defaults to look for html.
You could make your intention clear calling: /api/v1/users/1.json or in your routes:
namespace :api, defaults: { format: :json } do
This is mean that you make request in unsupported format (in your case not in JSON). Look at this code for more details.
I think you forget to specify Accept header or add .json suffix at the end of your URL.

Rails file download not working in JSON API with devise token authentication

After ruining one working day, I am desperate for help.
I have a Rails 4.1.0 application which also exposes a JSON API. In the API I am using devise with token_authentication. There is an Image model with paperclip attachment. While trying to download the image, the postman plugin shows success with status 200. But the image isn't downloaded.
I have tried using both the send_file and send_data method. For both the server shows two log entries. While debugging also, I can see that the 'authenticate_user_from_token' method of ApiController gets executed twice and in the second time the 'X-Auth-Token header' is missing(which is obvious because I am not sending this second request). This results in a 401 Unauthorized error and the file isn't downloaded(See logs at the bottom). I am not sure why send_file or send_data method is causing a second request to server.
Here is my code.
controllers/api/v1/images_controller.rb
class Api::V1::ImagesController < Api::V1::ApiController
def download
#image = Image.find(params[:id])
# Tried send_file
send_file #image.pic.path(:original)
# Tried send_data
data = File.read(#image.pic.path(:original))
send_data data, filename: #image.pic.original_filename, type: #image.pic.content_type
end
end
controllers/api/v1/api_controller.rb
class Api::V1::ApiController < ApplicationController
respond_to :json
before_filter :authenticate_user_from_token!
before_filter :authenticate_user!
protect_from_forgery with: :null_session
private
def authenticate_user_from_token!
user_email = params[:email].presence
user = user_email && User.find_by_email(user_email)
auth_token = request.headers["X-Auth-Token"]
if user && Devise.secure_compare(user.authentication_token, auth_token)
sign_in user, store: false
end
end
end
controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
models/image.rb
class Image < ActiveRecord::Base
PAPERCLIP_ROOT = "#{Rails.root}/storage"
has_attached_file :pic, :styles => {:original => "720x720", :medium => "120x120", :thumbnail => "40x40"}
end
config/routes.rb
Rails.application.routes.draw do
#API
namespace :api , defaults: {format: :json} do
namespace :v1 do
resources :images do
member do
get 'download'
end
end
devise_scope :user do
post "/sign_in", :to => 'sessions#create'
post "/sign_out", :to => 'sessions#destroy'
end
end
end
end
server_log
Started GET "/api/v1/images/14/download?email=user#example.com" for 127.0.0.1 at 2014-09-21 13:57:25 +0530
ActiveRecord::SchemaMigration Load (0.7ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by Api::V1::ImagesController#download as */*
Parameters: {"email"=>"user#example.com", "id"=>"14", "image"=>{}}
.
.
Sent file /Users/rajveershekhawat/workspace/dine_connect/storage/images/pics/000/000/014/original/images.jpg (0.5ms)
Completed 200 OK in 14067ms (ActiveRecord: 13.5ms)
Started GET "/api/v1/images/14/download?email=user#example.com" for 127.0.0.1 at 2014-09-21 13:57:40 +0530
Processing by Api::V1::ImagesController#download as HTML
Parameters: {"email"=>"user#example.com", "id"=>"14"}
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'user#example.com' LIMIT 1
Completed 401 Unauthorized in 3462ms
Please help. Thanks a lot.
Update:
Can somebody tell me why there are two request logs?
Can we even download, an image over json api whithout using Base64 encoding, like a normal download?
Try using send_data instead of send_file?
You might have to do a read on the paperclip file path.

Route interprets JSON request as HTML?

Using Ruby 1.8.7 and Rails 3.2.12.
I am experiencing an issue when testing URLs with a ".json" extension. I'm building custom error pages and have the following:
# errors_controller.rb
def show
#exception = env["action_dispatch.exception"]
respond_to do |format|
format.json { render :json => { :error => #exception.message, :status => request.path[1..-1] } }
format.html { render :file => File.join(Rails.root, 'public', request.path[1..-1]), :format => [:html], :status => request.path[1..-1], :layout => false }
end
end
# routes.rb
match ":status" => "errors#show", :constraints => { :status => /\d{3}/ }
# application.rb
config.exceptions_app = self.routes
For URLs such as "localhost:3000/session/nourl.json", I trigger the HTML block of respond_to, and I can verify that the server responds with the HTML format with these logs:
Processing by ErrorsController#show as HTML
Parameters: {"status"=>"404"}
Rendered public/404.html (13.2ms)
Completed 404 Not Found in 48ms (Views: 47.3ms | ActiveRecord: 0.0ms)
The only way I've been able to trigger the JSON block is with :format => :json in the route, then it works fine but "localhost:3000/session/nourl" would respond with JSON too.
It feels like I am doing something foolish here because I've seen other examples of both cases being triggered in the expected way and I see absolutely no other cases of similar behavior, so I'm compelled to think this is an isolated situation or it's some cascading issue that I cannot observe or am causing elsewhere.
If anyone could provide some insight on potential issues I would be appreciative.
Updated:
A little more info: If I query something like "localhost:3000/locations/1.json", I get the expected response; a JSON formatted page with the object details. I can't achieve this same behavior when requesting arbitrary URLs with a ".json" extension and attempting to format a custom JSON response to return. Is there a way to do this?
Rails delegates the call to the Error-Application where all the request-format stuff gets lost. So you will need to check that on your own. You could check on the request information like this:
def api_request?
env['ORIGINAL_FULLPATH'] =~ /^\/api/
end
def json_request?
env['ORIGINAL_FULLPATH'] =~ /\.json$/
end
Read more about this approach here: http://phillipridlen.com/notes/2012/12/13/returning-multiple-formats-with-custom-dynamic-error-pages-in-rails/

Backbone Mongoid Rails set up will not let me update a record

Can someone help me understand how to make Ryan Bate's screencast on Backbone.js work with MongoDB as my database while using the Mongoid gem.
This is what I am seeing.
When I create a new entry via the console, similar to how Ryan did it in the video with entry.create, Rails adds that entry just fine. Below is my Ruby log and my JavaScript headers log from Chrome Inspector.
Ruby Log
Started POST "/api/entries" for 127.0.0.1 at 2012-02-12 17:31:24 -0600
Processing by EntriesController#create as JSON
Parameters: {"name"=>"Heather", "entry"=>{"name"=>"Heather", "action"=>"create", "controller"=>"entries"}}
MONGODB w_market_development['system.namespaces'].find({})
MONGODB w_market_development['entries'].insert([{"_id"=>BSON::ObjectId('4f384bcc504b9348be000003'), "name"=>"Heather"}])
Completed 201 Created in 11ms (Views: 2.4ms)
Headers Log
Request URL:http://0.0.0.0:3000/api/entries
Request Method:POST
Status Code:201 Created
Request Headers (14)
Request Payload
{"name":"Heather"}
As you can see it posted fine. Now let me show you an update via the entry.save() example Ryan showed us.
Ruby Log
Started POST "/api/entries" for 127.0.0.1 at 2012-02-12 17:34:25 -0600
Processing by EntriesController#create as JSON
Parameters: {"_id"=>"4f38152c504b9345dc000005", "name"=>"Bloip", "winner"=>true, "entry"=>{"_id"=>"4f38152c504b9345dc000005", "name"=>"Bloip", "winner"=>true, "action"=>"create", "controller"=>"entries"}}
MONGODB w_market_development['system.namespaces'].find({})
MONGODB w_market_development['entries'].insert([{"_id"=>BSON::ObjectId('4f38152c504b9345dc000005'), "name"=>"Bloip", "winner"=>true}])
Completed 201 Created in 12ms (Views: 2.7ms)
Headers Log
Request URL:http://0.0.0.0:3000/api/entries
Request Method:POST
Status Code:201 Created
Request Headers (14)
Request Payload
{"_id":"4f38152c504b9345dc000005","name":"Bloip","winner":true}
As you can see when I complete the entry.save() on a current entry, which should be an update, the JSON is showing a POST instead of a PUT, which Mongoid is doing nothing with and the DB shows no changes. After Googling I found the following articles but nothing really helped.
https://github.com/codebrew/backbone-rails/issues/8
http://lostechies.com/derickbailey/2011/06/17/making-mongoid-play-nice-with-backbone-js/
When I was going through the RailsCast as described above. I was using the entries controller that Ryan put together. After much searching, copying, pasting, and retrying I found that I need a completely new Controller set up. Below is what I originally had.
class EntriesController < ApplicationController
respond_to :json
def index
respond_with Entry.all
end
def show
respond_with Entry.find(params[:id])
end
def create
respond_with Entry.create(params[:entry])
end
def update
respond_with Entry.update(params[:id], params[:entry])
end
def destroy
respond_with Entry.destroy(params[:id])
end
end
This is the Controller code the fixed the issue for me.
class EntriesController < ApplicationController
def index
render :json => Entry.all
end
def show
render :json => Entry.find(params[:id])
end
def create
entry = Entry.create! params
render :json => entry
end
def update
entry = Entry.find(params[:id])
entry.update_attributes!(params[:entry])
render :json => entry
end
def destroy
render :json => Entry.destroy(params[:id])
end
end
Thanks All!
Travis

Resources