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

一曲冷凌霜 提交于 2019-12-08 08:32:20

问题


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

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