1

我正在使用 Can.js 向 Ruby on Rails 服务器添加记录:

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();

实际上可以获取所有记录,findAll()但是save()会插入一条空白记录。我怀疑它可能是CSRF token,但这显示为警告,如果它不想插入记录,它可能根本不会创建任何记录,而不是添加名称为空的记录?(我也试过var todo = new Todo({name: "mow lawn", complete: true});了,结果是一样的)。

但 Rails 服务器在终端上打印为:

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)

在 Chrome 开发者工具中,我在 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

如果我的操作是:

  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

然后终端将显示:

"params[:todo] is"
nil

即使终端显示标准 Rails 日志:

Parameters: {"name"=>"mow lawn"}
4

2 回答 2

2

如 Rails 控制台输出所示,您的paramsHash 如下所示:

Parameters: {"name"=>"mow lawn"}

但是您的控制器正在使用以下内容创建一个新的 Todo 实例:

@todo = Todo.new(params[:todo])

:todo不在params哈希中,所以params[:todo]基本上nil你的 Todo 实例没有传递给它的属性,因此它正在为数据库nil中的name列保存。

您的控制器应该使用参数包装来解决这个问题,它将所有params哈希包装成一个params[:todo]子哈希。所以如果你的控制器中有这个:

class TodosController < ApplicationController
  wrap_parameters :todo
  ...
end

在执行操作之前,您的paramsHash 将转换为以下内容:

Parameters: {"name"=>"mow lawn", "todo" => {"name"=>"mow lawn"}}

然后您现有的操作代码应该可以工作。

于 2013-04-12T16:20:23.817 回答
0

就像Stuart M说的,你需要wrap_parameters :todo在控制器中设置。

wrap_parameters :todo不适用于默认的Content-Type: application/x-www-form-urlencodedcanjs 发送它的模型更新。

以下是让我解决问题的资源:

https://github.com/bitovi/canjs/issues/111

https://forum.javascriptmvc.com/topic/howto-use-application-json-and-encode-data-automatically-in-can-model

所以通过阅读那些我改变了我的应用程序看起来像这样:

应用程序/控制器/todo_controller.rb

class TodosController < ApplicationController
  wrap_parameters :todo

  def create
    Todo.new(params[:todo])
  end

end

应用程序/资产/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({
    ...
});

一切都像魔术一样工作:)


注意:您可能需要设置 CORS 才能使其工作,这是在公共 api 上设置的方法:

配置/路由.rb

SomeTodoApp::Application.routes.draw do

  resources :todos, except: [:new, :edit]

  match "*all" => "application#cors_preflight_check", via: :options

end

应用程序/控制器/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
于 2014-01-30T10:47:35.317 回答