问题加载类命令编辑:工作,虽然一路上有一些奇怪的行为

时间:2013-05-26 17:59:08

标签: ruby metaprogramming

我正在开发一个项目来重新创建ActiveRecord的一些功能。这是不起作用的部分

module Associations
  def belongs_to(name, params)
    self.class.send(:define_method, :other_class) do |name, params|
      (params[:class_name] || name.camelize).constantize
    end

    self.class.send(:define_method, :other_table_name) do |other_class|
      other_class.table_name
    end
    .
    .
    .
    o_c = other_class(name, params)
    #puts this and other (working) values in a query
    query = <<-SQL
      ...
    SQL
    #sends it off with db.execute(query)...

我正在建立这个测试文件:

require 'all_files' #holds SQLClass & others

pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)

#class Person
#end

class Pet < SQLClass
  set_table_name("pets")
  set_attrs(:id, :name, :owner_id)

  belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end

class Person < SQLClass
  set_table_name("people")
  set_attrs(:id, :name)

  has_many :pets, :foreign_key => :owner_id
end
.
.
.

我没有收到任何更改

.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)

为了确保在文件中加载类的顺序是一个问题,我开始使用空Person类的文件,正如预测给我的那样

undefined method `table_name' for Person:Class (NoMethodError)

由于这是一个学习项目,我不想更改测试以使我的代码工作(打开所有类,设置所有表/属性然后为belongs_to重新打开它们。但是,我'我坚持如何继续进行。)

编辑SQLClass:

class SQLClass < AssignmentClass

    extend SearchMod

    extend Associations

    def self.set_table_name(table_name)
         @table_name = table_name
    end

    def self.table_name
        @table_name
    end
#some more methods for finding rows, and creating new rows in existing tables

AssignmentClass的相关部分使用send上的attr_accessorset_attrs提供功能,并确保在initialize新的类实例之前所有名称匹配使用set_attrs设置的内容。

2 个答案:

答案 0 :(得分:0)

这突出了动态的,解释性的Ruby(等)和静态编译语言(如Java / C#/ C ++)之间的重要区别。在Java中,编译器运行所有源文件,查找所有类/方法定义,并将它们与用法进行匹配。 Ruby不会像这样工作 - 在执行class块之后,一个类“出现”。在此之前,Ruby解释器对此一无所知。

在测试文件中,首先定义Pet。在Pet的定义中,您有belongs_to :personbelongs_to执行:person.constantize,尝试获取Person的类对象。但是Person还不存在!它的定义稍后会出现在测试文件中。

有几种方法我认为你可以尝试解决这个问题:

一个是做Rails的工作:在自己的文件中定义每个类,并使文件名符合某些约定。覆盖constant_missing,并使其自动加载定义缺失类的文件。这将使加载顺序问题自动解决。

另一个解决方案是让belongs_to懒惰。它不是立即查找Person类对象,而只是记录PetPerson之间存在关联的事实。当有人试图调用pet.person时,请使用missing_method挂钩来实际定义方法。 (据推测,到那时所有的类定义都将被执行。)

另一种方法是做一些事情:

define_method(belongs_to) do
  belongs_to_class = belongs_to.constantize
  self.class.send(:define_method, belongs_to) do
    # put actual definition here
  end
  self.send(belongs_to)
end

此代码未经过测试,只是为了给您一个想法!虽然这可能是一个非常令人折服的想法。基本上,您定义了一个在第一次调用时重新定义自身的方法。就像使用method_missing一样,这允许您延迟类查找,直到第一次实际使用该方法。

如果我再说一句话:虽然你说你不想“超载”method_missing,但我认为这并不像你想象的那么多。这只是将代码提取到辅助方法中以保持method_missing的可管理性。也许是这样的:

 def method_missing(name,*a,&b)
   if has_belongs_to_association?(name)
     invoke_belongs_to_association(name,a,b)
   elsif has_has_many_association?(name)
     invoke_has_many_association(name,a,b)
   # more...
   else
     super
   end
 end

答案 1 :(得分:0)

进展!受Alex D建议使用method_missing延迟创建的启发,我改为使用define_method为名称创建方法,如下所示:

define_method, :other_class) do |name, params|
          (params[:class_name] || name.camelize).constantize
        end

define_method(:other_table_name) do |other_class|
  other_class.table_name
end

#etc

define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
        #p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an @params class instance variable and a getter for it,
#but nothing that should make params require an argument
        f_k = foreign_key(name, params)
        p f_k
        o_c = other_class(name, params)
        o_t_n = other_table_name(o_c)
        p_k = primary_key(params)

        query = <<-SQL
            SELECT *
            FROM #{o_t_n}
            WHERE #{p_k} = ?
        SQL

        row = DBConnection.execute(query, self.send(f_k))
        o_c.parse_all(row)
    end