How can can.js add a record to a Rails server?

匿名 (未验证) 提交于 2019-12-03 10:24:21

问题:

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!