根据 Rails 上 Ruby 中的参数接收到的数据创建对象

Create objects based on received data from params in Ruby on Rails

描述

我正在 Rails 上 Ruby 中的浏览器中基于所选(通过复选框)users 创建 messages

快照:

重现步骤

  1. 我的架构
ActiveRecord::Schema.define(version: 2021_11_13_142255) do

  create_table "messages", force: :cascade do |t|
    t.text "content"
    t.integer "user_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.string "role"
    t.integer "phone"
    t.boolean "admin"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

end
  1. messages_controller.rb
class MessagesController < ApplicationController
  def new
    @users = User.all

    @message = Message.new(message_params)
  end

  def create
    params[:user_objs].each do |u|
      # "params.inspect" returns
          # {"authenticity_token"=>"[FILTERED]",
          #  "user_objs"=>
          #   ["{\"id\":1,\"name\":\"Alex\",\"role\":\"Engineer\",\"phone\":998943333303,\"admin\":true,\"created_at\":\"2021-11-13T14:37:54.962Z\",\"updated_at\":\"2021-11-13T14:37:54.962Z\"}",
          #    "{\"id\":2,\"name\":\"Lucy\",\"role\":\"Accountant\",\"phone\":998943333303,\"admin\":false,\"created_at\":\"2021-11-13T14:39:52.742Z\",\"updated_at\":\"2021-11-13T14:39:52.742Z\"}"],
          #  "message"=>{"content"=>"Message from the browser"},
          #  "commit"=>"Send"}
      person = JSON.parse(u)
      @message = person.messages.new(message_params)
      if @message.save
        redirect_to root_path
      else
        @users = User.all
        render :new
      end
    end
  end

  private

  def message_params
    params.permit(
        :content,
        :user_id
      )
  end
end

  1. 消息 => new.html.erb
<div>
    <h1>Create and send a new message!</h1>

    <%= form_for(@message) do |form| %>
        <% if @message.errors.any? %>
            <div class="alert alert-danger">
                <h5 class="fw-bold">Invalid input!</h5>
                <%= @message.errors.full_messages.each do |error| %>
                    <div><%= error %></div>
                <% end %>
            </div>
        <% end %>
        
        <% @users.each do |u| %>
            <div>
                <p><%= check_box_tag "user_objs[]", u.to_json %> <%= u.name %></p>
            </div>
        <% end %>

        <p class="mb-3">
            <%= form.label :content, class: "form-label" %>
            <%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
        </p>

        <p class="mb-3">
            <%= form.submit "Send", class: "btn btn-primary" %>
        </p>
    <% end %>
</div>
<%= params.inspect %>
  1. 型号
# user.rb
class User < ApplicationRecord
    has_many :messages
end


# message.rb
class Message < ApplicationRecord
    belongs_to :user
end

预期行为

我期待为所有选定的用户创建消息

实际行为

NoMethodError in MessagesController#create
undefined method `messages' for #<Hash:0x000000011fe2b420>

我尝试了不同的方法,但无法在我的 params user_objs[] 中将 Ruby 对象转换为 JSON,因此我可以在我的控制器中解析它以根据所选对象创建消息user_objs[] 参数中的用户。

环境信息

ruby -v
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [arm64-darwin20]

rails -v
Rails 6.1.4.1

感谢您的帮助

问题是您在 person = JSON.parse(u) 中分配 json object/hash。这不是活动记录,因此在执行 person.messages 时会抛出错误。我相信您在创建操作中需要的是:

user = JSON.parse(u)
# make sure user.inspect gives you the user object you want
person = User.find(user["id"])
# person.inspect should give you the active record for the user

如果您想创建一个系统,您可以在其中向多个用户发送一条消息,您可以设置一个加入 table:

class User < ApplicationRecord
  has_many :user_messages 
  has_many :recieved_messages, though: :user_messages,
                               source: :message,
                               inverse_of: :recipients
end
# rails g model user_message user:belongs_to message:belongs_to read:boolean
class UserMessage  < ApplicationRecord
  belongs_to :user
  belongs_to :message
  # make sure to add a compound unique index to the migration as well
  validates_uniqueness_of :user_id, scope: :message_id
  delegate :content, to: :message
end
class Message  < ApplicationRecord
  has_many :user_messages 
  has_many :recipients, though: :user_messages,
                        source: :user,
                        inverse_of: :recieved_messages
end

has_many :recipients 将创建一个 recipient_ids= setter 和一个 recipient_ids getter,您可以在表单中使用它们:

<div>
  <h1>Create and send a new message!</h1>

  <%= form_with(model: @message) do |form| %>
    <% if @message.errors.any? %>
      <div class="alert alert-danger">
        <h5 class="fw-bold">Invalid input!</h5>
          <%= @message.errors.full_messages.each do |error| %>
            <div><%= error %></div>
        <% end %>
      </div>
    <% end %>

    <p class="mb-3">
      <%= form.collection_checkboxes(:recipient_ids, @users, :id, :name) %>
    </p>

    <p class="mb-3">
      <%= form.label :content, class: "form-label" %>
      <%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
    </p>

    <p class="mb-3">
      <%= form.submit "Send", class: "btn btn-primary" %>
    </p>
  <% end %>
</div>

绝对没有必要将整个记录作为 JSON 传递 - 您只需传递一个 ID 数组,rails 将完成创建连接 table 行的所有工作给你:

class MessagesController < ApplicationController

  def new
    @users = User.all
    @message = Message.new
  end

  def create
    @message = Message.new(message_params)
    if @message.save
      redirect_to root_path
    else
      @users = User.all
      render :new
    end
  end

  private

  def message_params
    params.require(:message)
          .permit(
            :content,
            recipient_ids: []
          )
  end
end

这避免了从单个请求创建多个记录的复杂性以及整个难题,即您将表单绑定到 Message 的单个实例但创建一堆记录,这必然会导致混淆。

如果您想一次创建多条记录,可以做到,但复杂性要高得多,您必须处理诸如创建一条消息失败时如何处理错误之类的问题,这可能超出您当前的技能水平.