I am using ETag caching for a Rails (4.1.1) action with the stale? method; however it does not take the request format into consideration. Example: if /stations.json has been loaded by the user and they then click a link to /stations they will get a cached JSON response instead of html.
Am I doing it wrong or is this a Rails bug?
# GET /stations
# GET /stations.json
def index
#title = "Stations"
#last_updated = Station.order("updated_at asc").last
if stale?(#last_updated, last_modified: #last_updated.try(:updated_at))
#stations = all_with_latest_observation
respond_to do |format|
format.html
format.json { render json: #stations }
end
end
end
I think you should add the key :etag when you call stale? method.
# GET /stations
# GET /stations.json
def index
#title = "Stations"
#last_updated = Station.order("updated_at asc").last
if stale?(etag: #last_updated, last_modified: #last_updated.try(:updated_at))
#stations = all_with_latest_observation
respond_to do |format|
format.html
format.json { render json: #stations }
end
end
end
Then let's see what's happen!
This seems to be a bug in ActionPack
I found a workaround
class ApplicationController
etag { request.format }
end
And of course the spec:
describe StationsController do
describe "GET index" do
describe "ETag" do
before { get :index, format: 'json' }
it "should not use the same ETag for different content types" do
get :index, format: 'json'
first_response = response.headers.clone
get :index, format: 'html'
expect(first_response['ETag']).to_not eq (response.headers['ETag'])
end
end
end
end
Related
I built my first api and it works as expected, but I am having a hard time understanding why I can't limit the results in my request. All the searches I've seen on the subject list the limit param the same as I have it listed below. My api is built with Grape inside Rails 5. Any ideas what I am doing wrong?
https://www.soledadmemorial.com/api/v1/plaques?limit=10
controllers/api/v1/plaques.rb
module API
module V1
class Plaques < Grape::API
include API::V1::Defaults
resource :plaques do
desc 'Return all Plaques'
get '', root: :plaques do
Plaque.all
end
desc 'Return a Plaque'
params do
requires :id, type: String, desc: 'ID of Plaque'
end
get ':id', root: 'plaque' do
Plaque.where(id: permitted_params[:id]).first!
end
end
end
end
end
plaques_controller.rb
class PlaquesController < ApplicationController
before_action :authenticate_user!, :except => [:index, :show]
before_action :set_plaque, only: [:show, :edit, :update, :destroy]
# GET /plaques
# GET /plaques.json
def index
#plaquelist = Plaque.search(params[:search]).paginate(:per_page => 10, :page => params[:page])
#plaques = Plaque.all
end
# GET /plaques/1
# GET /plaques/1.json
def show
#plaques = Plaque.all
end
# GET /plaques/new
def new
#plaque = Plaque.new
end
# GET /plaques/1/edit
def edit
end
# POST /plaques
# POST /plaques.json
def create
#plaque = Plaque.new(plaque_params)
respond_to do |format|
if #plaque.save
format.html { redirect_to #plaque, notice: 'Plaque was successfully created.' }
format.json { render :show, status: :created, location: #plaque }
else
format.html { render :new }
format.json { render json: #plaque.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /plaques/1
# PATCH/PUT /plaques/1.json
def update
respond_to do |format|
if #plaque.update(plaque_params)
format.html { redirect_to #plaque, notice: 'Plaque was successfully updated.' }
format.json { render :show, status: :ok, location: #plaque }
else
format.html { render :edit }
format.json { render json: #plaque.errors, status: :unprocessable_entity }
end
end
end
# DELETE /plaques/1
# DELETE /plaques/1.json
def destroy
#plaque.destroy
respond_to do |format|
format.html { redirect_to plaques_url, notice: 'Plaque was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_plaque
#plaque = Plaque.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def plaque_params
params.require(:plaque).permit(
:veteran_war,
:veteran_biography,
:veteran_first,
:veteran_last,
:veteran_display_name,
:group_type,
:veteran_nickname,
:group_name,
:veteran_middle,
:veteran_rank,
:veteran_branch,
:grid, :wall,
:direction,
:row,
:plaque_num,
:image,
:search
)
end
end
I'm not 100% sure which action it's going through - but to assume index for now:
def index
#plaquelist = Plaque.search(params[:search]).paginate(:per_page => 10, :page => params[:page])
#plaques = Plaque.all
return unless params[:limit]
#plaquelist = #plaquelist.limit(params[:limit])
#plaques = #plaques.limit(params[:limit])
end
I've not tested that but in theory if there is a limit param (which there is on the URL you gave) then it will take that limit and apply it to the objects you're returning. At the moment your controller isn't doing any logic with the limit parameter even if it's present.
This logic will 'return' after your code if there is no limit paramater, and if there is will change the results being given from the controller.
EDIT: can also be tidied up a bit but will leave the fun parts to you :D
I want to get json response according to filters. Now on page load I'm getting json response for the every properties which are listed in that page.
In the same page I have filters, so if I search property through filters according to that json response should change.
class PropertiesController < ApplicationController
def index
if params[:city].present?
#properties=Property.where("properties.city = ? ",params[:city],"%#{params[:city]}%")
elsif params[:cityname].present?
#properties=Property.where("properties.city = ? ",params[:cityname])
else
#properties = Property.where("properties.status = ?", '1')
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: #properties.as_json(only: [:id, :latitude, :longitude]) }
end
end
end
What is wrong with my code?
I wondering what would be the best practice to perform next task.
I have a search results to display from index action. Every individual record displays in the pop up through show action.
What I would love to do is to execute pop up if there is only one record found.
Here what I already tried.
def index
#companies = Company.search(params[:query]).results
#count = #companies.total
if #count == 1
return
render company_path
end
end
Seems like return, redirect_to or render aren't play well in one action.
Any other thought of doing it?
UPDATE added show action
def show
sleep 1/2
client = Elasticsearch::Client.new host:'127.0.0.1:9200', log: true
response = client.search index: 'companies', body: {query: { match: {_id: params[:id]} } }
#company = response['hits']['hits'][0]['_source']
respond_to do |format|
format.html # show.html.erb
format.js # show.js.erb
format.json { render json: #company }
end
# more code
end
The return is definitely killing you, but you're trying to render / redirect to a path for a specific resource without specifying the resource. I've taken a stab at something that might work a bit better for you:
class MyController
before_action :find_companies, only: :index
before_action :find_company, only: :show
before_action :show_company_if_matched, only: :index
def index
# do whatever you were doing here...
end
def show
respond_to do |format|
format.html # show.html.erb
format.js # show.js.erb
format.json { render json: #company }
end
# more code
end
private
def find_companies
#companies = Company.search(params[:query]).results
end
def find_company
client = Elasticsearch::Client.new host:'127.0.0.1:9200', log: true
response = client.search index: 'companies', body: {query: { match: {_id: params[:id]} } }
#company = response['hits']['hits'][0]['_source']
end
def show_company_if_matched
redirect_to company_path(#comapnies.first) if #companies.total == 1
end
end
EDIT: Updated to include show action
This is correct syntax :
def index
#companies = Company.search(params[:query]).results
#count = #companies.total
if #count == 1
render company_path # no params ?
return
else
redirect_to root_path
return
end
end
Use return after render or redirect it's good practice, because in some cases 'render' or 'redirect_to' do not do 'return' (cf: best practice ruby)
Remove the return from your controller. If I've understood your question, this should result in the behavior you're looking for:
if #count == 1
render company_path
else
# Do something else
end
If there is subsequent code in the controller that you do not want to execute, you can render and return as follows:
if #count == 1
render company_path and return
else
# Do something else
end
I want to grab content from a website, that I input into a submit form, and store that info as a json I can save to my db. I am trying to use HTTParty, but I'm not quite sure how to implement it to grab the data. Here is what I have so far.
controller
class UrlsController < ApplicationController
before_action :set_url, only: [:show, :edit, :update, :destroy]
#require "addressable/uri"
#Addressable::URI.parse(url)
# GET /urls
# GET /urls.json
def index
#urls = Url.all
end
# GET /urls/1
# GET /urls/1.json
def show
end
# GET /urls/new
def new
#url = Url.new
end
# GET /urls/1/edit
def edit
end
def uri?(string)
uri = URI.parse(string)
%w( http https ).include?(uri.scheme)
rescue URI::BadURIError
false
rescue URI::InvalidURIError
false
end
# POST /urls
# POST /urls.json
def create
#url = Url.new(url_params)
#app_url = params[:url]
respond_to do |format|
if #url.save
format.html { redirect_to #url, notice: 'Url was successfully created.' }
format.json { render action: 'show', status: :created, location: #url }
wordcount
else
format.html { render action: 'new' }
format.json { render json: #url.errors, status: :unprocessable_entity }
end
end
end
def wordcount
# Choose the URL to visit
#app_url = #url
#words = HTTParty.get(#app_url)
# Trick to pretty print headers
#wordcount = Hash[*#words]
end
# PATCH/PUT /urls/1
# PATCH/PUT /urls/1.json
def update
respond_to do |format|
if #url.update(url_params)
format.html { redirect_to #url, notice: 'Url was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #url.errors, status: :unprocessable_entity }
end
end
end
# DELETE /urls/1
# DELETE /urls/1.json
def destroy
#url.destroy
respond_to do |format|
format.html { redirect_to urls_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_url
#url = Url.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def url_params
params.require(:url).permit(:url)
end
end
That is my controller.rb. I am getting a 'bad argument (expected URI object or URI string)' from the line #words = HTTParty.get(#app_url) I need to change what url is put into the form to a valid URL, grab the content I want from that URL, and save that information.
Try something like this:
response = HTTParty.get('https://google.com')
puts response.body, response.code, response.message, response.headers.inspect
To answer your question you can implement the following method, make a new class or put it in a helper.
Might need to include HTTParty
def url_getter(url)
HTTParty.get(url)
end
and call it:
url_getter(#app_url)
My Rails app has a player class that works perfectly. Players can be created, deleted, and updated from my rails control panel without any issues.
I would like a remote counterpart to be able to join in the fun by creating players using a JSON request. Following the advice of the auto generated Rails comments above my create method : # POST /players.json I have started sending requests to localhost:3000/players.json
The JSON
{
"player": {
"name": "test",
"room_id": 0,
"skin_id": 1,
"head_id": 2,
"torso_id": 3,
"legs_id": 4,
"x_position": 5,
"y_position": 6,
"direction": 7,
"action": "",
"gender": "f"
}
}
However, I am running into this error message:
ActionController::ParameterMissing in PlayersController#create
param not found: player
So I guess my question is: How should I structure the JSON I am sending?
Additional info:
Ruby Version: 2.0
Rails Version: 4.0
I have tried sending my requests using Postman
Update - Player Params
Here is the player params method from my controller (as requested):
def player_params
params.require(:player).permit(:name, :room_id, :skin_id, :head_id, :torso_id, :legs_id, :x_position, :y_position, :direction, :action, :gender)
end
Update 2 - Player controller
class PlayersController < ApplicationController
before_action :set_player, only: [:show, :edit, :update, :destroy]
skip_before_filter :verify_authenticity_token, :if => Proc.new { |c| c.request.format == 'application/json' }
# GET /players
# GET /players.json
def index
#players = Player.all
end
# GET /players/1
# GET /players/1.json
def show
end
# GET /players/new
def new
#player = Player.new
end
# GET /players/1/edit
def edit
#rooms = Room.all.map { |room| [room.name, room.id] }
end
# POST /players
# POST /players.json
def create
#player = Player.new(player_params)
respond_to do |format|
if #player.save
format.html { redirect_to #player, notice: 'Player was successfully created.' }
format.json { render action: 'show', status: :created, location: #player }
else
format.html { render action: 'new' }
format.json { render json: #player.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /players/1
# PATCH/PUT /players/1.json
def update
respond_to do |format|
if #player.update(player_params)
format.html { redirect_to #player, notice: 'Player was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #player.errors, status: :unprocessable_entity }
end
end
end
# DELETE /players/1
# DELETE /players/1.json
def destroy
#player.destroy
respond_to do |format|
format.html { redirect_to players_url }
format.json { head :no_content }
end
end
def manage_furni
#player = Player.find(params[:id])
#furni = Furni.all
end
def add_furni
player = Player.find(params[:id])
player.furnis << Furni.find(params[:furni])
redirect_to manage_furni_path(player)
end
def remove_furni
player = Player.find(params[:id])
item = InventoryItem.find(params[:item])
player.inventory_items.delete(item)
redirect_to manage_furni_path(player)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_player
#player = Player.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def player_params
params.require(:player).permit(:name, :room_id, :skin_id, :head_id, :torso_id, :legs_id, :x_position, :y_position, :direction, :action, :gender)
end
end
Update 3: logs
(
"Processing by PlayersController#create as JSON",
"Completed 400 Bad Request in 31ms",
"ActionController::ParameterMissing (param not found: player):",
"app/controllers/players_controller.rb:103:in `player_params'",
"app/controllers/players_controller.rb:40:in `create'",
"Rendered /Users/drewwyatt/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.5ms)",
"Rendered /Users/drewwyatt/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_trace.erb (0.9ms)",
"Rendered /Users/drewwyatt/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (0.8ms)",
"Rendered /Users/drewwyatt/.rvm/gems/ruby-2.0.0-p247/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (16.3ms)"
){
}
First of all, I think your data format is ok and is not the problem here. When I ran exactly into the same problem it was because I did not send the Content-Type: application/json header along with my request.
In Postman, you can select the 'raw' data format and then 'JSON (application/json)' to include this header. In my case it looks like this:
Alternatively, you can also try it with curl (source):
curl -X POST -H "Content-Type: application/json" -d '{"player": {"name": "test","room_id": 0,"skin_id": 1,"head_id": 2,"torso_id": 3,"legs_id": 4,"x_position": 5,"y_position": 6,"direction": 7,"action": "","gender": "f"}}' localhost:3000/players.json
If you omit the -H "Content-Type: application/json", then you will receive a 400 response with the "param not found" error, if you include it it should work.
If you are trying this:
via Postman - Under form-data tab, you need to have the vars as :
player[name]
player[room_id]
.
.
via jQuery:
$.ajax({ url: 'url', type: 'post', data: { player: { name: 'Test', room_id: '0' } } })