2013-03-01 45 views
0

我正在使用扩展ActiveRecord :: Base的几个模型类来处理Rails应用程序。我已经在这些模型类中的一些模型类上实现了多种方法,可以用来更新数据库内容,并且这些方法都是按照我希望它们工作的方式工作的。例如,我有一个TeamStats类存储各种统计值到数据库中,我已经定义了允许我通过运用博弈结果来更新这些属性的方法:对ActiveRecord实例进行更改我从不想提交

class TeamStats < ActiveRecord::Base 

    belongs_to :team 

    def << game_result 
    # Lots of stuff here to update persisted attributes for 
    # wins, losses, total points scored, etc 
    end 

end 

class Team < ActiveRecord::Base 
    has_one :stats, :class_name => TeamStats, :dependent => destroy 
end 

现在我发现自己想要重用逻辑但是有一些我从不想提交给数据库的临时数据。例如,我想重新计算使用游戏子集的团队的统计数据,而不是所有的统计数据。所以,我有代码,不会是这样的:

# relevant_game_results is an array containing game results I want considered 
teams.each do |team| 
    new_stats = TeamStats.new(:team => team) 
    relevent_game_results.each do |results| 
     new_stats << results 
    end 
end 

# Do stuff to choose a team based on these un-persisted stats that have been 
# assigned to the team 

# After I'm done, through all the team_stats I created and make sure all of the 
# related team model objects still reference their original team stats values 

我给这家最初的计划是修改我的模型实例,然后调用这些实例reload当我完成了。例如:

teams.each do |team| 
    team.reload 
end 

我认为,将工作,但我不得不这样做对大量的对象,如果我可以,我宁愿做一个操作。

看来我真正需要的是一个总是回滚而不是承诺的事务。什么是最合适的“轨道方式”来做到这一点?我应该在transaction区块内做到这一点,然后在我的区块末尾加注ActiveRecord::Rollback?换句话说,像这样的东西?

Team.transaction do 

    teams.each do |team| 
     new_stats = TeamStats.new(:team => team) 
     relevent_game_results.each do |results| 
      new_stats << results 
     end 
    end 

    # Do stuff to choose a team based on these un-persisted stats that have been 
    # assigned to the team 

    raise ActiveRecord::Rollback 
end 

这似乎有点“脏”我但那只是我的Java背景透进来;) 有没有一种更简洁的方法就是更符合Rails的方式行?

更新:事实证明,将此包装在事务中并回滚不仅是丑陋的,看起来很难使其正常工作。因为执行此代码的方法本身可以在另一个事务中,并且由于ActiveRecord关系对象的某些更改倾向被自动保存,所以我不得不跳过许多环节才能使其工作。

基于接受的答案中的建议,我已经完全创建了一个新的TeamStats对象,并且从不保存它。似乎对我更好。

+0

你能提供一个愚蠢的简单的操作示例吗? – 2013-03-01 02:51:51

回答

1

而不是回滚,为什么不复制,然后永远不会保存它?例如:

# in TeamStats 

def hypothetically_add(games) 
    copy = dup 
    games.each {|game| copy << game } 
    copy 
end 

也许还追加游戏的统计对象一次一个是不正确的方法,如果您正在生成基于查询所有游戏的比赛统计。操作游戏的集合可能会更好。

# in Team 

def stats_from_games_where(*conditions) 
    games.where(*conditions).reduce(Hash.new(0)) do |stats, game| 
    stats[:wins] += 1 if # we won 
    ... 
    end 
end 
+0

我也考虑过制作副本......最后,我可能会被迫这样做。我在制作副本时遇到的问题是,TeamStats副本会将'belongs_to'设为同一个团队,该团队使用'has_one'来处理与其统计资料的关系。因此,当我制作统计数据副本时,相关团队是否也会更新以指向新的(未保存的)统计数据?我也可以制作团队副本,但是我会让它更难以回到我需要的东西 - 参考根据其统计信息选择的原始Team对象。 – 2013-03-02 02:48:48

+0

不要通过关联。只需制作一个重复的TeamStats对象并手动分配team_id即可。我不认为这会覆盖它,但你必须检查。 – AJcodez 2013-03-02 17:07:58

+0

事实证明,我完全不必担心这种关联。在考虑了你的建议并参考了文档后,我发现它说:“将对象分配给belongs_to关联不会保存对象,因为外键字段属于父对象,它也不会保存父对象。”所以我改变了我的代码来创建一个副本,并且从来没有保存过这个副本,并且一切都很顺利。谢谢! – 2013-03-02 18:54:36

1

你应该看看像ActiveAttr(https://github.com/cgriego/active_attr),或Virtus https://github.com/solnic/virtus。你会创建完全在内存中的对象。还有一个很好的railscast:http://railscasts.com/episodes/326-activeattr

+0

我认为这种方法存在的问题是我没有尝试在大多数时间使用内存中的对象。在大多数情况下,我真的希望将这些模型更改发送到数据库。这只是在某些情况下,我想要做一些不承诺的操作。 – 2013-03-01 03:28:11

+0

为什么不把逻辑转移到只负责计算统计信息的类中?然后它将负责根据需要更新TeamStats(或根据情况)。 – rainkinz 2013-03-01 04:25:39

+0

我一直在想这件事,但它看起来像很多额外的工作。所涉及的逻辑设置了自我的几个不同属性,所以如果我将该逻辑提取到它自己的类(或模块)中,那么我将不得不实现具有相同关系属性的另一组类,这些属性不会扩展ActiveRecord :: Base。我真的想要使用ActiveRecord给我的所有行为,我只是不想更多地控制我的更改是否被保存。 – 2013-03-01 04:59:58