I am trying to send json data to my RoR server so that a new object is created when the POST request is received by the server. I am really new to RoR so I am not sure how to set this up correctly.
I have been searching other posts on here for a while applying solutions to similar problems but nothing I do seems to work.
On the Rails end of things - I have a route set up specifically for this purpose.
in routes.rb
post '/api' => 'expenses#post_json_expense'
in expenses_controller.rb
# For creating an expense from the android app
def post_json_expense
Expense.new(expense_params)
end
# Never trust parameters from the scary internet, only allow the white list through.
def expense_params
params.require(:expense).permit(:user_id, :amount, :category, :description)
end
I also turned off token authentication in case that is causing the problem.
in config/application
config.action_controller.allow_forgery_protection = false
On the android side of things, I am using Volley to send the POST request
private void saveExpense () {
// Build the URL
String url = "https://my-project-url.herokuapp.com/api";
Log.i("API_REQUEST", url);
EditText etAmount = findViewById(R.id.et_amount);
EditText etDescription = findViewById(R.id.et_description);
EditText etCategory = findViewById(R.id.et_category);
// Get values from the EditTexts
double amount = Double.parseDouble(etAmount.getText().toString());
String description = etDescription.getText().toString().trim();
String category = etCategory.getText().toString().trim();
// If something was entered into all of the fields
if (amount > 0 && !description.isEmpty() && !category.isEmpty()) {
// Convert field entries into a JSON object
JSONObject expenseData = new JSONObject();
JSONObject expenseObject = new JSONObject();
try {
expenseData.put("user_id", user_id);
expenseData.put("amount", amount);
expenseData.put("description", description);
expenseData.put("category", category);
expenseObject.put("expense", expenseData);
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.PUT, url, expenseObject, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Toast.makeText(EditExpenseActivity.this, "POST Successful", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(EditExpenseActivity.this, MainActivity.class);
startActivity(intent);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(EditExpenseActivity.this, "POST Unsuccessful", Toast.LENGTH_SHORT).show();
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("Content-Type", "application/json");
return params;
}
};
// Make the API request
RequestQueue queue = Volley.newRequestQueue(this);
queue.add(jsonObjectRequest);
}
}
I keep getting a the error response each time I try to post data to the app. Where am I going wrong? I'm pretty new to rails is my first time trying to build an API so I am not sure what I am doing incorrectly.
Welcome to S.O.!
So, there's a few things that need addressing here. For starters, in your controller:
# For creating an expense from the android app
def post_json_expense
Expense.new(expense_params)
end
So, first, calling Expense.new
here will only create a new object, but it will not persist it to the database; you'll also need to call save
to do that.
Next, you are not returning any sort of response back to the caller. Perhaps returning something like the id of the new Expense would be in order, or the expense itself. I'd suggest constructing the call like so:
# For creating an expense from the android app
def post_json_expense
expense = Expense.new(expense_params)
unless expense.save
# TODO: Return an error status with some useful details
# You can get some from expense.errors
return render status: 400, json: { error: true }
end
# Render a JSON representation of the expense on successful save
render json: expense.as_json
end
Next, on the client side, you are sending Content-type
headers, which is good, but you could also send Accept
headers, which clues the server as to what you expect to receive back:
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("Content-Type", "application/json");
params.put("Accept", "application/json");
return params;
}
Finally, you have assigned the method to the POST route on your server:
post '/api' => 'expenses#post_json_expense'
But you are calling it as a PUT from your app:
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.PUT, url, expenseObject, new Response.Listener<JSONObject>() {
So, there is no PUT
route at that URL, hence why the request would always fail.
Cleaning up these issues should get you a successful response.
Personally, I find using a simple utility like curl
often helps debug these sort of communication errors, when you don't know whether the issue is the fault of an app-side coding issue or a server-side coding issue (or both). You can eliminate the variables by using something like curl
which you can be confident works, then debug from there.
Hope this helps you along!