Calling a $resource action from inside a user defined $scope function - ruby-on-rails-3

The frontend: AngularJS 1.1.5, which has ngResource returning a promise's $then function
The backend: Rails 3.2
The problem: Whenever I call the Card.update action from inside a function in an AngJS controller, I get no response from attempting to log both the success and error responses. The Card $resource behaves as expected works when calling the Card.update action from outside the function.
Rails Associations
Deck has_and_belongs_to_many :cards
Card has_and_belongs_to_many :decks
Rails routes
resources :cards do
get '/decks' => 'cards#decks', on: :member
end
Rails Card Controller 'update' action
def update
#card = Card.where( id: params[:id] ).first
unless params[:deck_id].nil?
#deck = Deck.where( id: params[:deck_id] ).first
#deck.cards << #card
end
render json: Card.update( params[:id], params[:card] )
end
Cards Resource Factory
app.factory "Card", ($resource) ->
$resource "/cards/:id",
id: "#id"
,
index:
method: "GET"
isArray: true
show:
method: "GET"
isArray: false
create:
method: "POST"
update:
method: "PUT"
destroy:
method: "DELETE"
decks:
method: "GET"
isArray: true
url: 'cards/:id/decks'
Cards update function (inside AngJS Controller): (Success/error messages aren't logged to the console).
$scope.updateCard = ( card_id, old_deck, new_deck ) ->
console.log 'CardCtrl updateCard function'
card_id = parseInt(card_id)
old_deck = parseInt(old_deck)
new_deck = parseInt(new_deck)
Card.update( id: card_id, deck_id: new_deck ).$then (success, error) ->
console.log 'Success'
console.log success
console.log 'Error'
console.log error

Introduction
I solved my own problem. I used this Github comment from Misko Hevery as reference.
Why my PUT request wasn't executing:
Apparently, in Angular 1.1.x, if you ever leave the angular execution context, you'll be outside of Angular's $scope.$apply function. In my case, I was working with JQuery UI's sortable function - whenever a Card was placed in a new Deck, I wanted to update the Deck's cards to reflect that update.
To the best of my understanding, because of this, I was outside the angular execution context. ngResource wouldn't execute the PUT statement and the $then function of the Angular's promise object will never be called.
The solution:
Simply wrapping the $scope.$apply function around the Card.updateaction allowed the action to execute, which subsequently allowed the $then function to execute. See the code example below for further clarification:
$scope.$apply ->
Card.update(
id: 1
deck_id: 2
front: "HELLO WORLD!"
).$then ((success) ->
console.log "Success"
console.log success
), (error) ->
console.log "Error"
console.log error

Related

Ajax POST request not hitting Elixir router path

I have this redux-observable epic which does a POST ajax request using RxJS.ajax.post and I don't think it is hitting my Elixir router properly as nothing is happening on my elixir backend. I am doing get requests to get categories correctly and in the same manner so I am hitting other paths in my Elixir router correctly. I am expecting the issue to be with my backend Elixir code not my frontend. I might need to change my path in router.ex.
When I press a button on the frontend, this object is what gets sent to the elixir backend (it dispatches this action with a product as the payload and hits the redux-observable epic below):
onPress = {() => {
props.uploadProduct({
name: props.name,
brand: props.brand,
description: props.description,
image: props.image
})
The epic:
import { ajax } from 'rxjs/observable/dom/ajax'
import { Observable } from 'rxjs'
export const uploadProductEpic = action$ =>
action$.ofType(UPLOAD_PRODUCT)
.mergeMap(action => {
ajax.post('http://localhost:4000/products/', action.payload)
})
.map(response => uploadProductFulfilled(response))
.catch(error => Observable.of(
uploadProductRejected(error))
)
the elixir router:
defmodule Api.Router do
use Plug.Router
if Mix.env == :dev do
use Plug.Debugger
end
plug :match
plug :dispatch
get "/categories/" do
Api.Repo.getCategories(conn)
end
post "/products/:product" do
IO.puts inspect conn
Api.Repo.insertProduct(conn, product)
end
end
IO.puts inspect conn doesn't log anything. So My Elixir router path post "/products/:product" do is not being hit by my POST request. What am I doing wrong?
This is the elixir function in repo.ex that I HOPE will insert the product into my database:
def insertProduct(conn, product) do
product = %Api.Product{name: product.name, brand: product.brand, description: product.description, image: product.image, rating: 0, numberOfVotes: 0}
changeset = Api.Product.changeset(product)
errors = changeset.errors
valid = changeset.valid?
case insert(changeset) do
{:ok, product} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Poison.encode!(%{
successs: "success"
}))
{:error, changeset} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(500, Poison.encode!(%{
failure: "Errors"
}))
end
end
I am a frontend developer just trying to get into Elixir so any guidance and patience is appreciated. Thanks.
Your data is sent in the body of the request, not in the URL, so the route should be:
post "/products"
You'll also need to plug in a JSON parser after plug :match and before plug :dispatch, as described in the Parameter Parsing section in the documentation of Plug.Router:
plug :match
plug Plug.Parsers, parsers: [:json],
pass: ["application/json"],
json_decoder: Poison
plug :dispatch
The JSON data will now be present in conn.body_params, which you can send to your function:
post "/products" do
Api.Repo.insertProduct(conn, conn.body_params)
end
And finally, the keys in the JSON would be strings, so you need to use the bracket syntax to access them instead of dots:
product = %Api.Product{name: product["name"], brand: product["brand"], description: product["description"], image: product["image"], rating: 0, numberOfVotes: 0}
I'm not sure how you've defined Api.Product.changeset, but the default Phoenix convention defines a 2 arity function which calls cast and extracts the data from a map itself. If you're doing the same, you can do this instead:
changeset = Api.Product.changeset(%Api.Product{}, product)

iOS app - Rails 4 and Devise as backend

I would like to know how to use rails as backend for my iOS app.
All I need is a User with email and password to authenticate using devise.
I already have a User created with devise and rails 4.
I did find this post http://jessewolgamott.com/blog/2012/01/19/the-one-with-a-json-api-login-using-devise/ explaining what I need, but some things are still missing.
When I try to do a POST via my iOS app, I get the message "Can't verify CSRF token authenticity". How do I solve that without skipping the filter verify_authenticity_token ?
How would the request code for the iOS look like? Right now I'm doing a POST to http://localhost:3000/api/users/sign_in.json and setting the HTTPBody = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:0 error:&jsonError], but the rails server is receiving only a string as key with the entire json dictionary, not an actual json dictionary.
params = {"{\"user\":{\"email\":\"qwe\",\"password\":\"123\"}}"=>nil, "action"=>"create", "controller"=>"api/sessions", "format"=>"json"}
How would I do an https request instead of http, so I can hide the password and email fields in case someone else tries to watch my internet traffic?
Thank you very much.
To use Rails Applications Mobile and Android and IOS, necessarily you have to use JSONP: example:
JS sample:
$.ajax({
url: '/api_mobile',
jsonp: "callback",
dataType: "jsonp",
cache: true,
data: {method: 'login', other_data ...},
success: function(res) {
// response object
console.log(res)
},
error: function(request, status, error) {
alert("Error server: " + request.status);
}
});
RAILS 4:
protect_from_forgery with: :exception, only: :api_mobile
# route /api_mobile
def api_mobile
json = {error: 'Not found Method'}
case params[:method]
when: 'login'
if User.login(params[:username], params[:password])
json = {notice: 'Login success'}
else
json = {error: 'Error Username or Password'}
end
end
render json: json, :callback => params[:callback]
end
All functions must be personalized and parameterized

Ember ApplicationRoute cannot handle error events

I cannot get my ApplicationRoute to handle errors. 401 or 500 (From Login operation) response from server did not fire the error event in my ApplicationRoute. Please Help!
I can see the 401 or 500 error in the console log.
FullCircle.ApplicationRoute = Ember.Route.extend
events:
error: (reason, transition) ->
alert reason
actions:
logout: ->
this.controllerFor('application').logged_out()
delete localStorage.authToken
this.transitionTo 'login'
FullCircle.LoginRoute = Ember.Route.extend
actions:
login: ->
self = this
loginController = #controllerFor 'login'
data = loginController.getProperties("username", "password")
# this would normally be done asynchronously
$.post("/login", data).then (response) ->
alert response.message
if response.user
localStorage.authToken = response.token
applicationController = self.controllerFor 'application'
transition = applicationController.get 'savedTransition'
# set isLoggedIn so the UI shows the logout button
applicationController.logged_in()
# if the user was going somewhere, send them along, otherwise
# default to `/posts`
if transition
transition.retry()
else
self.transitionTo '/documents'
As of ember 1.0, the events hash was renamed to actions. A route's error handler should be in the actions hash. So:
FullCircle.ApplicationRoute = Ember.Route.extend
actions:
error: (reason, transition) ->
alert reason
logout: ->
...

Catch $http use $httpBackend in App.run

I have a module App which will check if the user has sign in.
App.run ['$rootScope', 'UserService', ($rootScope, UserService) ->
UserService.current_user()
]
The UserService.current_user() will trigger a $http request.
So how can i write the $httpBackend to mockup the request? I have tried some method:
describe 'App', ->
$httpBackend = null
beforeEach(module('App')) # one
beforeEach inject ($injector) -> # two
$httpBackend = $injector.get('$httpBackend')
$httpBackend
.when('GET', '/api/1/users/current_user')
.respond(403, {"error":"not signin"})
it "should get current_user request", () ->
$httpBackend.expectGET('/api/1/users/current_user').respond(403, {'error': 'not signin'})
This will show the error:
Error: Unexpected request: GET /api/1/users/current_user No more request expected
If I change the sequence of # one and # two. It will show error
Error: Injector already created, can not register a module!
This makes me fall in depression. I need some help.
I made it working with this :
describe 'App', ->
beforeEach angular.mock.module('App')
it "should get current_user request", inject ($httpBackend) ->
$httpBackend.expectGET('/api/1/users/current_user').respond(403, {'error': 'not signin'})
$httpBackend.flush()
And it works also if you put the backend expectation in a before each :
describe 'App', ->
beforeEach angular.mock.module('App')
beforeEach inject ($httpBackend) ->
$httpBackend.expectGET('/api/1/users/current_user').respond(403, {'error': 'not signin'})
$httpBackend.flush()
it 'should test someting else', ->
See it in action in this Plunker.

backbone destroy not sending params

I've got a backbone model that I'm trying to destroy, but no params are being sent with the request, so the server is returning a 'Delete 404 not found' error.
I'll admit my structure is a bit strange as I'm creating/destroying the items based on if they are in a list already or not.
var list_item = new MyApp.Models.ListItem({item_id: this.model.id, group_id: this.model.group_id});
if($(e.currentTarget).hasClass('add')){
list_item.save(list_item, {
success: function(response){
this.model.attributes.addedtolist_id = response.id
console.log(this.model);
},
error: function(){
alert('could not save item');
}
});
} else if($(e.currentTarget).hasClass('remove')) {
list_item.id=this.model.addedtolist_id;
list_item.attributes.id = this.model.addedtolist_id;
console.log(list_item);
list_item.destroy({
success: function(){
alert('delete');
},
error: function(){
alert('could not uncheck');
}
});
}
the console output for list_item before destroy is
_escapedAttributes: Object
_previousAttributes: Object
_setting: false
attributes: Object
id: 2
item_id: 66
group_id: 64
__proto__: Object
cid: "c23"
id: 2
__proto__: q
but when I look at the headers sent with the delete request, I don't have any params being sent.
-----------------------update params being sent, 404 still being returned --------------
as per Yaroslav's recommendation, I've added a 'header' to the destroy method, but my rails controller is still returning a DELETE 404 not found error. I'm just trying to return the listitem to make sure i'm getting the right one before I destroy it.
My controller is
def destroy
listitem = Listitem.find(params[:id])
return render :json => listitem
end
I'd guess that you're setting the url in the model to a string:
Backbone.Model.extend({
url: '/list_items',
//...
});
That will tell Backbone to use exactly /list_items as the URL for all actions. You should use a function:
url: function() { return '/list_items/' + encodeURIComponent(this.id) }
or use a string with urlRoot and let the default url function add the id:
urlRoot: '/list_items'
What params are you expecting to be sent? Destroy makes just a http delete request by the url without body or any additional headers by default. The params argument is pased to the jquery ajax function, so you can specify headers there:
model.destroy({
...
headers : {
your_header : 123
}
})
list_item.destroy( **l** {
success: function(){
alert('delete');
}, ... );
Is that extra number one the problem?