14. Преглед на тестове и решения. Разни

14. Преглед на тестове и решения. Разни

14. Преглед на тестове и решения. Разни

9 декември 2013

Днес

Тестовете

Обяснения на сгрешени въпроси.

Разни

Ще разгледаме няколко различни дреболии в различни части на Ruby, донякъде свързани и с грешките от тестовете.

Object#eql?

1 == 1.0     # true
1.eql?(1.0)  # false

$! и $@

retry в ensure

retry изпълнява begin блока отначало.

retries_left = 3

begin
  connect_to_facebook
rescue ConnectionError
  retries_left -= 1
  retry if retries_left > 0
end

next, break, redo, retry

Има много хубава семантика за тях.

излизане от...рестартиране на...
...блокаnextredo
...методаbreakretry

За нещастие, retry не работи извън rescue от Ruby 1.9 насам.

Клас променливи

class Person
  @@count = 0

  def initialize
    @@count += 1
  end

  def self.how_many
    @@count
  end
end

Person.new
Person.new
Person.how_many # 2

Клас променливи

семантиката

Клас променливи

class B
  @@foo = 1
  def self.foo() @@foo end
  def self.hmm() @@bar end
end

class D < B
  @@bar = 2
  def self.bar() @@bar end
  def self.change() @@foo = 3; @@bar = 4; end
end

[B.foo, D.foo, D.bar] # [1, 1, 2]
B.hmm                 # error: NameError
D.change
[B.foo, D.foo, D.bar] # [3, 3, 4]
B.hmm                 # error: NameError
D.hmm                 # error: NameError

Class#new и Object#initialize

Всъщност, #initialize е просто instance метод.

Class#new е имплементиран горе-долу така:

class Class
  def new
    object = self.allocate
    object.send :initialize
    object
  end
end

Object#dup, Object#clone, #initialize_copy

Оцапани обекти в Ruby

Документация в Ruby

RDoc

YARD

Други - Rails API документацията

Други - API Dock

RDoc

Подробно относно RDoc

Единадесето предизвикателство

Какво се искаше

Бяхме дали този код само илюстративно:

class Student
  attr_accessor :name, :points, :rank

  def initialize(name, points, rank)
    @name   = name
    @points = points
    @rank   = rank
  end
end

ivan   = Student.new 'Иван', 10, :second
mariya = Student.new 'Мария', 12, :first
neycho = Student.new 'Нейчо', 9, :third

students = [ivan, mariya, neycho]

Нашето решение 1

Array

students.map(&[:name, :rank]) # => [['Иван', :second], ['Мария', :first], ['Нейчо', :third]]
class Array
  def to_proc
    proc do |object|
      map { |method| object.send(method) }
    end
  end
end

Нашето решение 2

Hash

students.each &{points: 0, rank: :last}
students.map(&:rank)   # => [:last, :last, :last]
class Hash
  def to_proc
    proc do |object|
      each do |property, value|
        object.send "#{property}=", value
      end
    end
  end
end

Стил

class Array
  def to_proc
    Proc.new { |obj,*args| self.map {|method| obj.send(method,*args)} }
  end
end

class Hash
  def to_proc
    Proc.new { |obj| self.map {|method,value|  obj.send((method.to_s+"=").to_sym,value)} }
  end
end

Излишно метапрограмиране

Из Array#to_proc:

-> object { map(&object.method(:send)) }
# vs.
-> object { map { |method| object.send(method) } }

Или това:

Proc.new { |object| map { |item| item.to_sym.to_proc.call(object) } }
# vs.
proc object { map { |method| object.send(method) } }

Излишни неща

*send методите работят както със символи, така и с низове; тук to_sym е излишно:

Proc.new { |object| map { |key, value| object.public_send("#{key.to_sym}=".to_sym, value) } }

Най-простото работещо решение

Тук може да се ползва просто Enumerable#map:

class Array
  def to_proc
    -> (object) do
      each_with_object([]) do |property_name, result|
        result << object.send(property_name.to_sym)
      end
    end
  end
end

Или това:

setter = property_name.to_s.concat('=')
# vs.
setter = "#{property_name}="

map vs. each

Не използвайте map, когато искате да кажете each:

class Hash
  def to_proc
    -> object { map { |property, value| object.send("#{property}=", value) } }
  end
end

Излишен chaining

class Hash
  def to_proc
    Proc.new { |element| map { |key, value| element.send key.to_s.concat("=").to_sym, value } }
  end
end

Стил

class Hash
  def to_proc
    Proc.new do |obj|
      self.map() { |element| obj.send(element.first.to_s + '=', element.last) }
    end
  end
end

Стил

class Hash
  def to_proc
    proc do |obj1|
      self.each do |key, value|
        obj1.send((key.to_s + "=").to_sym, value)
      end
    end
  end
end

Спазвайте публичния интерфейс

class Hash
  def to_proc
    proc do |object|
      each do |field, value|
        object.instance_variable_set("@#{field}".to_sym, value)
      end
    end
  end
end

Стил

class Hash
  def to_proc
    Proc.new do |obj, *args|
      map { |method_name, arg| obj.send (method_name.to_s + '=').to_sym, arg }
    end
  end
end

Стил и конвенции

Изводи

Въпроси