Attr_accessor сильно набрал Ruby on Rails

Просто интересно, может ли кто-нибудь пролить свет на основы методов получения геттеров в 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.

Какие-либо предложения?

Ответы

Ответ 1

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

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

Так же, как вы не можете указать тип переменной, вы не можете указать тип для 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

с определением 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 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 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.

Ответ 2

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

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

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

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

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