首页 > 解决方案 > 使用 ActiveModel 和 Forms 在 Rails 中丢失和不准确的有效负载

问题描述

我有一个Organisation类似的模型

class Organisation
  include ActiveModel::Model

  attr_accessor :orguid,
                :title, :firstname, :lastname, :role, :telephone, :extension, :email,
                :name, :branch, :address1, :address2, :address3, :city, :state, :country, :zip

end

在我的控制器中,我有以下操作:

# frozen_string_literal: true

require 'cgi'
require 'json'

class OrganisationsController < ApplicationController
  include Secured

  before_action :set_api, only: %i[dashboard create]
  before_action :user_info, only: %i[dashboard register]

  def dashboard
    @registration = @api.registered?
  end

  def register
    @organisation = Organisation.new
  end

  def create
    organisation_params
    register_data = params[:organisation].to_h
    register_data['oruid'] = org_uid
    @api.register(register_data)
  end

  private

  def set_api
    @api = CoreApi.new(org_uid)
  end

  def user_info
    @user_info = session[:userinfo].to_h
  end

  def org_uid
    CGI.escape(user_info['uid'])
  end

  def organisation_params
    params.require(:organisation).permit!
  end

end

在我的register.html.erb我有:

<h1> Register Your Organisation</h1>


<%= form_with model: @organisation, url: org_register_path do |f| %>
  <div class="container">
    <h2>Your Details</h2>
    <div class="form-row">
      <div class="form-group col-md-2">
        <%= f.label :title %>
        <%= f.text_field :title, class: 'form-control' %>
      </div>
      <div class="form-group col-md-5">
        <%= f.label :first_name %>
        <%= f.text_field :firstname, class: 'form-control' %>
      </div>
      <div class="form-group col-md-5">
        <%= f.label :last_name %>
        <%= f.text_field :lastname, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-12">
        <%= f.label :role %>
        <%= f.text_field :role, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-4">
        <%= f.label :telephone %>
        <%= f.telephone_field :telephone, class: 'form-control' %>
      </div>
      <div class="form-group col-md-2">
        <%= f.label :extension %>
        <%= f.text_field :extension, class: 'form-control' %>
      </div>
      <div class="form-group col-md-6">
        <%= f.label :email %>
        <%= f.email_field :email, class: 'form-control', readonly:'', value: @user_info['info']['name'] %>
      </div>
    </div>
  </div>

  <div class="container">
    <h2>Organisation Details</h2>
    <div class="form-row">
      <div class="form-group col-md-6">
        <%= f.label :name %>
        <%= f.text_field :name, class: 'form-control' %>
      </div>
      <div class="form-group col-md-6">
        <%= f.label :branch %>
        <%= f.text_field :branch, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-12">
        <%= f.label :address_line_1 %>
        <%= f.text_field :address1, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-12">
        <%= f.label :address_line_2 %>
        <%= f.text_field :address2, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-12">
        <%= f.label :address_line_3 %>
        <%= f.text_field :address3, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-4">
        <%= f.label :city %>
        <%= f.text_field :city, class: 'form-control' %>
      </div>
      <div class="form-group col-md-4">
        <%= f.label :state %>
        <%= f.text_field :state, class: 'form-control' %>
      </div>
      <div class="form-group col-md-4">
        <%= f.label :country %>
        <%= f.text_field :country, class: 'form-control' %>
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-md-2">
        <%= f.label :zip %>
        <%= f.text_field :zip, class: 'form-control' %>
      </div>
    </div>
  </div>

  <div class="container">
    <div class="form-row">
      <div class="form-group col-md-12">
        <%= f.button :Register, class: 'btn btn-primary' %>
      </div>
    </div>
  </div>
<% end %>

最后register我的方法core_api.rb是这样的:

  def register(data)
    body = data.to_json
    puts ">> >> >> >> #{body.class} :: #{body}"
    options = { headers: { 'Content-Type' => 'application/json' }, body: body }
    response = self.class.post('/organisations', options)
    #puts ">>>>>>>>>>>> #{response}"
  end

最后我的routes.rb文件包含:

Rails.application.routes.draw do

  get '/' => 'home#show'

  get '/auth/auth0/callback' => 'auth0#callback'
  get '/auth/failure' => 'auth0#failure'

  get '/logout',                    to: 'logout#logout',                as: 'logout'

  get '/organisations/dashboard',   to: 'organisations#dashboard',      as: 'org_dashboard'
  get '/organisations/register',    to: 'organisations#register',       as: 'org_register'
  post '/organisations/register',   to: 'organisations#create'

  root 'home#show'
end

现在,当我运行服务器并在日志中提交表单时,我得到:

>> >> >> >> String :: {"title":"","firstname":"","lastname":"","role":"","telephone":"","extension":"","email":"alijy3@yahoo.com","name":"we","branch":"we","address1":"we","address2":"","address3":"","city":"we","state":"","country":"we","zip":"","oruid":"auth0%7C5e5388493d670c11be833bca","contact_id":0}

在我看来,这很合适json。但是,由于 api 响应一直不成功,我用Postman拦截了传出的帖子,以查看正在发送的有效负载。令我惊讶的是,有效载荷不是平面 json,而是这样的: 邮递员截图

我有两个问题:

我在服务器上没有任何数据库。一切都是通过 api 获取/发布/保存的。我一直在阅读有关如何使用 Activemodel 和表单的信息,但我还没有设法解决这个问题。任何帮助或解释将不胜感激。

标签: ruby-on-railsformsactivemodel

解决方案


没有冒犯,但这是火车残骸。您不需要仅仅因为在这种特定情况下没有使用 ActiveRecord 就打破每个 rails 约定。

从使用ActiveModel::Attributes#attribute而不是 Ruby 内置的attr_accessor.

class Organisation
  include ActiveModel::Model
  include ActiveModel::Attributes
  [:orguid, :title, :firstname, :lastname, :role, :telephone, 
   :extension, :email, :name, :branch, 
   :address1, :address2, :address3, :city, :state, :country, :zip]
  .each do |name|
     attribute name
  end

  # @todo write validations!
end

这会创建类似于 ActiveRecord 属性的属性,您可以使用@organization.as_json.

然后让我们在那个控制器上重新开始,因为它的气味太多了,不值得打捞。

# routes.rb
resources :organisations, only: [:new, :create]


class OganizationsController < ApplicationController
  # GET /organizations/new
  def new
     @organization = Organization.new
  end

  # POST /organizations
  def create
    # You never manually parse out incoming params - thats Rack's job.
    # also since you have a model - USE IT!
    @organization = Organization.new(organization_params) do |o|
      o.orguid = org_uid
    end
    # validate the user input before you send it to an external API
    if @organization.valid? && @api.register(@organization)
      redirect_to '/somewhere'
    else
      render :new
    end
  end

  private

  # use monads here instead of callbacks!
  def user_info
    # Rails will serialize/deserialize hashes automatically 
    # from the session
    session[:userinfo] 
  end

  def org_uid
    # Have no clue what the heck you're doing with CGI escape. 
    @org_uid ||= user_info['uid']
  end 

  def api
    @api ||= CoreApi.new(org_uid)
  end

  def organization_params
    # You don't have any reason to use 'permit!' and give 
    # yourself a potential mass assignment vunerablity
    params.require(:organization)
          .permit(
             :title, :firstname, :lastname, :role, :telephone, 
             :extension, :email, :name, :branch, 
             :address1, :address2, :address3, :city, 
             :state, :country, :zip
           )
  end
end

重命名视图/organizations/new.html.rb。此时,您应该能够存根 API 并使用有效和无效输入进行集成测试。

整个session[:userinfo]事情仍然闻起来很糟糕 - 如果您从 OAuth 获取响应并将其推送到会话中,那么您将自己设置为非常糟糕的时间,因为这可能会导致 cookie 溢出。同样在 Rails 中,如果您曾经手动进行强制转换/序列化,那么这是一个非常好的迹象,表明您做错了什么。

不知道您的 CoreApi 类中发生了什么,但如果您使用的是 HTTParty,则不应执行任何手动 JSON 编码。

# @fixme name is way to generic. 
class CoreApi
  include HTTParty
  format :json # sets content type and encodes the content
  # ...
  def register(organization)
    response = self.class.post('/organisations', @organization.as_json)
    if response.success?
      true
    else
      @organization.errors.add(:base, 'Could not be registered')
      false
    end
  end
end

推荐阅读