【问题标题】:ActiveRecord Relation scoped access associated belongs_to objectActiveRecord 关系范围访问关联的 belongs_to 对象
【发布时间】:2016-07-01 05:05:17
【问题描述】:

我想知道是否有办法在 ActiveRecord::Relation 中访问关联的 belongs_to 对象

例如在这种关系中:

class Customer < ActiveRecord::Base
  has_many :invoices
end


class Invoice < ActiveRecord::Base
  belongs_to :customer

  def self.check_new
    # do something
  rescue => e
    MyLogger.log_error_for(customer, __method__)
  end
end

所以如果我打电话给

Customer.first.invoices.check_new

并发生错误。我想为这个特定的客户记录这个错误。

我知道我可以在类方法中使用scoped 获取发票并调用scoped.first.customer。 但这似乎有点脏。此外,如果还没有任何发票,这将不是一个解决方案。

我不知道是否有办法做到这一点,但对 scoped 的检查会给你类似的东西:

SELECT * FROM SOME JOIN WHERE customer_id = 1

所以有一种对 Customer 对象的引用。 有人可以帮忙吗?

更新: 这只是一个例子。其实我不做日志记录什么的。当然,它们是进行日志记录的更好方法。 我想要的只是将更新集合的代码放在模型类中。就像普通的 Customer.first.invoices.create 一样。并且可以访问此关系的父级。

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

    可能是这样的:

      customer = Customer.first
      customer.invoices.each {|invoice| invoice.check_new}
    

    其他一些想法:

    更新:

    我认为Customer.first.invoices.check_new 会抛出错误,因为您正试图在集合上调用类方法。

    ^^^

    这是不正确的。见here

    您可以按照您的要求进行操作。我不知道。看起来真的很纠结。我确定您有一个有效的用例。但是,尚不清楚它是什么。我仍然可能会做类似的事情:

      customer = Customer.first
      customer.invoices.check_new
    

    这确实回答您的问题,如果它不适合您的用例,请致歉。

    结束更新。

    所以,.check_new 应该是一个实例方法。大致如下:

      class Invoice < ActiveRecord::Base
    
        def check_new
          if errors = check_for_errors
            MyLogger.log_error_for(customer, __method__, errors)
          end
        end
    
        private 
    
        def check_for_errors
          ... error checking logic
        end
    
      end       
    

    为了让你的模型保持苗条,你可以制作一个普通的旧 Ruby 对象,例如:

      #app/managers/invoice_manager.rb
      class InvoiceManager
    
        class << self
    
          def check_new(invoices)
            invoices.each do |invoice|
              @invoice = invoice
              if @errors = invoice_errors
                log_errors
              end
            end
          end
    
          private
    
          def invoice_errors
            ... invoice error checking logic
          end
    
          def log_errors
            MyLogger.log_error_for(invoice.customer, caller[0][/`.*'/][1..-2], @errors)
          end
    
        end 
    
      end
    

    在这种情况下,你会这样做:

      InvoiceManager.check_new(Customer.first.invoices)
    

    这样一来,您的 Invoice 模型将恢复为仅处理数据库交互,并且您的业务逻辑完全分离。

    此外,使用这种方法,您的控制器根本不需要访问Invoice 类或其实例。或者,对Invoice 类一无所知。这改进了ControllerModel 层之间的分离。并减少对Invoice 的潜在下游更改的破坏性。

    最后,我发现测试 PORO 比测试控制器容易得多。

    【讨论】:

    • 此解决方案将解决这个特定问题(以更好的设计方式)。但这只是一个例子,也许不是最好的,对此感到抱歉。帖子的主题是主要问题。在集合上调用类方法不会引发错误,它只是调用方法。
    • 很高兴能为您提供帮助。祝你好运!如果您愿意,请记住“接受”。顺便说一句,我认为你的例子是可行的 - 所以,干得好。
    • 更正你在 ActiveRecord::Relation 上调用类方法。我会更新我的帖子。
    • 感谢您的更新,我已经知道相关帖子,不幸的是它没有回答我的问题。范围内与客户有关系,如何在范围内访问此关系。
    • 好的。提供了更多更新。我确定您有一个有效的用例。不知道答案。道歉。
    【解决方案2】:

    你有点困惑。

    为了简单起见,这是一个工作示例

    任何控制器

    def show  #(no matter which one, could be index or anything with GET request)
      @user = Customer.first
      @invoice = @user.invoices.first   #works with the properly set up belongs_to and has_many associations which you did well
      @invoice.check_new  #it is called on an instance (object) of invoice class, not on the class it self as you do
    end
    

    类发票

     def check_new  #if you put self before the method name then it is a class method and self is equal to Invoice (class name), if you don't then it's an instance method
       self.do_something  #now self means the instance itself which is the single @invoice at the moment. If you use self within the class method then this self would mean Invoice class just like in the name of the method
     end
    

    基本上,当您使用类本身时,您会使用类方法,并且您不会尝试对单个实例执行任何操作。例如

    @invoices = Invoice.limit(10).order(create_at: :desc) 
    

    在这里你试图找到类的一堆实例,所以你不能在单个实例上使用方法。但是,假设您想修改某些实例的属性。所以你必须去那个实例并改变它:

    @invoices.each do |invoice|
      invoice.check_new
      invoice.amount = 10
    end
    

    这里你一次改变一个实例,所以你需要实例方法。

    我建议您访问谷歌并查看差异。这在 Rails 中非常重要

    【讨论】:

    • 继续举例:我不想在 Invoice 实例上调用 check_new。我想在客户的“/所有发票”上称呼他们。我对该主题进行了一些研究,并采取了一些解决方法。
    • 你想用这个方法做什么?如果您想处理 1 个或多个实例的属性,那么您将使用实例方法。首先,您必须使用类方法找到给定的实例。在这种情况下,@customer = Customer.first 是您调用Customer class 的类方法。 @invoices = @customer.invoices 是一个实例方法,因为你在 @customer 实例上调用它。然后你遍历许多@invoices,但是由于迭代,你将调用像invoice.some_method这样的实例方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-13
    相关资源
    最近更新 更多