使用更新的属性调用Before_update回调

时间:2015-07-26 18:03:26

标签: ruby-on-rails

为什么before_update回调使用更新的参数?它不应该使用原始的参数吗?

我有一个Round模型belongs_to Case模型(Case has_many Rounds

Case的部分属性是通过Round的回调计算出来的。例如,当有一个新的Round获胜时,Case的wins属性会增加1。

我现在遇到回调问题,我需要更新我的案例。我在add_round模型中有一个方法delete_roundRound,分别添加和删除给定回合的属性。

为了澄清, delete_round和add_round实际上没有删除或创建回合,他们会删除或添加回合的统计数据到案例的属性

这是我的圆形模型:

class Round < ActiveRecord::Base

  belongs_to :case

  after_save     :add_round
  before_update  :delete_round
  before_destroy  :delete_round

  private
    # Adds this Round's stats to its Case
    def add_round
      self.case.add_round(self)
    end

    # Deletes this Round's stats from its Case
    def delete_round
      self.case.delete_round(self)
    end
end

以下是案例模型:

class Case < ActiveRecord::Base

  has_many :rounds,     dependent: :destroy

  def add_round(round)
    if round.win?
      update_attribute(wins: wins + 1)
    else
      update_attribute(losses: losses + 1)
    end
    update_attribute(win_percentage: 100 * wins / (wins + losses)
  end

  def delete_round(round)
    if round.win?
      update_attribute(wins: wins - 1)
    else
      update_attribute(losses: losses - 1)
    end
    update_attribute(win_percentage: 100 * wins / (wins + losses)
  end
end

和架构:

create_table "cases", force: :cascade do |t|
  t.datetime "created_at",                              null: false
  t.datetime "updated_at",                              null: false
  t.integer  "wins",                      default: 0
  t.integer  "losses",                    default: 0
  t.float    "win_percentage",            default: 0.0
end

create_table "rounds", force: :cascade do |t|
  t.integer  "case_id"
  t.boolean  "win"
  t.datetime "created_at",                 null: false
  t.datetime "updated_at",                 null: false
end

这是我想要发生的粗略的伪代码示例:

case = Case.create(wins: 0, losses: 0, win_percentage: 0)
round = Round.create(win: true, case_id: case.id)

# add_round is called

case.wins = 1
case.losses = 0
case.win_percentage = 100

round.update_attribute(win: false)

# before that parameter is updated, delete_round is called on the round with the old attributes

case.wins = 0
case.losses = 0
case.win_percentage = 0

# after that parameter is updated, add_round is called on the updated round

case.wins = 0
case.losses = 1
case.win_percentage = 0

round.destroy

# delete_round is called

case.wins = 0
case.losses = 0
case.win_percentage = 0

更新后,添加带有新参数的回合

出于某种原因,before_update将被发送更新的属性。为什么会这样?我该如何更改回调?

2 个答案:

答案 0 :(得分:0)

您为before_validation和before_save调用delete_round。在两种情况下调用before_save:on update和on create。 如果你只想在更新时调用delete_round,你必须告诉:

before_update :delete_round

before_save :delete_round, on: :update

<强>更新

问题在于你调用回调的方式和方法的逻辑:

after_save :add_round before_update :delete_round

只有在创建你的'after_save:add_round'时才会调用before_update而不调用delete_round。并且它们将在单个事务中调用相同的对象。因此,在大多数情况下,首先您将调用delete_round,然后调用add_round。由于他们正在进行相反的操作,因此最终不会有任何变化。

您实际想要做的是只调用一次结果处理,因此您的方法将是这样的:

def process_round(round) result = round.win? ? 1 : -1 update_attribute(wins: wins + result) update_attribute(losses: losses - result) end

我怀疑这种类型的回调变化很大,所以你只能以after_save为例:

在你的Round课程中: after_save :process_round

<强>更新

分离add_round和delete_round动作的另一种方法是使用这样的条件回调:

class Round&lt;的ActiveRecord ::基

belongs_to:case

before_update:add_round,if:win?

before_update:delete_round,除非:win?

before_destroy:delete_round

私有

# Adds this Round's stats to its Case
def add_round
  self.case.add_round(self)
end

# Deletes this Round's stats from its Case
def delete_round
  self.case.delete_round(self)
end

这可能是你真正需要的。

答案 1 :(得分:0)

我创建的另一个解决方案,除了已经提供的答案之外:

app.directive('myDirective', [ '$compile',
    function ($compile) {
        function postLink(scope, element, attrs) {
            // Here, both the DOM and the scope are available, so
            // you can extend the DOM with templates compiled against the scope

            // if you are using <my-directive template="button.template"></my-directive>
            var template = scope.$eval(attrs.template);

            // if you are using <my-directive template="{{ button.template }}"></my-directive>
            var template = attrs.template;  // interpolation is already processed against the scope

            // compile the template and append to existing DOM
            element.append($compile(template || defaultTemplate)(scope));
        }

        function template(element, attrs) {
            // Here, you cannot evaluate attrs.template against the scope
            // because the scope does not yet exist! Only the DOM is
            // available (you can use the raw values of attributes if needed)
            return '<div></div>';
        }

        return {
            template: template,
            link: postLink
        }
    }
])