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.
Какие-либо предложения?
Спасибо
2 ответа
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.
Ruby - это динамически типизированный язык; подобно многим динамически типизированным языкам, он придерживается типизации утки - из английского языка: "Если он ходит, как утка, и крякает, как утка, то это утка".
Плюс в том, что вам не нужно объявлять типы ни для одной из ваших переменных или членов класса. Ограничения на типы объектов, которые вы можете хранить в переменных или членах класса, зависят только от того, как вы их используете - если вы используете <<
чтобы "записать вывод", вы можете использовать файл, массив или строку для хранения вывода. Это может значительно повысить гибкость ваших занятий. (Сколько раз вы были расстроены тем, что API, который вы должны использовать, требовали FILE *
C стандартный указатель файла ввода-вывода, а не позволяет передавать в буфер?)
Недостатком (и, на мой взгляд, он большой) является то, что у вас нет простого способа определить, какие типы данных вы можете безопасно хранить в любой данной переменной или члене. Возможно, один раз в каждый високосный год для переменной или члена вызывается новый метод - ваша программа может завершиться с ошибкой NoMethodError
и ваше тестирование, возможно, пропустило его полностью, потому что оно опиралось на данные, которые вы, возможно, не осознавали, были жизненно важными. (Это довольно надуманный пример. Но в угловых случаях есть большинство недостатков программирования, а динамическая типизация делает угловые случаи гораздо труднее обнаружить.)
Короче говоря: нет никаких ограничений на то, что вы можете хранить в своих полях адреса. Если он поддерживает методы, которые вы вызываете для этих объектов, то, насколько это касается языка, он являетсяAddress
, Если он не поддерживает необходимые вам методы, он завершится сбоем во время достаточно исчерпывающего тестирования.
Просто убедитесь, что вы используете возможности тестирования в полной мере, чтобы убедиться, что вы используете свой код в достаточной степени, чтобы найти объекты, не полностью совместимые с требуемым API.