如何创建更新 Child 属性的 before_action Rails

How to create a before_action that updates a Child's attributes Rails

对于这个应用,一个App有很多Elements,每个Elements有很多Features。我想在每次 AppsController#show 运行 时更新 Elements 属性。但是,我 运行 遇到了一个问题,因为我必须深入研究每个元素的 Features。我似乎无法从我的应用程序控制器调用 @app.elements.features(或根本无法调用 features。)

我在 App's Show View 上嵌入了表格,其中列出了应用程序的 ElementsFeatures,所以我不能将其称为 ElementsController#show 或其他简单的名称就像那样,因为那样它就永远不会更新了。我必须找到一些方法来从 AppsController 调用 ElementsController 中的此操作,但这对我来说听起来不太 Rails-ey。

这是我到目前为止所做的,现在我不知道下一步该做什么。完成这项工作的最佳方法是什么?

我的应用程序控制器 (parent):

before_action :set_completion, only: :show

def set_completion
   @app = App.find(params[:id])
   @app.update_attribute(:completion_status,
   (((@app.elements.where(completion: true).count) / (@app.elements.count).to_f) * 100 )
   )
 end

元素控制器(child):

  def update_feature_completion
  @app = App.find(params[:app_id])
      @element.update_attribute(:element_feature_completion,      
      (@element.features.where(completion: true).count)
      )
  end

编辑: 到目前为止我所拥有的(无论如何都不简单):

def set_completion
@parent = Parent.find(params[:id])

  @parent.childs.each do |child|
        if (child.childs_childs.where(completion: true).count) == (child.childs_childs.count) #if all childs_childs of the child are completed...
          then child.update_attribute(:completion, true) #...mark the child as completed.
            else child.update_attribute(:completion, false) #if all childs_childs of the child are not completed, then mark the child as not completed
        end
        #changes child completion status to % of childs_childs completed
        child.update_attribute(:child_completion_status,
          child.childs_childs.where(completion: true).count / child.childs_childs.count.to_f
          )
        @parent.update_attribute(:parent_completion_status,
            @parent.childs.child_completion_status.sum
          )

  end
end

Parent 型号

class Parent < ActiveRecord::Base
  belongs_to :user
  has_many :children, dependent: :destroy
  has_many :childs_children, dependent: :destroy
  accepts_nested_attributes_for :children
  accepts_nested_attributes_for :childs_childs
end

Child 型号

class Child < ActiveRecord::Base
  belongs_to :parent
  has_many :childs_children, dependent: :destroy
  accepts_nested_attributes_for :childs_children
end

Child的Child型号

class childs_child < ActiveRecord::Base
  belongs_to :child
  belongs_to :parent
end

最后一段代码如下所示:

@parent.update_attribute(:parent_completion_status,
   (@parent.children.sum(:child_completion_status) / @parent.children.count) * 100
    )

这基本上是通过找出所有 children 的完成百分比来找到 :parent_completion_status 的完成百分比。

我知道这很令人困惑,但我相信外面的人能够理解它。当我问这个问题时,我无法按照我最初想要的方式解决这个问题。

建议如果您尝试做类似的事情,请尝试按照我所做的步骤进行操作,除非有更简单的方法适合您。

这可能会在以后的路上咬到你的后方。每次 GET 请求到达显示页面时数据库中的数据都会发生变化,这可能会给您的服务器带来不必要的压力。想一想所有访问您页面的网络爬虫。他们会无意中触发这些数据库事务的发生。

如果您要更改任何持久化数据,应该通过 POST 方法来完成。动作回调通常不用于此类工作,通常应仅用于身份验证和缓存之类的事情。那么我可以提供替代解决方案吗?

所以我们从一个很好的深层嵌套关系开始。但是,当我们向 apps#show 发出 GET 请求时,我们不必不断地进行这些计算,而是必须首先查看导致这些变化的原因。看来您的数据只会随着功能更新为完整而真正改变。那就是您应该强制进行这些更改的地方。如果你做了这样的事情怎么办

让我们尝试采用 RESTful 方法来解决这个问题。由于功能已标记为完整,因此它们各自的元素和应用程序也应更新。我们还假设如果每个功能都未标记为完整,则无法将元素标记为完整。

在您的 apps#show 页面上,我假设您正在显示应用程序的所有元素和元素从属功能。我想您会希望您的用户单击某个功能上的按钮,将该功能标记为已完成。因此,让我们创建一个允许我们这样做的 RESTful 路由。

config/routes.rb

resources :features do 
  member do
    post 'complete' # features#complete
  end
end

所以现在我们有一个 RESTful 路由,它将在您的 FeaturesController 中查找完整的操作。

features_controller.rb

def complete
  if @feature.mark_as_complete
     # show some message that says "You successfully completed 'Feature A', Good job!"
  else
     #  Respond with nasty error message of why this request couldn't process
  end 
end

现在您有了一个与该功能配合使用的操作。现在您所要做的就是在您的 apps#show 页面中创建一个 link 并遍历每个元素的功能

app/views/apps/show.html.haml

- @app.elements.each do |element|
  - element.features.each do |feature|
    = feature.name
    = link_to complete_feature_path(feature), method: :post

当用户完成每个功能时,他们可以单击 link,这将向功能发送 post 方法#complete action

class Feature < ActiveRecord::Base
  def mark_as_complete
    self.update_attributes(completion: true)
    self.element.update_completion_status
  end
end

class Element < ActiveRecord::Base
  def update_completion_status
    if (self.feautres.where(completion: true).count) == (self.features.count) # if all features are complete
      self.update_attributes(completion: true)
      self.update_attributes(feature_completion_status: calculate_percent_complete)
      self.app.update_completion_status
    else
      self.update_attributes(feature_completion_status: calculate_percent_complete)
    end
  end

  private
    def calculate_percent_complete
      self.features.where(completion: true).count / child.childs_childs.count.to_f
    end
end

class Feature < ActiveRecord::Base
  def mark_as_complete
    self.update_attributes(completion: true)
    self.element.update_completion_status
  end
end

class App < ActiveRecord::Base
  def update_completion_status
    # similar to that of Element I assume
  end
end

这样,仅当 POST 请求将功能标记为完成时,才会更新相应的数据。您在 apps#show 操作中应该做的就是提供要显示的适当对象。