我正在尝试使用Rails 4.1构建一个小费用跟踪应用。当用户提交费用请求时,默认情况下将其状态标记为待处理。管理员必须批准该请求。我使用state_machine gem来做到这一点。
使用nested_form_for从费用显示页面添加注释,如下所示:
<%= nested_form_for (@expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.collection_select :state, @expense.state_transitions, :event, :human_to_name, :include_blank => @expense.human_state_name, class: "form-control" %>
</div>
<%= f.fields_for :comments, @expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
控制器看起来像:
class ExpensesController < ApplicationController
def new
@expense = Expense.new
@item = @expense.items.build
@comment = @expense.comments.build
end
def show
@expense = Expense.find(params[:id])
@items = Item.where(:expense_id => @expense.id)
end
def update
@expense = Expense.find(params[:id])
if @expense.update(expense_params)
if @expense.state == "approved"
ExpenseMailer.expense_approved(@expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
elsif @expense.state = "rejected"
ExpenseMailer.expense_declined(@expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
end
else
render 'edit'
end
end
private
def expense_params
params.require(:expense).permit(:claim, :department_id, :expense_type_id, :expense_attachment, :state, :notes, items_attributes: [:id, :description, :amount, :issue_date, :_destroy], comments_attributes:[:id, :comment, :expense_id])
end
问题是,如果我在不改变下拉列表中的状态的情况下添加评论,我会认为状态无效&#39;错误和编辑页面显示。我只需点击更新按钮即可完成此操作。但是,评论还没有创建。另一方面,如果我更改状态并添加注释,则会显示注释而没有任何问题。
params的价值是:
Started PATCH "/expenses/14" for 127.0.0.1 at 2014-08-15 13:31:40 +0530
Processing by ExpensesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MAEL2UYzos76NV6/eumHkXcpR2ge09wm6eOGQ+eEGCA=", "expense"=>{"state"=>"", "comments_attributes"=>{"0"=>{"comment"=>"vv"}}}, "commit"=>"Submit", "id"=>"14"}
状态机的费用模型如下:
state_machine initial: :pending do
state :pending
state :approved
state :rejected
event :approved do
transition [:pending, :rejected] => :approved
end
event :rejected do
transition [:pending, :approved] => :rejected
end
end
猜猜在构建评论属性时我犯了一些错误。有人能让我知道我必须在哪里做出改变吗?
拒绝的记录器信息:
Started GET "/expenses/17" for 127.0.0.1 at 2014-08-15 16:22:43 +0530
Processing by ExpensesController#show as HTML
Parameters: {"id"=>"17"}
[1m[35mExpense Load (0.2ms)[0m SELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1 [["id", 17]]
[1m[36mItem Load (0.1ms)[0m [1mSELECT "items".* FROM "items" WHERE "items"."expense_id" = ?[0m [["expense_id", 17]]
[1m[35mComment Load (0.2ms)[0m SELECT "comments".* FROM "comments" WHERE "comments"."expense_id" = ? [["expense_id", 17]]
Rendered expenses/show.html.erb within layouts/application (16.2ms)
Completed 200 OK in 45ms (Views: 42.8ms | ActiveRecord: 0.5ms)
Started PATCH "/expenses/17" for 127.0.0.1 at 2014-08-15 16:22:53 +0530
Processing by ExpensesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MAEL2UYzos76NV6/eumHkXcpR2ge09wm6eOGQ+eEGCA=", "expense"=>{"state"=>"rejected", "comments_attributes"=>{"0"=>{"comment"=>"checking logger for rejected!"}}}, "commit"=>"Submit", "id"=>"17"}
[1m[36mExpense Load (0.2ms)[0m [1mSELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1[0m [["id", 17]]
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (8.1ms)[0m [1mUPDATE "expenses" SET "state" = ?, "updated_at" = ? WHERE "expenses"."id" = 17[0m [["state", "rejected"], ["updated_at", "2014-08-15 10:52:53.030676"]]
[1m[35mSQL (0.2ms)[0m INSERT INTO "comments" ("comment", "created_at", "expense_id", "updated_at") VALUES (?, ?, ?, ?) [["comment", "checking logger for rejected!"], ["created_at", "2014-08-15 10:52:53.040889"], ["expense_id", 17], ["updated_at", "2014-08-15 10:52:53.040889"]]
[1m[36m (4.2ms)[0m [1mcommit transaction[0m
Redirected to http://localhost:3000/expenses
Completed 302 Found in 24ms (ActiveRecord: 12.8ms)
要获得批准的记录器信息:
Started GET "/expenses/16" for 127.0.0.1 at 2014-08-15 16:22:30 +0530
Processing by ExpensesController#show as HTML
Parameters: {"id"=>"16"}
[1m[35mExpense Load (0.3ms)[0m SELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1 [["id", 16]]
[1m[36mItem Load (0.2ms)[0m [1mSELECT "items".* FROM "items" WHERE "items"."expense_id" = ?[0m [["expense_id", 16]]
[1m[35mComment Load (0.3ms)[0m SELECT "comments".* FROM "comments" WHERE "comments"."expense_id" = ? [["expense_id", 16]]
Rendered expenses/show.html.erb within layouts/application (167.3ms)
Completed 200 OK in 244ms (Views: 213.7ms | ActiveRecord: 1.1ms)
Started PATCH "/expenses/16" for 127.0.0.1 at 2014-08-15 16:22:41 +0530
Processing by ExpensesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MAEL2UYzos76NV6/eumHkXcpR2ge09wm6eOGQ+eEGCA=", "expense"=>{"state"=>"approved", "comments_attributes"=>{"0"=>{"comment"=>"checking logger!"}}}, "commit"=>"Submit", "id"=>"16"}
[1m[36mExpense Load (0.2ms)[0m [1mSELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1[0m [["id", 16]]
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (0.5ms)[0m [1mUPDATE "expenses" SET "state" = ?, "updated_at" = ? WHERE "expenses"."id" = 16[0m [["state", "approved"], ["updated_at", "2014-08-15 10:52:41.604580"]]
[1m[35mSQL (0.5ms)[0m INSERT INTO "comments" ("comment", "created_at", "expense_id", "updated_at") VALUES (?, ?, ?, ?) [["comment", "checking logger!"], ["created_at", "2014-08-15 10:52:41.607555"], ["expense_id", 16], ["updated_at", "2014-08-15 10:52:41.607555"]]
[1m[36m (4.0ms)[0m [1mcommit transaction[0m
Redirected to http://localhost:3000/expenses
Completed 302 Found in 17ms (ActiveRecord: 5.3ms)
答案 0 :(得分:1)
我不知道为什么您收到错误,但我会为state_machine
/ aasm
提供一些建议。宝石
-
状态机
由于Rails是object-orientated,你必须了解这些state machine宝石是如何工作的 - 它们是建立一个状态机的电子方法的推断&#34; (用于预测有限的&#34;状态&#34;在电路中):
我试图证明的是,通过在您的应用中加入state machine
,您实际上会指示对象的状态(&# 39; s 不只是另一个属性)
目前,您将state
模型的Comment
属性视为属性,当它可以被视为对象本身时
-
<强>对象强>
请注意State Machine repo中的此功能:
注意这与 state 属性无关?
我认为您最好将state
方法视为一种影响state_machine
本身的方法。我会以几种方式做到这一点:
- 设置状态&#34;默认&#34; state_machine声明中的状态
- 使用OOP原则验证 state 对象
醇>
#app/models/expense.rb
Class Expense < ActiveRecord::Base
state_machine :state, :initial => :pending do #-> sets the state to "pending" unless specified otherwise
end
end
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def update
if @expense.approved?
...
end
end
end
-
<强>修正强>
关于您无法创建评论,我认为问题将是双重的
首先,您在视图中构建您的评论。除了任何事情,这是不好的做法(针对MVC) - 您最好在model
内构建关联对象:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
def self.build id
expense = (id.present?) self.find id : self.new
expense.comments.build
return expense
end
end
这允许您执行以下操作:
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def new
@expense = Expense.build
end
def edit
@expense = Expense.build params[:id]
end
end
这基本上会为您的嵌套comments
表单提供触发编辑所需的预构建嵌套对象。新方法(因此您不需要在视图中调用@expense.comments.build
)
关于非保存功能 - 我当然会看看你如何保存state
属性。 我怀疑你没有正确传递属性(IE默认提交时你state
param使用的值不正确
我建议使用以下内容:
- 从&#34;默认&#34;调查你的参数更新
state
属性是否与属性的模型定义匹配?- 如果没有,那将是你的问题
醇>
-
<强>更新强>
感谢您的更新
好的,问题似乎是如果state
值是默认值,则不会传递它。我认为解决这个问题的方法是set a default
value for the collection_select
:
:include_blank => @expense.human_state_name
<%= f.collection_select :state, @expense.state_transitions, :event, :human_to_name, { selected: @expense.human_state_name}, class: "form-control" %>
更新2
由于state_machine
使您能够跟踪&amp;成功转换后的fire实例方法,您可能希望执行以下操作:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
state_machine :state, :initial => :pending do
state :pending
state :approved
state :rejected
event :approve do
transition [:pending, :rejected] => :approved
end
event :reject do
transition [:pending, :approved] => :rejected
end
after_transition :on => :approved, :do => :send_approval_email
after_transition :on => :rejected, :do => :send_rejection_email
def send_approval_email
ExpenseMailer.expense_approved(self).deliver #-> might need to call outide of state_machine block
end
def send_rejection_email
ExpenseMailer.expense_declined(self).deliver
end
end
end
这将使您能够执行以下操作:
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def update
@expense = Expense.find params[:id]
if @expense.update(expense_params)
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
end
end
end
顺便说一下,你需要改变你的事件&#34;为你的&#34;州和#34;提供不同的名字。根据上面面向对象的引用,您需要能够调用@object.approve
等