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)
Related
I'm currently trying to make an API doc page thanks to nelmio-api-bundle. I only have one route which is a POST route. I'm receiving a JSON in the body of the request and I'm using the Serializer from symfony to deserialize it in a DTO. I'm also using a DTO for the response (which contains the status code, a bool set to true or false, and a message). Now I'm trying to use these DTO (for input and output) to build the API documentation with nelmio-api-bundle but how to make it ? I'm using PHP8.1 attributes to make it, for response it almost works (except that the response is shows as an array) but I don't know how to make it for the inputs.
Here is my current code:
#[Route('/user', methods: ['POST'])]
#[OA\Parameter(
name: 'user',
description: 'The user information in JSON',
in: 'query',
required: true
)]
#[OA\Response(
response: 200,
description: 'Returns the success response',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: new Model(type: SuccessResponseDTO::class))
)
)]
public function registerUser(Request $request, LoggerInterface $logger): JsonResponse
{
//the code to register the user
}
And here is the result:
Do someone know how to make it ?
Currently the documentation is clear and vibrant with the common HTTP verbs, but we started implementing some HEAD routes today and it is not tested the same way as the others.
To test say a GET method:
conn = get conn, controller_path(conn, :controller_method, params)
So I would assume you would just change get to head, but this is not the case.
Here is my route:
template_journeys_count_path HEAD /v1/templates/:template_id/journeys GondorWeb.V1.JourneyController :count
and my controller method:
def count(conn, %{"template_id" => template_id}) do
count = Templates.get_journey_count(template_id)
conn
|> put_resp_header("x-total-count", count)
|> send_resp(204, "")
end
and my test:
conn = head conn, template_journeys_count_path(conn, :count, template.id)
assert response(conn, 204)
But I am getting an error saying no response received and the resp_header that I added what not in conn.resp_headers
Am I missing something? I also tried to set up a connection build using Plug.ConnTest's method build_conn passing the HEAD method into it but still no luck.
OK after more reading and testing using postman. Phoenix will automatically change HEAD requests into GET requests, there for when phoenix was looking for my route in the router, it was hitting the get route that matched the path with was my :index method.
For HEAD routes:
the verb in the router must be a get, ex: get '/items', :index
if you want to share a path, just add the put_resp_header on your returned connection in the controller method, only the header will be sent in the response
It is ok that the response code is not a 204, as per the w3c doc's HEAD requests can have a 200 response
testing a HEAD request, you can just change a the get to a head and test the response_headers and that no body was sent.
To show my changes... here is my router:
get "/journeys", JourneyController, :index
my controller method:
def index(conn, %{"template_id" => template_id}) do
journeys = Templates.list_journeys(template_id)
conn
|> put_resp_header("x-total-count", "#{Enum.count(journeys)}")
|> render("index.json", journeys: journeys)
end
and my test:
test "gets count", %{conn: conn, template: template} do
conn = head conn, template_journey_path(conn, :index, template.id)
assert conn.resp_body == ""
assert Enum.at(get_resp_header(conn, "x-total-count"), 0) == "1"
end
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
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
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.