【问题标题】:"method missing" error on Rails/Ruby metaprogramming attemptRails/Ruby 元编程尝试出现“方法缺失”错误
【发布时间】:2014-06-15 13:18:00
【问题描述】:

我正在尝试第一次涉足元编程,但进展并不顺利!这是一个 Rails 4.1 应用程序,我正在尝试重构一个活动记录模型(用户)以结合两种非常相似的方法。原始方法是稍微复杂的 DB 调用,可以按预期工作。

原代码:

  def retweet_count(league)
    celebrity_ids = Roster.
      where("user_id = ? and league_id = ?", self.id, league.id).
      select(:celebrity_id).map { |r| r.celebrity_id }
    Tweet.where({
      tweet_date: league.start_date..league.end_date,
      celebrity_id: celebrity_ids
    }).select(:retweet_count).inject(0) do |sum, n|
      sum + ( n.retweet_count || 0 )
    end
  end

  def favorite_count(league)
    celebrity_ids = Roster.
      where("user_id = ? and league_id = ?", self.id, league.id).
      select(:celebrity_id).map { |r| r.celebrity_id }
    Tweet.where({
      tweet_date: league.start_date..league.end_date,
      celebrity_id: celebrity_ids
    }).select(:favorite_count).inject(0) do |sum, n|
      sum + ( n.favorite_count || 0 )
    end
  end

新代码:

  twitter_stats_count :retweet, :favorite

  private

  def twitter_stats_count(*stats)
    stats.each do |statistic|
      stat = send(statistic).to_s
      define_method "#{stat}_count" do |league|
        celebrity_ids = Roster.
          where("user_id = ? and league_id = ?", self.id, league.id).
          select(:celebrity_id).map { |r| r.celebrity_id }
        Tweet.where({
          tweet_date: league.start_date..league.end_date,
          celebrity_id: celebrity_ids
        }).select("#{stat}_count").inject(0) do |sum, n|
          sum + ( n.send("#{stat}_count") || 0 )
        end
      end
    end
  end

当我尝试启动我的 rails 服务器时新代码产生的错误:

/Users/kiddo/.rvm/gems/ruby-2.1.0/gems/activerecord-4.1.0.rc2/lib/active_record/dynamic_matchers.rb:26:in `method_missing': undefined method `twitter_stats_count' for User (call 'User.connection' to establish a connection):Class (NoMethodError)

我似乎无法弄清楚我做错了什么,所以任何指针都将不胜感激!


仅供参考,这是我工作的最终代码。我主要接受 Holger Just 的建议,但也吸收了其他几个方面的建议,所以大家都赞成!

  def team_ids(league)
    Roster.where(user_id: self.id, league_id: league.id).pluck(:celebrity_id)
  end

  def self.twitter_stats_count(*stats)
    stats.each do |statistic|
      stat = statistic.to_s
      define_method "#{stat}_count" do |league|
        Tweet.where({
          tweet_date: league.start_date..league.end_date,
          celebrity_id: self.team_ids(league)
        }).sum("#{stat}_count")
      end
    end
  end

  twitter_stats_count :retweet, :favorite

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-4 metaprogramming


    【解决方案1】:

    您的方法存在几个问题:

    • 您直接在类上调用twitter_stats_count,而不是类的实例。因此,该方法需要是类方法。您可以将其定义为类方法

      def self.twitter_stats_count(*stats)
        # ...
      end
      
    • 此外,您在定义方法之前调用该方法。在 Ruby 中,所有内容(甚至方法定义)都会被执行。因此,您只能在定义方法后调用它们。因此,您需要在定义之后调用 twitter_stats_count 方法。

    【讨论】:

      【解决方案2】:

      这看起来很复杂。如果我没记错的话,你可以通过重构代码来减少重复:

      def retweet_count(league)
        league_tweets(league).sum(:retweet_count)
      end
      
      def favorite_count(league)
        league_tweets(league).sum(:favorite_count)
      end
      
      def celebrity_ids(league)
        Roster.where(user_id: self.id, league_id: league.id).pluck(:celebrity_id)
      end
      
      def league_tweets(league)
        Tweet.where(
          tweet_date: league.start_date..league.end_date,
          celebrity_id: celebrity_ids(league)
        )
      end
      

      【讨论】:

        【解决方案3】:

        twitter_stats_count 应该是一个类方法,但是你做的是把它变成一个实例方法,也许你可以试试这个:

        # no private here
        def self.twitter_stats_count(*status)
            #your codes here
        end
        

        【讨论】:

          【解决方案4】:

          您收到此错误是因为您已将 twitter_stats_count 定义为私有方法,您不能在 self 上调用它。你必须把它放在一个实例方法中,而不是调用它。

          查看this.

          例如以下给出相同的错误:

          class Foo
              baz   
          
            private
            def baz
              puts "baz called"
            end
          end
          

          但是这会起作用:

          class Foo
            def dummy
              baz 
            end
          
          
            private
            def baz
              puts "baz called"
            end
          end
          
          foo = Foo.new
          foo.dummy
          

          【讨论】:

          • 访问修饰符对类方法没有影响,仅对单例类的方法有影响,即在class << self ... end范围内时。
          • 此外,私有/公共范围甚至不会成为问题,因为调用发生在同一个类上,如果正确定义,即使对于公共方法也是可能的..
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-01
          相关资源
          最近更新 更多