我有这个模特
class Book < ActiveRecord::Base
has_many :accountings
has_many :commodities, through: :accountings
end
class Accounting < ActiveRecord::Base
belongs_to :book
belongs_to :commodity
end
class Commodity < ActiveRecord::Base
has_many :accountings
end
我的书形式是:
<%= form_for(book) do |f| %>
<%= render 'shared/error_messages', object: book %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :commodity_ids %>
<%= f.collection_select :commodity_ids, Commodity.all, :id, :name, {}, {multiple: true} %>
<br />
<%= f.submit class: 'btn btn-primary' %>
<% end %>
在BooksController:
def new
@book = Book.new
end
def create
@book = Book.new(book_params)
if @book.save
redirect_to @book
else
render 'new'
end
end
def edit
@book = Book.find(params[:id])
end
def update
@book = Book.find(params[:id])
if @book.update_attributes(book_params)
redirect_to @book
else
render 'edit'
end
end
private
def book_params
params.require(:book).permit(:name, commodity_ids: [])
end
这一切都很好。更新Accounting
时会添加和删除commodity_ids
条记录。 (1)
现在我需要添加一个新模型:Company
,因为Book
和Commodity
由所有公司共享,Accounting
必须属于company
,并且也没有在所有系统中共享。 Accounting
成为:
class Accounting < ActiveRecord::Base
belongs_to :book
belongs_to :commodity
belongs_to :company
end
Company
型号:
class Company < ActiveRecord::Base
has_many :accountings
end
Accounting
不仅仅是商品和书籍(和公司)之间的关系,它也代表了一种商业模式。
此处的约束是:当Commodity
添加新的Book
时,必须为每个 {{1}创建新的Accounting
}。 (2)
我确实尝试将Company
与Book
,companies
联系起来。但是,它不起作用。即使它不代表商业模式,书也不关心公司,我认为Book是链接模型的好人选。 (3)
现在我正在考虑添加一个新模型accountings
,它通过此模型关联书籍和商品,并且在保存时,这个新模型会生成所有BookCommodity
所需的Accounting
条记录。 (4)
在添加第五个模型之前,我想问你是否有更好的方法来管理这些东西?
被修改
在Github,你可以找到一个demo_finance项目,只有这篇文章的代码。它有4个分支:
这里的关键任务是在书中添加或删除商品,并更新帐户,就像在版本(1)(主分支)中工作一样。
编辑#2
我试着这样做:
companies
但它不起作用。在更新时它会引发:
class Book < ActiveRecord::Base
has_many :accountings
has_many :commodity_companies, through: :accountings
has_many :commodities, through: :commodity_companies
end
我也尝试这样做:
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly
Cannot modify association 'Book#commodities' because it goes through more than one other association.
但是,这个构建操作不会在使用class Book < ActiveRecord::Base
has_many :accountings do
def build(attributes = {}, &block)
Company.all.each do |company|
@association.build(attributes.merge(company_id: company.id), &block)
end
end
end
has_many :commodities, through: :accountings
end
的更新书籍上调用。
答案 0 :(得分:0)
这只是一个解决方案。之后,我深深地了解了你的问题。我要说你必须在你的书形式中添加所有公司作为多项选择。我知道也许它现在会让你的想法脱离你的想法,但你必须遵循铁路流量。您已经说过要通过基于公司的会计更新商品,然后您想自动更新数据,但您的代码不提供这些。
选项1
这是建议:
<强>控制器强>
添加新的强参数
def book_params
params.require(:book).permit(:name, commodity_ids: [], company_ids: [])
end
<强>模型强>
您需要修改一些关联
class Book < ActiveRecord::Base
has_many :accountings
has_many :commodities, through: :accountings
has_many :companies, through: :accountings
end
class Accounting < ActiveRecord::Base
belongs_to :book
belongs_to :commodity
belongs_to :company
end
class Commodity < ActiveRecord::Base
has_many :accountings
has_many :companies, through: :accountings
has_many :books, through: :accountings
end
class Company < ActiveRecord::Base
has_many :accountings
has_many :books through: :accountings
has_many :commodites through: :accountings
end
查看强>
您可以选择所有公司,以便用户无需选择选项(可选)
<%= form_for(book) do |f| %>
<%= render 'shared/error_messages', object: book %>
<%= f.label :company_ids %>
<%= f.collection_select :company_ids, Company.all, :id, :name, {:selected: Company.all.map(&:id)}, {multiple: true} %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :commodity_ids %>
<%= f.collection_select :commodity_ids, Commodity.all, :id, :name, {}, {multiple: true} %>
<br />
<%= f.submit class: 'btn btn-primary' %>
<% end %>
另外,请尝试不要创建BookCommodity
来创建Accounting
,因为BookCommodity
等于Accounting
选项2
对于另一个选项,如果您不想在表单中显示,可以在before_save回调中添加模型。这只是
before_save :set_companies
def set_companies
self.company_ids = Company.all.map(&:id)
end
<强>结论:强>
那些有利有弊。在第一个选项中,用户可以选择公司,但用户需要先选择公司。最后一个,用户不选择公司,但当用户想要更新书籍或商品的公司时,他将遇到困难。与往常一样,我更喜欢您使用第一种方式,因为用户将易于编辑。就这样。我希望这可以帮到你。
答案 1 :(得分:0)
回答我自己的问题:
我没有选择这个答案作为正确答案。因为,虽然它有效,但我认为可能有更好的答案,这应该得到赏金。
我让它覆盖方法commodity_ids=
,还需要将dependent: :destroy
选项添加到帐户,并将范围-> {uniq}
添加到商品中。
class Book < ActiveRecord::Base
has_many :accountings, dependent: :destroy
has_many :commodities, ->{ uniq }, through: :accountings
def commodity_ids=(ids)
self.accountings = Commodity.where(id: ids).map do |commodity|
Company.all.map do |company|
accountings.find_by(company_id: company.id, commodity_id: commodity.id) ||
accountings.build(company: company, commodity: commodity)
end
end.flatten
end
def commodities=(records)
self.commodity_ids = records.map(&:id)
end
end
class Commodity < ActiveRecord::Base
end
class Company < ActiveRecord::Base
end
class Accounting < ActiveRecord::Base
belongs_to :book
belongs_to :commodity
belongs_to :company
def to_s
"#{book.name} - #{company.name} - #{commodity.name}"
end
end
从头开始在控制台运行:
~/ (main) > Book.create name: 'Book 1'
=> #<Book id: 1, name: "Book 1", created_at: "2015-09-30 11:47:23", updated_at: "2015-09-30 11:47:23">
~/ (main) > Commodity.create name: 'Commodity 1'
=> #<Commodity id: 1, name: "Commodity 1", created_at: "2015-09-30 11:47:37", updated_at: "2015-09-30 11:47:37">
~/ (main) > Commodity.create name: 'Commodity 2'
=> #<Commodity id: 2, name: "Commodity 2", created_at: "2015-09-30 11:47:40", updated_at: "2015-09-30 11:47:40">
~/ (main) > Commodity.create name: 'Commodity 3'
=> #<Commodity id: 3, name: "Commodity 3", created_at: "2015-09-30 11:47:42", updated_at: "2015-09-30 11:47:42">
~/ (main) > Company.create name: 'Company 1'
=> #<Company id: 1, name: "Company 1", created_at: "2015-09-30 11:47:51", updated_at: "2015-09-30 11:47:51">
~/ (main) > Company.create name: 'Company 2'
=> #<Company id: 2, name: "Company 2", created_at: "2015-09-30 11:47:54", updated_at: "2015-09-30 11:47:54">
~/ (main) > bb = Book.first
=> #<Book id: 1, name: "Book 1", created_at: "2015-09-30 11:47:23", updated_at: "2015-09-30 11:47:23">
~/ (main) > bb.commodity_ids = ['', nil, 1, '3']
=> [
[0] "",
[1] nil,
[2] 1,
[3] "3"
]
~/ (main) > bb.save
=> true
~/ (main) > bb.reload
=> #<Book id: 1, name: "Book 1", created_at: "2015-09-30 11:47:23", updated_at: "2015-09-30 11:47:23">
~/ (main) > bb.accountings.map(&:to_s)
=> [
[0] "Book 1 - Company 1 - Commodity 1",
[1] "Book 1 - Company 2 - Commodity 1",
[2] "Book 1 - Company 1 - Commodity 3",
[3] "Book 1 - Company 2 - Commodity 3"
]
~/ (main) > bb.commodities.map(&:name)
=> [
[0] "Commodity 1",
[1] "Commodity 3"
]
~/ (main) > bb.commodity_ids = ['']
=> [
[0] ""
]
~/ (main) > bb.save
=> true
~/ (main) > bb.reload
=> #<Book id: 1, name: "Book 1", created_at: "2015-09-30 11:47:23", updated_at: "2015-09-30 11:47:23">
~/ (main) > bb.commodities.map(&:name)
=> []
~/ (main) > bb.accountings.map(&:to_s)
=> []
~/ (main) >
答案 2 :(得分:0)
这是acts_as_tenant宝石的完美应用。我在生产应用程序中运行这个gem,这是一个很好的解决方案。基本上,您可以使用此gem来获取原始工作解决方案并为系统中的每个公司复制它。每个公司的数据将在一个数据库中与其自己的系统隔离。
文档非常好,但我很乐意回答有关实施的具体问题。我将这个宝石与Sorcery和Rolify结合起来进行身份验证和授权,但这取决于你。我可以在一张图片上有很多公司。在您的情况下,您的公司可以轻松切换到不同的公司,但您可以允许个别公司只访问他们的数据。
对于这样一个复杂的问题,这是一个优雅而简单的解决方案。