attr accessor mocno wpisał Ruby on Rails

Zastanawiam się, czy ktoś może rzucić trochę światła na podstawy getter setters w Ruby on Rails z widokiem na silnie wpisane. Jestem bardzo nowy w ruby on rails i głównie dobrze rozumiem .NET.

Na przykład, załóżmy, że mamy klasę. NET o nazwie 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;}
}

W Ruby napisałbym to jako

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

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

Patrząc na wersję Ruby klasy Person jak Mogę określić typy dla metod accessor FirstName, LastName i HomeAddress? Jeśli Miałem spożywać tę klasę mogę karmić dowolny typ do HomeAddress ale chcę, aby ta metoda accessor akceptowała Tylko Adres typu.

Jakieś sugestie ?

Dzięki

Author: meagar, 2011-11-03

2 answers

TL; DR: Nie, to niemożliwe ... i długa odpowiedź, tak jest to możliwe, przeczytaj sekcję metaprogramowanie :)

Ruby jest językiem dynamicznym, dlatego nie otrzymasz ostrzeżeń/błędów typu kompilacji, jak w językach takich jak C#.

Tak samo jak nie można określić typu dla zmiennej, nie można określić typu dla attr_accessor.

To może zabrzmieć głupio z. NET, ale w społeczności Ruby ludzie oczekują, że napiszesz testy. Jeśli to zrobisz, tego typu problemy w zasadzie znikną. W Ruby on Rails powinieneś przetestować swoje modele. Jeśli to zrobisz, nie będziesz miał żadnych problemów z przypadkowym przypisaniem czegoś w niewłaściwym miejscu.

Jeśli mówisz o ActiveRecord w Ruby on Rails, przypisanie ciągu znaków do atrybutu, który jest zdefiniowany jako liczba całkowita w bazie danych, spowoduje wyrzucenie wyjątku.

Przy okazji, zgodnie z konwencją, nie należy używać CamelCase dla atrybuty, więc poprawna definicja klasy powinna być

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

Jednym z powodów tego jest to, że jeśli pierwsza litera jest pisana wielką literą, Ruby zdefiniuje stałą zamiast zmiennej.

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

Metaprogramowanie hacków

Nawiasem mówiąc, Ruby ma również szalenie ogromne możliwości metaprogramowania. Możesz napisać własne attr_accessor za pomocą sprawdzania typu, które można użyć czegoś w rodzaju

typesafe_accessor :price, Integer

Z definicją coś Jak

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!

lub na przynajmniej coś w tym stylu :) ten kod działa! Ruby pozwala definiować metody w locie, tak działa attr_accessor.

Również bloki są prawie zawsze zamknięciami, co oznacza, że mogę wykonać if value.is_a? type bez podawania go jako parametru.

To zbyt skomplikowane, by wyjaśnić, kiedy to prawda, a kiedy nie. W skrócie, istnieją różne rodzaje bloków
  • Proc, który jest tworzony przez Proc.new
  • lambda, czyli utworzone przez słowo kluczowe lambda

Jedną z różnic jest to, że wywołanie return w lambda zwróci tylko z samego lambda, ale gdy zrobisz to samo z Proc, zwróci się cała Metoda wokół bloku, która jest używana podczas iteracji, np.]}

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    

Jeśli lubisz tego typu rzeczy, polecam sprawdzić PragProg Ruby Metaprogramming screencast .

 35
Author: Jakub Arnold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-08 11:11:19

Ruby jest językiem pisanym dynamicznie ; podobnie jak wiele języków pisanych dynamicznie, przylega dopisania kaczek - od angielskiego idiomu "jeśli chodzi jak kaczka i kwacze jak kaczka, to jest to kaczka."

Plusem jest to, że nie musisz deklarować typów na żadnej ze swoich zmiennych lub członków klasy. Ograniczenia dotyczące tego, jakie typy obiektów można przechowywać w zmiennych lub składnikach klasy pochodzą tylko z , jak ich używasz -- jeśli używasz << aby "zapisać wyjście", możesz użyć pliku lub tablicy lub łańcucha do przechowywania wyjścia. Może to znacznie zwiększyć elastyczność zajęć. (Ile razy denerwowało cię to, że API, którego używasz, wymaga standardowego wskaźnika pliku IO FILE * C zamiast pozwalać na przekazywanie w buforze?)

Minusem (i moim zdaniem jest to duży) jest to,że nie ma łatwego sposobu na określenie, jakie typy danych można bezpiecznie przechowywać w danej zmiennej lub elemencie. Być może raz na każdy skok rok, nowa metoda jest wywoływana na zmiennej lub członie -- Twój program może zawiesić się z NoMethodError, a twoje testy mogły go całkowicie pominąć, ponieważ opierały się na wejściach, których możesz nie zdawać sobie sprawy, że były istotne. (Jest to dość wymyślny przykład. Jednak przypadki narożne są tam, gdzie istnieje większość wad programowania, a dynamiczne pisanie sprawia, że przypadki narożne są o wiele trudniejsze do wykrycia.)

W skrócie: nie ma ograniczeń co do tego, co można przechowywać w polach adresu. Jeśli obsługuje metody, które wywołujesz tych obiektów, jest to-jeśli chodzi o język - an Address. Jeśli nie obsługuje metod, których potrzebujesz, ulegnie awarii podczas wystarczająco wyczerpujących testów.

Po prostu upewnij się, że używasz urządzeń testujących w pełni, aby upewnić się, że ćwiczysz kod wystarczająco, aby znaleźć obiekty, które nie są w pełni zgodne z wymaganym API.

 4
Author: sarnold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-02 23:16:37