how to post JSON data to my rails app? - ruby-on-rails

I'm am new to rails. I want to consume my rest API with an android application. So I want to test whether my controller handles POST request or not. I'm using Advanced REST client for chrome to make a POST request.
Errors
Bad request 400 - rest client (chrome)
Update 1:
Log output:
Started POST "/android" for 127.0.0.1 at 2015-04-17 00:51:09 +0530
Error occurred while parsing request parameters.
Contents:
{tablet_id:1,pulse:2,pulse_timestamp:'NOW()'}
ActionDispatch::ParamsParser::ParseError (795: unexpected token at '{tablet_id:1,pulse:2,pulse_timestamp:'NOW()'}'):
My rails controller:
class Android::PulseFeedbacksController < ApplicationController
before_action :set_pulse_feedback, only: [:show, :edit, :update,:destroy]
respond_to json
# GET /pulse_feedbacks
# GET /pulse_feedbacks.json
def index
#pulse_feedbacks = PulseFeedback.all
respond_to do |format|
format.json { render json: #pulse_feedbacks }
format.xml { render xml: #pulse_feedbacks }
end
end
def create
#pulse_feedback = PulseFeedback.new(pulse_feedback_params)
respond_to do |format|
if #pulse_feedback.save
format.html { redirect_to #pulse_feedback, notice: 'Pulse feedback was successfully created.' }
format.json { render :show, status: :created, location: #pulse_feedback }
else
format.html { render :new }
format.json { render json: #pulse_feedback.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_pulse_feedback
#pulse_feedback = PulseFeedback.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def pulse_feedback_params
params[:pulse_feedback]
end
end

Check out the rails routing guides. Specifically at the section where it defines the types of HTTP verbs created for a controller. By default the only POST is :create. But you can edit your routes to explicitly allow POSTs for your own custom APIs by changing Routes.

Maybe this would help:
def pulse_feedback_params
params.require(:pulse_feedback).permit!
end
But of course server log output will be very helpful.

Thank you Guys, after lot of head breaking got it working.
The problem was with my application_controller.rb and my json format above.
I had to comment this line
before_action :authenticate_user!
in my application_controller.rb file
class ApplicationController < ActionController::Base
respond_to :html, :json
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
#before_action :authenticate_user!
end
It wasn't accepting POST request from unauthorized clients(as I was using a chrome-addon to make POST request)
And my json format should have been:
{
"pulse_feedback": {
"tablet_id": "1",
"pulse": "2",
"pulse_timestamp": "NOW()"
}
}

Related

Rails callback check if record exists, else return 404

I'm building an app where users can accept bookings from clients via their own booking page. These unique urls will all be public facing (no auth) and sent to potential clients by the user (this is how my client requested the functionality). When I enter an existing user's booking URL (e.g. https://localhost:3000/users/1/appointments/new) in the browser, the page works perfectly. When I enter the URL for a user that does not exist (e.g. https://localhost:3000/users/5999/appointments/new) I get the following error:
ActiveRecord::RecordNotFound in BookingsController#booking_page
Couldn't find User with 'id'=100
Instead of this error I would like to redirect to the 404 page instead. This is my controller (redirect_to_not_found is not being used, I was testing this in a before_action):
class BookingsController < ApplicationController
before_action :authenticate_user!, except: :booking_page
before_action :set_user, only: :booking_page
layout 'public', only: :booking_page
def booking_page
respond_to do |format|
if #user
format.html { render :booking_page }
format.json { render json: #user, status: :ok }
else
format.html { render(:file => Rails.root.join('public', '404'), :formats => [:html], :status => 404, :layout => false) }
format.json { render json: 'Not Fount', status: :not_found }
end
end
end
private
def redirect_to_not_found
respond_to do |format|
if #user == nil
format.html { render(:file => Rails.root.join('public', '404'), :formats => [:html], :status => 404, :layout => false) }
format.json { render json: 'Not Fount', status: :not_found }
end
end
end
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def bookings_params
params.require(:user_booking).permit(:client_firstname, :client_surname, :client_email, :client_mobile_namber, :services_required, :notes, :date, :start_time, :end_time, :location, :cost, :payment_completed)
end
end
Is there any way I can assign set the #user variable/object before running the booking_page action method and check if the user exists in the database at the same time?
I tried using the accepted answers from here but I still get the same error.
You can add this to your controller to rescue from this error/exception. You can put it in your ApplicationController for app-wide effect or in specific controllers.
rescue_from ActiveRecord::RecordNotFound do |exception|
logger.error "Not found ..."
redirect_to 404_path # You will have to configure this yourself in routes.rb
# ... OR use your method
redirect_to_not_found
end
User.find raises error when record is not in the DB. You can use nil-flavour of finders, e.g. find_by
#user = User.find_by(id: params[:id])
It will set #user to nil if it's not in the DB

rails request spec post syntax

I can't seem to get my head over how to make post requests for testing a url in request spec tests, here's the test code
RSpec.describe "Certifications", type: :request do
describe "denies public access" do
it "for new certification form" do
get new_certification_path
expect(response).to redirect_to new_user_session_path
end
it "for creating certification" do
certification_attributes = FactoryGirl.attributes_for :certification
expect {
post "/certifications", { certification: certification_attributes }
}.to_not change(Certification, :count)
expect(response).to redirect_to new_user_session_path
end
end
end
Which gives the error
1) Certifications denies public access for creating certification
Failure/Error: post "/certifications", { certification: certification_attributes }
ArgumentError:
unknown keyword: certification
I've tried the :certifications => certification_attributes, basically can't get my head over on how to pass params.
The controller under test is, adding only relevant methods to this post.
class CertificationsController < ApplicationController
skip_before_action :authenticate_user!, if: :skip_user_authentication
before_action :set_certification, only: [:show, :edit, :update, :destroy]
# GET /certifications
# GET /certifications.json
def index
#certifications = Certification.all
end
# GET /certifications/1
# GET /certifications/1.json
def show
end
# GET /certifications/new
def new
#certification = Certification.new
end
# POST /certifications
# POST /certifications.json
def create
#certification = Certification.new(certification_params)
respond_to do |format|
if #certification.save
format.html { redirect_to #certification, notice: 'Certification was successfully created.' }
format.json { render :show, status: :created, location: #certification }
else
format.html { render :new }
format.json { render json: #certification.errors, status: :unprocessable_entity }
end
end
end
protected
def skip_user_authentication
request.format.json? && (action_name.eql?('show') || (action_name.eql?('index')))
end
end
I am trying to assert the behaviour of allowing all methods except certifications.json or certifications/1.json to not require authentication, there are other tests which access these URLs and they pass. The part of ensuring it does not allow any other request is where I am stuck. I am using Devise with Omnitauth Google OAuth2 for authentication in this application.
certification_attributes
{
:name=>"Foundation Certification",
:description=>"Foundation Certification",
:terms=>"Foundation Certification",
:seo_meta_keywords=>["laaa", "lalala certifications"],
:seo_meta_description=>"Foundation Certifications"
}
Send request parameters under :params keyword:
post "/certifications", params: { certification: certification_attributes }
^^^^^^
Looks like you have some sort of authentication set up. You need to log the user in before attempting a POST.
Passing of the params to post looks OK. Tricky to say more without seeing your controller.

How to create API in rails 5?

I am new to web service and Rails too. I have a doubt in creating API using rails 5. How do I create an API for a rails 5 application? I can find some tutorials for API only applications using rails 5. But I need both API and views in a single rails 5 application. How should I do that?
You can create a new rails project as usual:
$ rails new my_project
$ cd my_project
$ bundle
And then you can use scaffold to generate some code:
$ rails g scaffold Product name:string price:float
And migrate your database:
$ rails db:migrate # => update the database
You can now have a look at app/controllers/products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
#products = Product.all
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
#product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: #product }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:name, :price)
end
end
As you can see there's respond_to in the create action. This is how you can respond to different request types.
Read more about it: http://edgeapi.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to
I've my own solution to build API very quickly https://github.com/igorkasyanchuk/fake_api at least on the stage of prototyping.
Using this gem I was able to provide a skeleton of the API for Frontend team, later it was changed to regular rails controllers.
While create a RESTful API, According to Rails 5 release notes, generating an API only application will:
Start the application with a limited set of middleware
Make the ApplicationController inherit from ActionController::API instead of ActionController::Base
Skip generation of view files,
I am listing down article/ Blog post's link read those:
Best Pratices for API only rails app
Rails Api only Web app
Rails Token based authentication: API only rails web app
Must read, Api only rails web app
Read this also, rails api only web app
Restful API with rails
Create a REST Api with Rails
Create a REST Api with Rails 5
Read any one of the above blog and just go through others for getting additional details or for the knowledge purpose
Have a Great Coding! 😀👨‍💻👩‍💻
Just build you regular rails app and when you need a API behavior on that method just respond with a json like this
def index
#cards = Card.all
render json: { status: 'Success', message: 'Loaded all cards', data: #cards }, status: :ok
end
If you really need the additional behavior from the controller to you can just take a regular controller and change the inheritance to something like this
class CardsController < ActionController::API
#your code
end
For more information on this specifically checkout Rails API Applications

How to use POST with ruby on rails?

I created a blog application using rails. I'm trying use that as an API for a mobile app. I'm trying to write JSON to POST the content into the application.
#model/post.rb
class Post < ActiveRecord::Base
has_many :comments
belongs_to :category
end
#model/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
end
#model/category.rb
has_many :posts
#controllers/comments_controller
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /comments/new
def new
#comment = Comment.new
end
# GET /comments/1/edit
def edit
end
# POST /comments
# POST /comments.json
def create
#comment = Comment.new(comment_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'comment was successfully created.' }
format.json { render :show, status: :created, location: #comment }
else
format.html { render :new }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:rating, :comment, :post_id, :user_id)
end
end
What will be the JSON for the POST request for posting a blog post and a comment?
How should I pass association through JSON?
Here is the route for that action.
POST /comments(.:format) comments#create
If you want to be able to create a post and comment in the same request you will need to use accepts_nested_attributes_for.
class Post
has_many :comments
accepts_nested_attributes_for :comments
end
The params to create a post and a comment would look something like this:
{
"post" : {
"title": "Hello World",
"comments_attributes": [
{
"rating": 3,
"comment": "Pretty mediocre if you ask me."
}
]
}
}
Rails does a pretty good job at abstracting out the difference between parameters sent as application/x-www-form-urlencoded, multipart or JSON. Dealing with JSON input is just like dealing with regular form input.
Then in your PostsController you would need to whitelist the nested attributes:
class PostsController < ApplicationController
# POST /posts
def create
#post = Post.new(post_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #post, notice: 'comment was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
private
def post_params
params.require(:post).allow(:title, comments_attributes: [:rating, :comment])
end
end
If you want your blog app to provide an API for external apps to use, you'll have to create an API layer within your application. This exposes endpoints that other apps can use to retrieve/post information to/from your site.
One of the first steps would be to scope out an api layer in your routes.rb:
YourBloggingApp::Application.routes.draw do
namespace '/api', defaults: {format: 'json'} do
scope '/v1' do
scope '/blog_posts' do
get '/' => 'api_blog_posts#index'
post '/' => 'api_blog_posts#create'
...
end
end
end
end
Then you can build the appropriate actions within your api controllers:
class Api::BlogPostsController < ApplicationController
def index
#blog_posts = BlogPost.all
end
def create
#blog_post = BlogPost.create(title: params[:title], description: params[:description])
end
...
end
And when a user from your mobile app fills out a form, the form can send a POST request to https://yourappdomain.com/api/v1/blog_posts/, the blog_posts_controller#create action is triggered, which allows you to then pass in the given params sent through the request to create blog posts, comments, or whatever you set up in your project.
This is a very generic example, and there are a lot of specific details to cover on this topic, but this AirPair article provides a decent starting point for building a RESTful API.
{"comment":{"comment":"blah blah blah", "post_id":1}}
I have to pass the post_id in that POST request.

CSRF Error in Production: ActionController::InvalidCrossOriginRequest

I am getting the following error on my comments controller in my production environments only (works fine in development). The user flow is as follows: I have jQuery that runs when a specific button is pushed which renders a partial file to add a new comment, a simple form. The respond_to method for the .js request in the controller is for new.js.erb file. This should be relatively easy to do but something is going wrong in the Rails (I am using Rails 4.1.1) code or on my server (Rackspace Cloud Server). The error is below:
ActionController::InvalidCrossOriginRequest in CommentsController#new
Security warning: an embedded tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.
I have tried the following code in my comments controller (does not work). It simply renders the .js file in the browser as a text string (javascript does not work).
protect_from_forgery except: :new
skip_before_action :verify_authenticity_token
I have tried removing the protect_from_forgery with: :exception method in the application_controller.rb file but it does not work (just renders the javascript in the browser as a text string).
I have tried replacing "protect_from_forgery with: :exception" with "protect_from_forgery with: :null_session" and this does not work either (gives the same InvalidCrossOriginRequest error above).
I am running out of options to fix this. Again, it's only happening in production. On my local machine (via localhost), everything works fine. The code for my comments controller is below:
class CommentsController < ApplicationController
# before_action :set_comment, only: [:show, :edit, :update, :destroy]
before_action :load_topic
before_action :authenticate_user!
# protect_from_forgery except: :new
# skip_before_action :verify_authenticity_token
# GET /comments
# GET /comments.json
def index
#comments = Comment.all
end
# GET /comments/1
# GET /comments/1.json
def show
end
# GET /comments/new
def new
#comment = Comment.new
end
# GET /comments/1/edit
def edit
end
# POST /comments
# POST /comments.json
def create
#comment = #topic.comments.new(comment_params)
#comment.user_id = current_user.id
respond_to do |format|
if #comment.save
format.html { redirect_to #topic, notice: 'Comment was successfully created.' }
format.json { render :show, status: :created, location: #comment }
format.js
else
format.html { redirect_to #article, alert: 'Unable to add comment' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
format.js { render 'fail_create.js.erb'}
end
end
end
# PATCH/PUT /comments/1
# PATCH/PUT /comments/1.json
def update
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to #comment, notice: 'Comment was successfully updated.' }
format.json { render :show, status: :ok, location: #comment }
else
format.html { render :edit }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
# DELETE /comments/1
# DELETE /comments/1.json
def destroy
#comment = #topic.comments.find(params[:id])
#comment.destroy
respond_to do |format|
format.html { redirect_to #topic, notice: 'Comment was successfully deleted.' }
format.json { head :no_content }
format.js
end
end
private
def load_topic
#topic = Topic.find(params[:topic_id])
end
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:topic_id, :body, :name)
end
end
Any advice on fixing this issue would be appreciated.
Has this actually happened to real users or are you only seeing this error in your logs/monitoring?
This error tends to happen when crawlers are visiting your site (which obviously doesn't happen in your development environment).
The documentation is suggesting you add these to your controller action:
skip_before_action :verify_authenticity_token, if: :json_request?
protected
def json_request?
request.format.json?
end
However, if this is not the case, I think you actually have a CORS problem. 2 possible reasons:
Is your site available via HTTP and HTTPS? These are different origins!
Do you have multiple domains running this site? Try to inspect/log the request headers and see if there is any difference in the Origins.
You can try reproducing this in development too, if you edit your hosts file and point a domain to your local server.

Resources