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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With