attr_accessor 强类型 Rub​​y on Rails

发布于 2024-12-13 09:25:33 字数 800 浏览 0 评论 0原文

只是想知道是否有人可以从强类型的角度阐明 Ruby on Rails 中 getter setter 的基础知识。我对 Ruby on Rails 非常陌生,并且对 .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 地址。

有什么建议吗?

Just wondering if anyone can shed some light on the basics of getter setters in Ruby on Rails with a view on strongly typed. I am very new to ruby on rails and predominately have a good understanding of .NET.

For example, let's consider we have a .net class called Person

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;}
}

In Ruby, I would write this as

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

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

Looking at the Ruby version of the Person class how do I specify the types for the accessor methods FirstName, LastName and HomeAddress? If I were to consume this class I could feed any type into HomeAddress but I want this accessor method to accept only the TYPE Address.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

赏烟花じ飞满天 2024-12-20 09:25:33

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

元编程技巧

顺便说一句,Ruby 还具有极其强大的元编程功能。您可以编写自己的带有类型检查的 attr_accessor ,它可以

typesafe_accessor :price, Integer

与定义 something 类似的东西一起使用,例如

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

区别之一是,在 lambda 中调用 return 只会从 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 元编程截屏

TL;DR: No it's not possible ... and long answer, yes it is possible, read the metaprogramming section :)

Ruby is a dynamic language, that's why you won't get compile time type warnings/errors as you get in languages like C#.

Same as you can't specify a type for a variable, you can't specify a type for attr_accessor.

This might sound stupid to you coming from .NET, but in the Ruby community, people kind of expect you to write tests. If you do so, these types of problems will basically vanish. In Ruby on Rails, you should test your models. If you do so, you won't really have any trouble with accidentaly assigning something somewhere wrong.

If you're talking about ActiveRecord in Ruby on Rails specifically, assigning a String into an attribute which is defined as an Integer in the database will result in exception being thrown.

By the way, according to convention, you shouldn't use CamelCase for attributes, so the correct class definition should be

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

One reason for this is that if you Capitalize the first letter, Ruby will define a constant instead of a variable.

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

Metaprogramming hacks

By the way, Ruby also has insanely huge metaprogramming capabilities. You could write your own attr_accessor with a type check, that could be used something like

typesafe_accessor :price, Integer

with definition something like

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!

or at least something along these lines :) This code works! Ruby allows you to define methods on the fly, which is how attr_accessor works.

Also blocks are almost always closures, which means I can do the if value.is_a? type without passing it as a parameter.

It's too complicated to explain here when this is true and when it's not. In short, there are different types of blocks

  • Proc, which is created by Proc.new
  • lambda, which is created by the keyword lambda

one of the differences is that calling return in a lambda will only return from the lambda itself, but when you do the same thing from a Proc, the whole method around the block will return, which is used when iterating, e.g.

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    

If you're into this kind of stuff, I recommend you check out PragProg Ruby Metaprogramming screencast.

一影成城 2024-12-20 09:25:33

Ruby 是一种动态类型语言;与许多动态类型语言一样,它遵循 鸭子类型——来自英语习语,“如果它走路像鸭子,叫起来像鸭子,那就是鸭子。”

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

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

简而言之:您可以在地址字段中存储的内容没有限制。如果它支持您在这些对象上调用的方法,那么就语言而言,它就是一个地址。如果它不支持您需要的方法,那么它会在足够详尽的测试过程中崩溃。

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

Ruby is a dynamically typed language; like many dynamically typed languages, it adheres to duck typing -- from the English Idiom, "If it walks like a duck and quacks like a duck, then it's a duck."

The upside is that you don't have to declare types on any of your variables or class members. The restrictions on what types of objects you can store into the variables or class members comes only from how you use them -- if you use << to "write output", then you could use a file or array or string to store the output. This can greatly increase the flexibility of your classes. (How many times have you been upset that an API you must use required a FILE * C standard IO file pointer rather than allowing you to pass in a buffer?)

The downside (and, in my mind, it's a big one) is that there's no easy way for you to determine what data types you can safely store into any given variable or member. Perhaps once every leap year, a new method is called on a variable or member -- your program might crash with a NoMethodError, and your testing might have missed it completely because it relied on inputs you might not realize were vital. (This is a fairly contrived example. But corner cases are where most programming flaws exist and dynamic typing makes corner cases that much harder to discover.)

In short: there's no restriction on what you can store in your Address fields. If it supports the methods you call on those objects, it is -- as far as the language is concerned -- an Address. If it doesn't support the methods you need, then it will crash during sufficiently-exhaustive testing.

Just be sure to use the testing facilities to their fullest, to make sure you're exercising your code sufficiently to find any objects not fully compliant with a required API.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文