正确的 Rails 大量逻辑方法

Correct Rails Method for Lots of Logic

我目前正在建设一个每周 运行 进行自主竞赛的网站。我的逻辑检查前一周是否分配了获胜者,如果没有,它会滚动,找到获胜者并分配奖杯。

逻辑一切正常,但其中很多是在应用程序控制器中 运行,在我的内心深处,我觉得这是不对的。

比如第一名比第二名多,第二名比第三名多,就需要创建一个第一名第二名和第三名的奖杯,奖励给正确的用户。

        if first_place > second_place && second_place > third_place
          @week_previous.winner_id  = week_entries[0].id
          @week_previous.save

          first_trophy = week_entries[0].user.trophies.new
          first_trophy.week_id  = @week_previous.id
          first_trophy.user_id  = week_entries[0].user_id
          first_trophy.position = "first"
          first_trophy.country  = week_entries[0].user.country 
          first_trophy.pro      = false
          first_trophy.save

          if second_place >= 1
            second_trophy = week_entries[1].user.trophies.new
            second_trophy.week_id  = @week_previous.id
            second_trophy.user_id  = week_entries[1].user_id
            second_trophy.position = "second"
            second_trophy.country  = week_entries[1].user.country 
            second_trophy.pro      = false
            second_trophy.save
          end

          if third_place >= 1
            third_trophy = week_entries[2].user.trophies.new
            third_trophy.week_id  = @week_previous.id
            third_trophy.user_id  = week_entries[2].user_id
            third_trophy.position = "third"
            third_trophy.country  = week_entries[2].user.country 
            third_trophy.pro      = false
            third_trophy.save
          end
        end

这是将奖杯直接构建到控制器中,我经常听到 'fat model skinny controller' 的争论,我觉得我 运行 认为这完全相反!

如何将奖杯作品移动到模型中?我确信我可以在周模型中使用类似 after_save 的东西,但我不完全确定如何保持逻辑正常工作。我尝试了几次,但经常收到错误 undefined method to_model.

我知道我可以继续努力让它发挥作用,但我只是觉得这不是做事的 'Rails Way',所以我想在早期阶段解决它。

非常感谢任何帮助。

谢谢!

根据评论编辑:

感谢您抽出宝贵时间查看此内容。简而言之 shell,我想要实现的是一个从周一到周日 运行 比赛的系统。 'Active Week' 的范围是 Date.today 介于 :start_date 和 :end_date.

之间的那一周

下周一开始新的一周,这会将活跃的一周移到前一周,然后将奖杯分配给前一周的前 3 名条目。它通过检查前一周是否有 winner_id 来分配奖杯。如果是,则没有 运行 任何逻辑,但是一旦范围移动到新的一周,前几周 :winner_id 现在为零,因此逻辑 运行s新一周有人第一次来本站。

简化一下:

因此,用户在当前活跃周对条目进行投票。一旦该周超出活动范围,它就会为本周排名前 3 位的用户创建奖杯。

在不了解上下文的情况下很难给出好的建议。但引起我注意的是,该代码中有很多重复,并且您在这些重复中更新了用户奖杯。因此,作为第一步,我至少会将该逻辑转移到用户中:

# in user.rb
def record_trophy_win(prev_week, entry, position)
  trophies.create(
    week_id:  prev_week.id,
    user_id:  entry.user_id,
    position: position,
    country:  entry.user.county,
    pro:      false 
  )
end

这允许将控制器中的部分更改为:

if first_place > second_place && second_place > third_place
  @week_previous.update_attribute(:winner_id, week_entries[0].id)

  first_trophy = week_entries[0].user.record_trophy_win(
    @week_previous, week_entries[0], 'first'
  )

  second_trophy = week_entries[1].user.record_trophy_win(
    @week_previous, week_entries[1], 'second'
  ) if second_place >= 1

  third_trophy = week_entries[2].user.record_trophy_win(
    @week_previous, week_entries[2], 'third'
  ) if third_place >= 1
end

该逻辑在下一步中可能属于 Trophy class。取决于上下文...

而且我注意到你的 week_entry 有一个 user。然后您向 user 添加一个 trophy,它又具有一些 user 字段。这不会导致循环依赖吗?或者您是否使用完全相同的条目覆盖现有记录?这需要一些清晰度。

隔离业务逻辑的一种方法是服务对象设计模式。示例:

class TrophyRewarder
  def initialize(winners, last_week_winners)
    @winners = winners
    @last_week_winners = last_week_winners
  end

  def reward(options)
    # All code to determine and store winners comes here
  end
end

您可以将此代码放在文件夹 app/services 中,然后在您的控制器中调用它,如下所示:

trophy_rewarder = TrophyRewarder.new(users, Winners.from_last_week)
trophy_rewarder.reward(options)

好处是您可以从控制器调用此服务对象,也可以从后台任务调用。另一个好处是服务对象可以使用很多不同的 data/AR 模型,但它并不依赖于模型。

我希望这对展示如何组织代码有所帮助。