In my heroku RoR app,I have a controller in which I support JSON requests. There I have the following functions:
def mssg_as_json
#message = Message.new
#message.text = params.require(:messages)
#message.save
string = "http://link.com/"
#message.url = string + #message.id.to_s
#message.save
render json: { url: #message[:url] }
end
def return_mssg_as_json
if #message = Message.find_by(id: params[:id])
render json: { message: #message[:text] }
else
render json: {errors: :not_found}, status: :not_found
end
end
I want to support XML requests too. My idea is to somehow convert the XML to JSON but I have no idea how. How can I modify the code to support both XML and JSON?
P.S.
My routes are:
get "messages/api" => "messages#return_mssg_as_json"
post "messages/api" => "messages#mssg_as_json"
The requests are send to main_url/messages/api
So, you should take advantage of the standard routes and actions. In routes.rb, you might do something like:
Rails.application.routes.draw do
resources :messages
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :messages
end
end
end
Note that your controller is now nested inside api/v1. That allows you to identify the path as an api and to maintain versions over time. That solid's practice. Also note that you have a standard messages resource for your web app.
Then, your controller would look like:
class Api::V1::MessagesController < Api::V1::BaseController
def create
#message = Message.new(message_params)
respond_to do |format|
if #message.save
#message.update(url: "http://link.com/#{#message.id}")
format.json { render json: create_hsh, status: :ok }
format.xml { render xml: create_hsh, staus: :ok }
else
format.json { render json: #message.errors, status: :unprocessable_entity }
format.xml { render xml: #message.errors, status: :unprocessable_entity }
end
end
end
def show
respond_to do |format|
if #message = Message.find_by(id: params[:id])
format.json { render json: show_hsh, status: :ok }
format.xml { render xml: show_hsh, status: :ok }
else
format.json { render json: {errors: :not_found}, status: :not_found }
format.xml { render xml: {errors: :not_found}, status: :not_found }
end
end
end
private
def create_hsh
#message.attributes.with_indifferent_access.slice(:url)
end
def show_hsh
attr = #message.attributes.with_indifferent_access
attr.slice(:foo, :bar).merge!(message: attr[:text])
end
def message_params
params.require(:message).permit(:text)
end
end
Note that this controller inherits from Api::V1::BaseController. That would be a controller that you set up to do api-relevant client authentication (key/token checking, etc.). Something, perhaps, like:
class Api::V1::BaseController < ActionController::API
before_action :authorize
def authorize
# do your checks in a way that results in a variable #authorized?
render(json: {errors: :unauthorized}, status: :unauthorized) unless #authorized?
end
end
So, now you're using a single controller action to respond to all format types (that you elect to offer). Then, you clients would post something like:
http://your.api.com/messages.json
To get a json response. Or:
http://your.api.com/messages.xml
To get an xml response. You might notice the bit that says: namespace :api, defaults: {format: 'json'} do. This means that your client could call:
http://your.api.com/messages
and it will default to the json format.
Now you don't have a bunch of random endpoints (like mssg_as_json), just the regular RESTful ones that your clients will expect. Your API clients will love you for that.
You'll note that show_hsh is the accepted code from your earlier question.
Related
I am using rails attr_encrypted gem for encrypting data before storing in database. It works fine on my application as it encrypts with the provided key and decrypts it using the same key via my application. But when I create a instance with my rails console, it does not encrypt with the key that is provided in the application (
uses some random key each time maybe) and hence I am not able to decrypt it when I see that instance in my application.
Below picture shows that if I create the user with the same name twice in console, each time the encrypted data is different. I am following the tutorial on this page
When I try to access the page on my application, the user made by console are showing this error
Here is my code for my model.rb file and am using a temporary key for demo purpose:
class Model < ActiveRecord::Base
attr_encrypted_options.merge!(:encode => true)
attr_encrypted :user, key: "aMI9uV87sL46Nwv+8qeAOUp5nsvzp5C/FkVAOFkcCtk="
attr_encrypted :password, key: "aMI9uV87sL46Nwv+8qeAOUp5nsvzp5C/FkVAOFkcCtk="
end
Here is my controller code:
class ModelsController < ApplicationController
before_action :set_model, only: [:show, :edit, :update, :destroy]
# GET /models
# GET /models.json
def index
#models = Model.all
end
# GET /models/1
# GET /models/1.json
def show
end
# GET /models/new
def new
#model = Model.new
end
# GET /models/1/edit
def edit
end
# POST /models
# POST /models.json
def create
#model = Model.new(model_params)
respond_to do |format|
if #model.save
format.html { redirect_to #model, notice: 'Model was successfully created.' }
format.json { render :show, status: :created, location: #model }
else
format.html { render :new }
format.json { render json: #model.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /models/1
# PATCH/PUT /models/1.json
def update
respond_to do |format|
if #model.update(model_params)
format.html { redirect_to #model, notice: 'Model was successfully updated.' }
format.json { render :show, status: :ok, location: #model }
else
format.html { render :edit }
format.json { render json: #model.errors, status: :unprocessable_entity }
end
end
end
# DELETE /models/1
# DELETE /models/1.json
def destroy
#model.destroy
respond_to do |format|
format.html { redirect_to models_url, notice: 'Model was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_model
#model = Model.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def model_params
params.require(:model).permit(:user, :password, :host)
end
end
respond_with is acatually meant to use with ActiveModel's instances. I tried to use it with OpenStruct's instance, but it raises an error.
Is that ever possible to use respond_with with custom objects?
class CryptController < ApplicationController
respond_to :json
def my_action
respond_with OpenStruct.new(foo: 'foo', bar: 'bar')
end
# ...
end
Raises: **undefined method persisted?' for nil:NilClass**
ruby-2.1.4#rails4/gems/actionpack-4.2.5.1/lib/action_dispatch/routing/polymorphic_routes.rb:298:inhandle_list'
/home/workstat/.rvm/gems/ruby-2.1.4#rails4/gems/actionpack-4.2.5.1/lib/action_dispatch/routing/polymorphic_routes.rb:206:in polymorphic_method'
/home/workstat/.rvm/gems/ruby-2.1.4#rails4/gems/actionpack-4.2.5.1/lib/action_dispatch/routing/polymorphic_routes.rb:114:inpolymorphic_url'
respond_with is a helper method that exposes a resource to mime requests.
From the documentation
respond_with(#user)
for the create action, is equivalent (assuming respond_to :xml in the example) to:
respond_to do |format|
if #user.save
format.html { redirect_to(#user) }
format.xml { render xml: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.xml { render xml: #user.errors, status: :unprocessable_entity }
end
end
end
The precise equivalent is dependent upon the controller action.
The key takeaway is that respond_with takes a #instance variable as an argument and first attempts to redirect to the corresponding html view. Failing that, it renders an xml response, in the case above.
You are passing in an ostruct, which doesn't correspond to an instance of your model. In this case, respond_with doesn't know where to redirect to in your views and doesn't have an instance from which to render a mime response.
See this RailsCast and this blogpost from José Valim.
A note: The error undefined method persisted? is generated by Devise and probably because it can't find a route.
This code is for a UserList (a user can create a User To-Do List). This particular resource does not hold the list items, but just the title of the list, and the type of list.
class Api::V1::UserListsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
def index
if authenticate_user
user_lists = #current_user.user_lists
if user_lists
respond_with user_lists, each_serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's lists."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def show
if authenticate_user
user_lists = #current_user.user_lists
user_list = user_lists.find_by_id(params[:id])
if user_list
respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not find user's list."}, status: :not_found
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def create
if authenticate_user
user_list = #current_user.user_lists.new(user_list_params)
if (user_list.save!)
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not create new User List."}, status: :unprocessable_entity
end
else
render json: { error: "User is not signed in." }, status: :unauthorized
end
end
def update
if authenticate_user
user_list = #current_user.user_lists.find_by_id(params[:id])
if (user_list.update_attributes(user_list_update_params))
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
#respond_with user_list, serializer: Api::V1::UserListSerializer
else
render json: { error: "Could not update User List." }, status: :unprocessable_entity
end
end
end
private
def user_list_params
params.require(:user_list).permit(:user_id, :type_id, :title)
end
def user_list_update_params
params.require(:user_list).permit(:type_id, :title)
end
end
Now the update works when I PUT/PATCH... but I get a
Completed 204 No Content in 24ms (ActiveRecord: 4.3ms)
It's been about 4+ months since I've done any rails, and back then I was only just beginning to learn it.
1) Does anyone know why I'm not getting anything back? I know it's something to do with my respond_with line of code in update, but I'm not sure exactly what.
2) Can someone clarify to me the difference between the SHOW respond_with and the CREATE respond_with. I recall having an issue grasping this back then, and obviously now.
SHOW
respond_with user_list, serializer: Api::V1::UserListSerializer
CREATE
respond_with :api, :v1, #current_user, user_list, serializer: Api::V1::UserListSerializer
a) Why does create require :api and :v1 first, but show does not?
b) Why does create require the #current_user, but show does not?
Appendix: Here is my Serializer for reference
class Api::V1::UserListSerializer < ActiveModel::Serializer
attributes :id, :user_id, :type_id, :title
has_many :items, embed: :ids
end
I know this is 2 years too late, but after some digging, I found the empty response with the 204 is intentional (as mentioned above). If you use respond_with this will always be the case. A workaround would be to use render instead (example below):
class Api::V1::ItemsController < ApplicationController
respond_to :json
...
def update
#item = Item.find(params[:id]
if #item
#item.update_attribute(item_params)
render json: #item
end
end
...
end
You're not supposed to get anything back other than the 204. Any intelligent client does not need to receive back the data it just sent you -- it needs only confirmation that the data was persisted.
Do not mistakenly pass your class Api::V1::UserListSerializer as a key/value pair (Hash form). You will get an error including the text class or module needed. It should look like this:
serialize :some_array, Api::V1::UserListSerializer
Or, perhaps clearer would be:
serialize(:some_array, Api::V1::UserListSerializer)
You miss one param and you are rendering an object class with no content : 204 - No Content
That may seem obvious, but it is common to be in the habit of passing things as a key/value pair.
One improve:
before_action :authenticate_user, only: [:create, :show, :update, ...]
https://apidock.com/rails/ActiveRecord/Base/serialize/class
def update
#item = Item.find(params[:id])
respond_with(:api, :v1, #item) do |format|
if #item.update(item_params)
format.json { render json: #item}
else
format.json { render json: {error: #item.errors.full_messages}}
end
end
end
Am using Rails 3.0.19 and JBuilder Gem 2.0.6 to render JSON responses.
JBuilder: https://github.com/rails/jbuilder
Following is the code am using to send error-messages for a specific API.
render :json, :template=>"/api/shared/errors.json.jbuilder", :status=> :bad_request
For some reason, the client receives 200-ok status. While, I have expected 400 (bad_request).
Any help, please?
Here is my code in detail:
def render_json_error_messages
#render :template=> "/api/shared/errors.json.jbuilder", :status=> :bad_request, :formats => [:json]
respond_to do |format|
format.json {
render :template=> "/api/shared/errors.json.jbuilder", :status=> 400
}
end
end
And in a before_filter method, I use render_json_error_messages
This works:
controller
def some_action
render status: :bad_request
end
some_action.jbuilder
json.something "test"
Try rendering jbuilder to string then set the status... works in Rails 4.1.4
jstr = render_to_string( template: 'api/shared/index.jbuilder', locals: { nodes: #nodes})
respond_to do |format|
format.html
format.json { render json: jstr, status: :bad_request }
end
Else following also works
format.json { render template: 'api/shared/index.jbuilder', status: 404 }
I don't know about Rails 3.0, but I was able to provide the appropriate status by simply adding a respond_to block to the controller action. As an example, I have an create like so:
def create
# ... create logic
respond_to do |format|
format.html
format.json {
render status: :created, success: true
}
end
The above code sets my status code to 201 and renders app/views/orders/create.json.jbuilder
Hope that helps.
maybe reverse the thinking:
#pictures = ...
respond_to do |format|
format.html
format.json do
if #error.present? # #error contains some error message
render json: #error, status: :unprocessable_entity
else
render template: 'api/shared/index.jbuilder'
end
end
api/shared/index.jbuilder
json.array! #pictures, :id, :filename, :width, :height
works in Rails5.1.4
(I've broken out the 2nd question that originally was part of this post into a separate post)
I am creating a product landing page with Rails in which users can enter their email address to be notified when the product launches. (Yes, there are services/gems etc that could do this for me, but I am new to programming and want to build it myself to learn rails.)
On submit of the form, if there are errors, the app currently redirects to '/invites' I would like to instead display error messages on the same page/URL as the original form? (In my case, the form is located at root while the error messages are displaying at '/invites')
I have read the Rails Guide on Routes and numerous stackoverflow posts on handling form errors nothing I've found seems to answer the question I have.
Update: Based on the reply from #rovermicrover I would like to clarify that, while I'm open to an Ajax solution, I'm fine with a page refresh that displays the error message. (I was not able to get the recommendation by #rovermicrover to function as desired - see my response to that solution below for more details.)
What I did:
Invite model:
class Invite < ActiveRecord::Base
attr_accessible :email
validates :email, :presence => {:message => "Please enter an email address."}
end
My routes file:
SuggestionBoxApp::Application.routes.draw do
root to: 'invites#new'
resources :invites
end
This is what I have in the Invites controller (I've only included the actions I'm referencing: new, create, show - it's basically the default of what Rails might generate):
class InvitesController < ApplicationController
def show
#invite = Invite.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #invite }
end
end
def new
#invite = Invite.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
format.json { render json: #invite, status: :created, location: #invite }
else
format.html { render action: "new" }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end
end
Please let me know if there is any additional info I can provide in helping to answer this question. Thanks!
Make the form 'remote'
form_for #invite, :remote => true
....
Then in the controller
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
format.html { redirect_to #invite }
format.js { render :action => 'create_suc'}
else
format.html { render action: "new" }
format.js { render :action => 'create_fail' }
end
end
end
/invites/create_suc.js.erb
$('#errors').remove()
$('#new_invite').prepend("<div class='Thanks'>Thanks for signing up</div>")
$('#new_invite').hide("")
/invites/create_fail.js.erb
$('#new_invite').html('<%= escape_javascript render("form", :invite => #invite) %>');
Forms is a partial with your.... form in it, and also the handling of all errors on #invite.
There is a way to do this without resorting the making the form submit "remote", from a pure Ruby on Rails perspective. However, you can do this only if the browser has enabled cookies.
The idea is to save the form data in the session information in case of an error.
Just remember to delete the session data in case of success.
def new
#invite = Invite.new(session[:invite])
respond_to do |format|
format.html # new.html.erb
format.json { render json: #invite }
end
end
def create
#invite = Invite.new(params[:invite])
respond_to do |format|
if #invite.save
session.delete(:invite)
format.html { redirect_to #invite }
format.json { render json: #invite, status: :created, location: #invite }
else
session[:invite] = params[:invite]
format.html { render action: "new" }
format.json { render json: #invite.errors, status: :unprocessable_entity }
end
end
end