attr_accessor строго напечатал Ruby on Rails - PullRequest
16 голосов
/ 03 ноября 2011

Просто интересно, может ли кто-нибудь пролить свет на основы методов установки геттеров в Ruby on Rails с видом на строго типизированные.Я очень плохо знаком с ruby ​​на рельсах и в основном хорошо разбираюсь в .NET.

Например, давайте рассмотрим, что у нас есть класс .net с именем 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;}
}

В 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

Глядя на версию Ruby класса Person, как мне указать типы для методов доступа FirstName, LastName и HomeAddress?Если бы я использовал этот класс, я мог бы передать любой тип в HomeAddress, но я бы хотел, чтобы этот метод доступа принимал только адрес TYPE.

Есть предложения?

Ответы [ 2 ]

36 голосов
/ 03 ноября 2011

TL; DR : Нет, это невозможно ... и длинный ответ, да, это возможно, прочитайте раздел метапрограммирования:)

Ruby - это динамический язык, поэтому вы не будете получать предупреждения / ошибки типа времени компиляции, как в таких языках, как C #.

То же, что вы не можете указать тип для переменной, вы не можете указать тип для attr_accessor.

Возможно, это звучит глупо, если вы пришли из .NET, но в сообществе Ruby люди ожидают, что вы будете писать тесты. Если вы сделаете это, эти типы проблем в основном исчезнут. В Ruby on Rails вы должны тестировать свои модели. Если вы сделаете это, у вас не возникнет никаких проблем с случайным назначением чего-то неправильного.

Если вы говорите конкретно об ActiveRecord в Ruby on Rails, назначение 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

с определением что-то как

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 будет возвращаться только из самой лямбды, но когда вы делаете то же самое из 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 голосов
/ 03 ноября 2011

Ruby является динамически типизированным языком; как и многие другие языки с динамической типизацией, он придерживается утки, набирающей - из английской идиомы: «Если он ходит как утка и крякает как утка, то это утка».

Положительным моментом является то, что вам не нужно объявлять типов ни для одной из ваших переменных или членов класса. Ограничения на типы объектов, которые вы можете хранить в переменных или членах класса, зависят только от того, как вы их используете - если вы используете << для «записи вывода», то вы можете использовать файл или массив или строка для хранения вывода. Это может значительно повысить гибкость ваших занятий. (Сколько раз вы были расстроены тем, что API, который вы должны использовать, требовали стандартного указателя файла ввода-вывода FILE * C, а не позволяли передавать его в буфер?)

Недостатком (и, на мой взгляд, большим) является то, что у вас нет простого способа определить, какие типы данных вы можете безопасно хранить в любой заданной переменной или элементе. Возможно, один раз в високосный год для переменной или члена вызывается новый метод - ваша программа может аварийно завершить работу с NoMethodError, и ваше тестирование могло пропустить его полностью, поскольку оно основывалось на данных, которые вы, возможно, не понимали, были жизненно важны. (Это довольно надуманный пример. Но в угловых случаях есть большинство недостатков программирования, а динамическая типизация делает угловые случаи гораздо труднее обнаружить.)

Короче говоря: нет ограничений на то, что вы можете хранить в своих полях адреса. Если он поддерживает методы, которые вы вызываете для этих объектов, это - для языка - и Address. Если он не поддерживает необходимые вам методы, он завершится сбоем во время достаточно исчерпывающего тестирования.

Просто убедитесь, что использует средства тестирования в полной мере, чтобы убедиться, что вы используете свой код в достаточной степени, чтобы найти объекты, не полностью соответствующие требуемому API.

...