3

I'm using the rails-api gem to have just a Rails API and using Angular to power my frontend. Whenever I use $http, it will only work if I pass in params instead of data. Here's an example with trying to log in a user and create a new session:

'use strict';

app.controller('LoginCtrl', function($scope, $location, $http, tokenHandler) {
  $scope.login = function() {
    $http({
      url: 'http://localhost:3000/api/admins/sign_in',
      method: 'POST',
      params: $scope.admin
    }).success(function(data) {
      if (data.success) {
        $scope.ngModel = data.data.data;
        tokenHandler.set(data.data.auth_token);
        $location.path('/admin/blog');
      } else {
        $scope.ngModel = data;
        $scope.user.errors = data.info;
      }
    }).error(function(msg) {
      $scope.admin.errors = 'Something is wrong. Please try again.';
    });
  };
});

If instead of params I used data: { admin: $scope.admin }, Rails complains to me that params[:admin] is nil. It seems to not be coming through at all.

However, if I use params, I get this:

Started POST "/api/admins/[email protected]&password=[FILTERED]" for 127.0.0.1 at 2014-09-07 20:08:04 -0400
Processing by Admin::SessionsController#create as HTML
  Parameters: {"email"=>"[email protected]", "password"=>"[FILTERED]"}

Which I can work with. It's just weird that it seems to only work when the request is processed as HTML. When I use data, I get this:

Started OPTIONS "/api/admins/sign_in" for 127.0.0.1 at 2014-09-07 20:36:24 -0400
Processing by Admin::SessionsController#create as */*

Is it suppose to say processing by */*? I'd think it should understand it's supposed to process by json specifically.

My sessions controller looks like this:

class Admin::SessionsController < Devise::SessionsController
  skip_before_filter :verify_authenticity_token

  before_filter :authenticate_user!, except: [:create]
  respond_to :json

  # ...
end

The weird thing is I definitely got it working the first time just using data: { admin: $scope.admin }, but ever since, the params seem to never come through unless I use params: $scope.admin.

ALSO:

I'm using Devise for authentication, and I had to add this to my ApplicationController:

class ApplicationController < ActionController::API
  include ActionController::MimeResponds

  before_filter :set_cors_headers
  before_filter :cors_preflight

  private 

    def set_cors_headers
      headers['Access-Control-Allow-Origin'] = AppConfig.client['origin']
      headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
      headers['Access-Control-Allow-Headers'] = '*'
      headers['Access-Control-Max-Age'] = "3628800"
    end

    def cors_preflight
      head(:ok) if request.method == :options
    end
end

Anyone ever dealt with this before?

2 Answers 2

1

I've finally got it working and while I'm still confused, I think I've got somewhere close to what the problem was: My CORS configuration in my Rails API.

From what I've learned, Angular sends data in JSON format by default. This goes through as "Content-Type:application/json;charset=UTF-8", whereas in jQuery AJAX requests, it goes through as "Content-Type:application/x-www-form-urlencoded; charset=UTF-8", and is converted to a query string using $.param(). I'll admit, I've probably heard this before, but haven't truly registered this fact and its effects until now.

In my application controller, I configured my CORS settings like so:

def set_cors_headers
  headers['Access-Control-Allow-Origin'] = AppConfig.client['origin']
  headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
  headers['Access-Control-Allow-Headers'] = '*'
  headers['Access-Control-Max-Age'] = "3628800"
end

def cors_preflight
  head(:ok) if request.method == :options
end

AppConfig is just an OpenStruct that tells my Rails API what origin to accept requests from. And then everything else was supposed to simply set the CORS headers.

For some reason of which I'm still not sure, this wasn't working for JSON requests. I got the above code from a tutorial using Angular and Rails, and in the case of the tutorial, they manually stripped out the asset pipeline, leaving everything else about Rails in, whereas rails-api strips out some Rails configuration. This may be why setting the CORS headers in ApplicationController wasn't working.

What did work was to use the rack-cors gem and then add this bit to development.rb:

  config.middleware.use Rack::Cors do
    allow do
      origins 'localhost:9000'
      resource '*', :headers => :any, :methods => [:get, :post, :options, :delete]
    end
  end

This tells my app to accept requests from localhost:9000, and to accept any headers. I thought I was accomplishing that with headers['Access-Control-Allow-Headers'] = '*' in my ApplicationController, but I guess not. Once I specified Rails to use those middleware settings, everything worked perfectly. My Rails API can now accept application/json from my Angular app.

If someone could fill in the gaps where I'm still confused, I'd appreciate it. But I hope this helps others.

Sign up to request clarification or add additional context in comments.

Comments

0

You can send either :params or :data (or both, I guess). According to the angularjs docs at https://docs.angularjs.org/api/ng/service/$http

params – {Object.} – Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified.

data – {string|Object} – Data to be sent as the request message data.

The controller is expecting http-type parameters/form data, so passing the object via params works - it gets converted, whilst passing the same via :data doesn't because it doesn't get converted.

I don't know if there is a smart way to unpack the data format at the Rails Controller end, but you can convert the object within your $http request into serialized parameters using $.param(data)http://api.jquery.com/jQuery.param/

data: $.param($scope.your_data_object) e.g. $scope.admin

and then unpack params[:data] at the controller.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.