Rails项目中处理不同类型开发项目与地块资源关联的最优方案
最优方案按你的业务场景分两种,都是Rails生态下的标准实践,改造成本和可维护性都远高于你提到的其他思路:
场景1:不同开发类型的差异化字段少、后续不会频繁新增类型
用「枚举类型 + JSONB存扩展字段」的方案,完全不需要动现有关联逻辑:
- 第一步:给
developments表加两个字段,生成迁移文件执行即可:
# db/migrate/xxx_add_dev_type_and_extra_attrs_to_developments.rb class AddDevTypeAndExtraAttrsToDevelopments < ActiveRecord::Migration[7.0] def change add_column :developments, :dev_type, :integer, default: 0 add_column :developments, :extra_attrs, :jsonb, default: {} add_index :developments, :dev_type end end
在Development模型中定义枚举和扩展字段映射:
# app/models/development.rb class Development < ApplicationRecord has_many :lots # 原有关联完全不用改 enum dev_type: { apartment: 0, house: 1, townhouse: 2 } # 映射不同类型的独有字段,直接当普通属性调用 store_accessor :extra_attrs, :floor_count, :unit_per_floor # 公寓独有字段 store_accessor :extra_attrs, :garden_area, :parking_space_count # 住宅独有字段 store_accessor :extra_attrs, :shared_area_ratio # 联排别墅独有字段 end
- 第二步:修改新建入口传类型参数,不需要改路由规则,索引页的新建按钮直接带参数即可:
# 新建公寓按钮 <%= link_to "新建公寓", new_development_path(dev_type: :apartment) %> # 新建住宅按钮 <%= link_to "新建住宅", new_development_path(dev_type: :house) %>
控制器接收参数赋值:
# app/controllers/developments_controller.rb def new @development = Development.new(dev_type: params[:dev_type]) end
- 第三步:表单用局部视图拆分差异化部分,公共字段统一维护:
主表单文件:
# app/views/developments/_form.html.erb <%= form_with model: @development do |f| %> <%# 所有类型通用的公共字段 %> <div> <%= f.label :项目名 %> <%= f.text_field :name %> </div> <div> <%= f.label :地址 %> <%= f.text_field :address %> </div> <%# 按类型渲染对应的差异化字段局部 %> <%= render "forms/#{@development.dev_type}", f: f %> <%= f.submit "保存" %> <% end %>
分别新建三个差异化局部文件,比如公寓的字段文件:
# app/views/developments/forms/_apartment.html.erb <div> <%= f.label :总楼层 %> <%= f.number_field :floor_count %> </div> <div> <%= f.label :每层户数 %> <%= f.number_field :unit_per_floor %> </div>
这个方案全程不需要改lots相关的任何逻辑,代码冗余度极低,新增类型只需要加枚举值、新增对应局部表单即可。
场景2:不同开发类型的业务逻辑差异大,后续需要单独定制功能
用Rails原生的单表继承(STI)方案,可扩展性更强:
- 第一步:给
developments表加STI约定的type字段:
# db/migrate/xxx_add_type_to_developments.rb class AddTypeToDevelopments < ActiveRecord::Migration[7.0] def change add_column :developments, :type, :string add_index :developments, :type end end
- 第二步:新建各类型的子类模型,所有子类继承自Development父类:
# app/models/apartment.rb class Apartment < Development # 可单独定义公寓独有的验证、回调、方法、关联 validates :floor_count, presence: true end # app/models/house.rb class House < Development validates :garden_area, presence: true end # app/models/townhouse.rb class Townhouse < Development end
父类Development里的关联、公共方法所有子类自动继承,lots的关联还是只需要在父类定义一次即可,完全不需要修改子资源逻辑。
- 第三步:路由可以可选加别名,方便路径书写:
# config/routes.rb Rails.application.routes.draw do resources :developments do resources :lots # 原有子资源路由完全不用改 end # 可选的类型别名路由 %w[apartment house townhouse].each do |type| get "new/#{type}", to: "developments#new", defaults: { type: type.classify } end end
- 第四步:表单渲染逻辑和场景1完全一致,按类型渲染对应局部即可,差异化字段可以直接存在
developments表中,也可以用JSONB存储,按需选择即可。
你提到的其他方案的问题说明:
- 拆分多个独立scaffold、多对子资源的方案冗余度极高,后续公共逻辑修改需要改多份代码,维护成本至少翻3倍,除非不同类型的
lots逻辑也完全不同,否则完全不需要考虑。 - 多表单的思路本质就是上面提到的局部视图拆分,不需要单独配置路由,直接按类型渲染对应局部即可。
内容的提问来源于stack exchange,提问作者LJ-03
相关产品推荐
相关产品推荐

