attr_accessor强类型Ruby on Rails

问题描述 投票:16回答:2

只是想知道是否有人能够在Ruby on Rails中对getter setter的基础知识进行一些了解,并提供强类型视图。我对rails上的ruby非常新,并且主要对.NET非常了解。

例如,我们假设我们有一个名为Person的.net类

class Person
{
 public string Firstname{get;set;}
 public string Lastname{get;set;}
 public Address HomeAddress{get;set;}
}

class Address
{
 public string AddressLine1{get;set;}
 public string City{get;set;}
 public string Country{get;set;}
}

在Ruby中,我会写这个

class Person
 attr_accessor :FirstName
 attr_accessor :LastName
 attr_accessor :HomeAddress
end

class Address
 attr_accessor :AddressLine1
 attr_accessor :City
 attr_accessor :Country
end

查看Person类的Ruby版本如何指定访问器方法FirstName,LastName和HomeAddress的类型?如果我要使用这个类,我可以将任何类型提供给HomeAddress,但我希望这个访问器方法只接受TYPE地址。

有什么建议?

ruby-on-rails ruby accessor attr-accessor
2个回答
36
投票

TL; DR:不,这是不可能的...而且答案很长,是的,有可能,阅读元编程部分:)

Ruby是一种动态语言,这就是为什么你不会像C#这样的语言得到编译时类型警告/错误。

与您无法为变量指定类型相同,您无法为attr_accessor指定类型。

对于来自.NET的人来说,这可能听起来很愚蠢,但在Ruby社区,人们希望你能编写测试。如果你这样做,这些类型的问题基本上会消失。在Ruby on Rails中,您应该测试您的模型。如果你这样做,你就不会有任何麻烦,因为意外地分配错误的东西。

如果您在Ruby on Rails中专门讨论ActiveRecord,则将String分配给在数据库中定义为Integer的属性将导致抛出异常。

顺便说一下,按照惯例,你不应该使用CamelCase作为属性,所以正确的类定义应该是

class Person
 attr_accessor :first_name
 attr_accessor :last_name
 attr_accessor :home_address
end

class Address
 attr_accessor :address_line1
 attr_accessor :city
 attr_accessor :country
end

其中一个原因是,如果你将第一个字母大写,Ruby将定义一个常量而不是变量。

number = 1   # regular variable
Pi = 3.14159 # constant ... changing will result in a warning, not an error

元编程黑客攻击

顺便说一下,R​​uby还具有疯狂的元编程功能。您可以使用类型检查编写自己的attr_accessor,可以使用类似的东西

typesafe_accessor :price, Integer

有定义 某物 喜欢

class Foo

  # 'static', or better said 'class' method ...
  def self.typesafe_accessor(name, type)

    # here we dynamically define accessor methods
    define_method(name) do
      # unfortunately you have to add the @ here, so string interpolation comes to help
      instance_variable_get("@#{name}")
    end

    define_method("#{name}=") do |value|
      # simply check a type and raise an exception if it's not what we want
      # since this type of Ruby block is a closure, we don't have to store the 
      # 'type' variable, it will 'remember' it's value 
      if value.is_a? type
        instance_variable_set("@#{name}", value)
      else
        raise ArgumentError.new("Invalid Type")
      end
    end
  end

  # Yes we're actually calling a method here, because class definitions
  # aren't different from a 'running' code. The only difference is that
  # the code inside a class definition is executed in the context of the class object,
  # which means if we were to call 'self' here, it would return Foo
  typesafe_accessor :foo, Integer

end

f = Foo.new
f.foo = 1
f.foo = "bar" # KaboOoOoOoM an exception thrown here!

或至少在这些方面的东西:) 这段代码有效! Ruby允许您动态定义方法,这就是attr_accessor的工作原理。

块也几乎总是闭包,这意味着我可以做if value.is_a? type而不将它作为参数传递。

如果这是真的,那么在这里解释太复杂了。简而言之,有不同类型的块

  • Proc,由Proc.new创建
  • lambda,由关键字lambda创建

其中一个区别是在return中调用lambda只会从lambda本身返回,但是当你从Proc做同样的事情时,块周围的整个方法将返回,这在迭代时使用,例如

def find(array, something)
  array.each do |item| 
    # return will return from the whole 'find()' function
    # we're also comparing 'item' to 'something', because the block passed
    # to the each method is also a closure
    return item if item == something
  end
  return nil # not necessary, but makes it more readable for explanation purposes
end    

如果你喜欢这种东西,我建议你看看PragProg Ruby Metaprogramming screencast


4
投票

Ruby是一种dynamically typed语言;像许多动态类型的语言一样,它坚持duck typing - 来自英语成语,“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就像一只鸭子。”

好处是您不必在任何变量或类成员上声明类型。您可以存储到变量或类成员中的对象类型的限制仅来自您使用它们的方式 - 如果您使用<<来“编写输出”,那么您可以使用文件或数组或字符串来存储输出。这可以大大提高课程的灵活性。 (你有多少次因为你必须使用的API需要一个FILE * C标准IO文件指针而不是允许你传入一个缓冲区?)

缺点(在我看来,它是一个很大的缺点)是你没有简单的方法来确定你可以安全地存储到任何给定变量或成员的数据类型。也许每一个闰年,一个新的方法被调用变量或成员 - 你的程序可能会崩溃与NoMethodError,你的测试可能完全错过它,因为它依赖于你可能没有意识到的输入是至关重要的。 (这是一个相当人为的例子。但角落情况是大多数编程缺陷存在的地方,动态类型使角落案例更难发现。)

简而言之:您可以在地址字段中存储的内容没有限制。如果它支持你调用这些对象的方法,那么就语言而言,它就是Address。如果它不支持您需要的方法,那么它将在充分详尽的测试期间崩溃。

请务必充分利用测试工具,确保您充分运用代码,找到不完全符合所需API的任何对象。

© www.soinside.com 2019 - 2024. All rights reserved.