attr_accessor strongly typed Ruby on Rails

前端 未结 2 826
你的背包
你的背包 2020-12-14 02:52

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

相关标签:
2条回答
  • 2020-12-14 03:24

    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.

    0 讨论(0)
  • 2020-12-14 03:39

    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.

    0 讨论(0)
提交回复
热议问题