问题
I am using Can.js to add a record to a Ruby on Rails server:
var Todo = can.Model({
findAll : 'GET /todos',
findOne : 'GET /todos/{id}',
create : 'POST /todos',
update : 'PUT /todos/{id}',
destroy : 'DELETE /todos/{id}'
}, {});
Todo.prototype.description = function() {
return this.name + " and " + this.complete;
};
Todo.findAll({}, function(todos) {
todos.each(function(todo) {
console.log(todo.name, todo.complete);
console.log(todo.description());
});
});
var todo = new Todo({name: "mow lawn"});
todo.save();
the findAll()
actually can get all the records, but the save()
will insert a blank record. I suspected it might be the CSRF token
, but that shows as a warning, and if it didn't want to insert the record, it probably won't create any record at all, instead of adding a record with name being nothing? (I also tried var todo = new Todo({name: "mow lawn", complete: true});
and it is the same result).
but the Rails server prints out on the terminal as:
Started POST "/todos" for 127.0.0.1 at 2013-04-12 08:16:05 -0700
Processing by TodosController#create as JSON
Parameters: {"name"=>"mow lawn"}
WARNING: Can't verify CSRF token authenticity
(0.2ms) begin transaction
SQL (0.8ms) INSERT INTO "todos" ("complete", "created_at", "name", "updated_at")
VALUES (?, ?, ?, ?) [["complete", nil], ["created_at", Fri, 12 Apr 2013 15:16:05 UTC
+00:00], ["name", nil], ["updated_at", Fri, 12 Apr 2013 15:16:05 UTC +00:00]]
(1.5ms) commit transaction
Completed 201 Created in 11ms (Views: 1.3ms | ActiveRecord: 2.5ms)
In the Chrome developer tool, I see in Network:
Request URL:http://localhost:3000/todos
Request Method:POST
part of header:
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
form data:
name:mow lawn
If my action is:
def create
p "params[:todo] is", params[:todo]
@todo = Todo.new(params[:todo])
respond_to do |format|
if @todo.save
format.html { redirect_to @todo, notice: 'Todo was successfully created.' }
format.json { render json: @todo, status: :created, location: @todo }
else
format.html { render action: "new" }
format.json { render json: @todo.errors, status: :unprocessable_entity }
end
end
end
then terminal will show:
"params[:todo] is"
nil
even though the terminal shows the standard Rails log:
Parameters: {"name"=>"mow lawn"}
回答1:
As shown in the Rails console output, your params
Hash looks like the following:
Parameters: {"name"=>"mow lawn"}
But your controller is creating a new Todo instance using the following:
@todo = Todo.new(params[:todo])
:todo
is not in the params
Hash, so params[:todo]
is nil
and basically your Todo instance has no attributes passed to it, hence it is saving nil
for the name
column in the DB.
Your controller should be using parameter wrapping to solve this, which will wrap all the params
Hash into a params[:todo]
sub-Hash. So if you have this in your controller:
class TodosController < ApplicationController
wrap_parameters :todo
...
end
Your params
Hash will be transformed to the following before the action is performed:
Parameters: {"name"=>"mow lawn", "todo" => {"name"=>"mow lawn"}}
And then your existing action code should work.
回答2:
Like Stuart M said, you need to set the wrap_parameters :todo
in the controller.
But wrap_parameters :todo
does not work with the default Content-Type: application/x-www-form-urlencoded
canjs sends on it's model updates.
Here are the resources that let me work it out:
https://github.com/bitovi/canjs/issues/111
https://forum.javascriptmvc.com/topic/howto-use-application-json-and-encode-data-automatically-in-can-model
So by reading those I changed my app to look like this:
app/controllers/todo_controller.rb
class TodosController < ApplicationController
wrap_parameters :todo
def create
Todo.new(params[:todo])
end
end
app/assets/javascripts/canjs.models.js
$.ajaxPrefilter(function(options, originalOptions, jqXHR){
if( options.processData && /^(post|put|patch|delete)$/i.test( options.type ))
{
options["contentType"] = "application/json";
options["data"] = JSON.stringify(originalOptions["data"]);
}
});
Todo = Model.new({
...
});
And everything worked like magic :)
NOTE: You may need to setup CORS for this to work, here is how to set it up on a public api:
config/routes.rb
SomeTodoApp::Application.routes.draw do
resources :todos, except: [:new, :edit]
match "*all" => "application#cors_preflight_check", via: :options
end
app/controllers/application_controller.rb
class ApplicationController < ActionController::API
# protect_from_forgery is always a good thing if you know your clients can support get it working
protect_from_forgery
before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers
# For all responses in this controller, return the CORS access control headers.
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, PUT, PATCH, GET, OPTIONS'
headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
headers['Access-Control-Request-Method'] = '*'
headers['Access-Control-Max-Age'] = "1728000"
end
# If this is a preflight OPTIONS request, then short-circuit the
# request, return only the necessary headers and return an empty
# text/plain.
def cors_preflight_check
if request.method == :options
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, PUT, PATCH, GET, OPTIONS'
headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
headers['Access-Control-Request-Method'] = '*'
headers['Access-Control-Max-Age'] = '1728000'
render :text => '', :content_type => 'text/plain'
end
end
end
来源:https://stackoverflow.com/questions/15975309/how-can-can-js-add-a-record-to-a-rails-server