I want to retrieve all the embedded documents of a document to return as a "list" of json elements.
I have documents as follows:
class Parent
include Mongoid::Document
field :name, :type => String
embeds_many :kids
class Kid
include Mongoid::Document
field :kidname, :type => String
embedded_in :parent, :inverse_of => :kids
I have defined two routes
get 'parents/:kidname' => 'parents#getparents'
where getparents is defined as
#retval = Parent.where("kids.kidname" => params[:kidname])
respond_to do |format|
format.html # index.html.erb
format.json { render json: #retval}
end
This gives me the correct output, i.e., the parent whose kid's name is params[:kidname]
However when I try to do the reverse, i.e., retrieve the list of all kids whose parent's name is params[:name], that doesn't work! The route is
get 'kids/:name' => 'parents#getkids'
and getkids is defined as
def getkids
#parent = Parent.where("name" => params[:name])
#kids = #parent.kids
respond_to do |format|
format.html # index.html.erb
format.json { render json: #kids}
end
end
What am I doing wrong? Does it matter where getkids is defined .. I defined it in parents_controller, should it be in kids_controller? Please help!
Thanks.
if you re not getting the results or getting some error change this line #parent = Parent.where("name" => params[:name]) to #parent = Parent.where("name" => params[:name]).first
Related
I have a controller that render a json with data from models. When i enter route to get it it do nothing and just show and error in console.
Controller:
class Api::ForecastController < ApplicationController
before_action :set_hourly_forecast, only: %i[ show edit update destroy ]
def index
respond_to do |format|
format.html do
#hourly_forecasts = HourlyForecast.where(forecast_location_id: params[:forecast_location_id]).paginate(:page => params[:page], :per_page=>24) if params[:forecast_location_id].present?
end
format.json do
weather_service = WeatherService.where(name: params[:name])
#forecast_location = ForecastLocation.where(weather_service_id: weather_service)#& municipality: #municipality.name)
#hourly_forecasts = HourlyForecast.where(forecast_location_id: forecast_location.id ).paginate(:page => params[:page], :per_page=>24) if params[:forecast_location_id].present?
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_hourly_forecast
#hourly_forecast = HourlyForecast.find(params[:id])
end
# Only allow a list of trusted parameters through.
def hourly_forecast_params
params.require(:hourly_forecast).permit(:forecast_location_id, :date, :temperature, :humidity, :rain, :rain_probability, :wind_speed, :wind_direction)
end
end
Error:
> Started GET "/api/forecast.json?name=dark_sky" for 127.0.0.1 at 2022-04-20 18:33:29 +0200
Processing by Api::ForecastController#index as JSON
Parameters: {"name"=>"dark_sky"}
No template found for Api::ForecastController#index, rendering head :no_content
Completed 204 No Content in 53ms (ActiveRecord: 6.2ms | Allocations: 4983)
The route i use its
127.0.0.1:3000/api/forecast.json?name=dark_sky
So the output should be a json with all hourly model.
But it just do nothing and on console it does get and the select but jumps this error of template, i dont understand it since im new on rails.
If need more controllers, models or anything ask in comments.
You have to have a separate template file to render json index.json.jbuilder for example.
# app/views/api/forecasts/index.json.jbuilder
json.array! #hourly_forecasts do |hourly_forecast|
json.extract! hourly_forecast, :id, :forecast_location_id, :date, :temperature, :humidity, :rain, :rain_probability, :wind_speed, :wind_direction
json.url api_forecast_url(hourly_forecast, format: :json)
end
https://github.com/rails/jbuilder
If you don't need to customize rendered json too much, render json inline in the controller
format.json do
weather_service = WeatherService.where(name: params[:name])
#forecast_location = ForecastLocation.where(weather_service_id: weather_service)#& municipality: #municipality.name)
#hourly_forecasts = HourlyForecast.where(forecast_location_id: forecast_location.id ).paginate(:page => params[:page], :per_page=>24) if params[:forecast_location_id].present?
render json: #hourly_forecasts
end
https://guides.rubyonrails.org/layouts_and_rendering.html#rendering-json
https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
I am using acts_as_api to provide a JSON response for some models in my system. My API related code is (reduced to make the example easier):
# File app/modes/item.rb
# API
acts_as_api
api_accessible :v1_list do |template|
template.add :id
template.add :client_name
end
This API is working as expected. It is important to know that client_name is a method containing:
def client_name
client.name
end
That is, the client name is not included in the item model but in the client model. Thus, this info is not contained in the items table.
Using Bullet gem I have noticed that a N+1 query is being performed in the clients table. For each item, a SQL query to the clients table is also performed.
I know that ActiveRecord has some utilities in the API that avoids N+1 queries, and I would like to know if there is a way to use that ActiveRecord feature together with acts_as_api gem.
The gem documentation shows this
def index
#users = User.all
#Note that it’s wise to add a root param when rendering lists.
respond_to do |format|
format.xml { render_for_api :name_only, :xml => #users, :root => :users }
format.json { render_for_api :name_only, :json => #users, :root => :users }
end
end
So for your case you should simply eager load the client association
def index
#items = Item.includes(:client).all
# Note that it’s wise to add a root param when rendering lists.
respond_to do |format|
format.xml { render_for_api :name_only, :xml => #items, :root => :items }
format.json { render_for_api :name_only, :json => #items, :root => :items }
end
end
My goal is to be able to update a saved record when items are de-selected from a collection_select (and then that record is resubmitted.) Thanks in advance for your help!
Details
I've got a form for Newsavedmaps. Newsavedmaps can have many waypoints. Users can select waypoints in a collection_select, and when they save the Newsavedmap, these waypoints are saved to their own database table.
The problem: when users open the Newsavedmap they've saved, I want them to be able to de-select a waypoint. When they save the Newsavedmap again, I want the de-selected waypoint to be deleted.
This is a Rails 2.3X app I'm maintaining, which is why the collection_select uses a different format below.
Model
class Newsavedmap < ActiveRecord::Base
belongs_to :itinerary
has_many :waypoints, :dependent => :destroy
accepts_nested_attributes_for :waypoints, :reject_if => lambda { |a| a[:waypointaddress].blank? }, :allow_destroy => true
end
View
<% form_for #newsavedmap, :html => { :id => 'createaMap' } do |f| %>
<%= f.error_messages %>
<%= f.text_field :name, {:id=>"savemap_name", :size=>30 }%></p>
<%= collection_select :waypoints, :waypointaddress, #newsavedmap.waypoints, :waypointaddress, :waypointaddress, {}, { :multiple => true, :class => "mobile-waypoints-remove", :id =>"waypoints" } %>
<% end %>
Newsavedmaps Controller
def create
#newsavedmap = Newsavedmap.new(params[:newsavedmap])
waypoint = #newsavedmap.waypoints.build
respond_to do |format|
if #newsavedmap.save
flash[:notice] = 'The new map was successfully created.'
format.html { redirect_to "MYURL"}
format.xml { render :xml => #newsavedmap, :status => :created, :location => #newsavedmap }
else
format.html { render :action => "new" }
format.xml { render :xml => #newsavedmap.errors, :status => :unprocessable_entity }
end
end
end
def update
#newsavedmap = Newsavedmap.find(params[:id])
if #newsavedmap.itinerary.user_id == current_user.id
respond_to do |format|
if #newsavedmap.update_attributes(params[:newsavedmap])
flash[:notice] = 'Newsavedmap was successfully updated.'
format.html { redirect_to "MYURL" }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #newsavedmap.errors, :status => :unprocessable_entity }
end
end
else
redirect_to '/'
end
end
Params when creating new record
Parameters: {"newsavedmap"=>{"name"=>"Name of my map", OTHER FIELDS NOT SHOWN ABOVE, "waypoints"=>{"waypointaddress"=>["1600 Pennsylvania Ave NW, Washington, DC 20500", "350 5th Ave, New York, NY 10118"]}}
I think your problem is in correctly built form (and params which come from it).
I suggest to
look at gem cocoon or nested_form
add waypoints_attributes to attr_accessible
implement helpers from gem (from point #1) in your form
And Rails magic should done other job
Another variant (without using gems) (I think much more difficult way!)
You can remove accept_nested_attributes at all and work with your params directly. But in this case you should manage manually all process: correct inserting records, correct destroying them.
In your case it should smth like this (it is not tested!). The example based on your params which posted in the question.
def create
# delete params 'waypoints' it will be manage manually
waypoints = params[:newsavedmap].delete(:waypoints)
#newsavedmap = Newsavedmap.new(params[:newsavedmap])
waypoints.each do |waypoint|
#newsavedmap.waypoints.build(:waypointaddress => waypoint)
end
if #newsavedmap.save
...
end
end
the main troubles will be in method update
def update
# delete params 'waypoints' it will be manage manually
waypoints = params[:newsavedmap].delete(:waypoints)
# find and setup attributes
#newsavedmap = Newsavedmap.find(params[:id])
#newsavedmap.attributes = params[:newsavedmap]
# TROUBLES start here
# destroy not checked (but existed in DB) waypoints
existed_waypoints = #newsavedmap.waypoints
existed_waypoint_addresses = existed_waypoints.map(&:waypointaddress)
new_waypoints = []
waypoints.each do |waypoint|
if existed_waypoint_addresses.include?(waypoint)
# existed waypoint was selected on the form
# find it and add to new_waypoints
new_waypoints << #newsavedmap.waypoints.find_by_waypointaddress(waypoint)
else
# new waypoint was selected on the form
# build it and add to new_waypoints
new_waypoints << #newsavedmap.waypoints.build(:waypointaddress => waypoint)
end
end
# setup new records for waypoints
#newsavedmap.waypoints = new_waypoints
if #newsavedmap.save
# destroy existed but not selected waypoints
(existed_waypoints - new_waypoints).map(&:destroy)
...
end
end
I'm trying to use Rspec-rails on my application but I fail when I try to verify the response of my request.
This is my code :
# app/controllers/user_controller.rb
# GET api/users/:id
def show
#user = User.find(:id)
respond_to do |format|
format.json {render template: "/api/users/detailed"}
end
end
...
# spec/controllers/api/users_controller_spec.rb
require "spec_helper"
describe Api::UsersController do
include Devise::TestHelpers
describe "GET #show" do
user = FactoryGirl.build(:user)
it "assigns the requested user to #user" do
get :show, :format => 'json', :id => user.id
assigns(:user).should eq(#user)
end
it "renders the #detailed view" do
get :show, :format => 'json', :id => user.id
response.should be_respond_to("api/users/detailed")
#expect(response).to render_template("api/users/detailed")
#expect(subject).to render_template("/api/users/detailed")
#render(:template => "/api/users/detailed.json.rabl", :format => "json")
#response.should render_template("/api/users/detailed.json.rabl")
end
end
I commented a part of what I have tested.
I just want to be sure that my controller respond_to a template called "api/users/detailed".
Most often, I have the following error :
expecting <"api/users/detailed"> but rendering with <"">
I'm pretty sure my request called the controller because the first describe "assigns the requested user to #user" works.
I have a collection of #clients with attributes id and email
I want to render this json format
[
{"id":" 1","label":"johndoe#yahoo.com","value":"1"},{"id":" 2","label":"paulsmith#gmail.com.com","value":"2"}
]
in clients_controller I defined the following method
def search
#clients = Client.where(:user_id => current_user.id).select('id','email')
render :partial => "clients/search"
end
and here is the view _search.json.erb
[
<%= raw #client.map{|client| '{"id":"' +" #{client.id}" +'","label":"' + "#{client.email}" + '","value":"' +"#{client.id}" +'"}' }.join(",") %>
]
this is working, but I found it fugly...is there a more elegant way to generate a custom json format in a view?
Use a helper function you call from the view to format the output or a library function you call from the controller. Example (of later):
def search
#clients = Client.where(:user_id => current_user.id).select('id','email')
respond_to do |format|
format.html
format.json do
render :json => custom_json_for(#clients)
end
end
end
private
def custom_json_for(value)
list = value.map do |client|
{ :id => " #{client.id}",
:label => client.email.to_s,
:value => client.id.to_s
}
end
list.to_json
end
You just need use the to_json method. In you case it's
#client.to_json(:only => [:id, :label, :value])
You could use jBuilder gem from GitHub
for clients_controller
def search
#clients = Client.where(:user_id => current_user.id)
end
and search.json.jbuilder
json.id #clients.id
json.label #clients.email
json.value #clients.id
For more info you can visit Jbuilder on RailsCast
You can use https://github.com/dewski/json_builder/ to customize your json response in the view and separate it from the controller. It's good when you need to add some "current user" depending attributes like
[{:attending => event.attending?(current_user)}]