LoginSignup
2
2

More than 5 years have passed since last update.

BathRuby 2015 - Nothing is Something by Sandi Metz 前半のメモ

Posted at

動画
https://www.youtube.com/watch?v=9lv2lBq6x4A&feature=youtu.be

missingno15さんがこちらに書いたコードを自分のメモとして記述。
https://gist.github.com/missingno15/4bc7efabe7a45ff895fd


require 'pry'

class Animal
  DATABASE = [
    { id: 1, name: "Mockingbird" },
    { id: 2, name: "Pheasant" },
    { id: 3, name: "Duck" },

    { id: 4, name: "Platypus" },
    { id: 5, name: "Penguin" },
    { id: 6, name: "Peacock" },
    { id: 7, name: "Hummingbird" },
    { id: 8, name: "Owl" }
  ]

  attr_accessor :id, :name

  def self.find(id)
    target_data = DATABASE.find { |data| data[:id] == id }

    if target_data
      self.new(target_data)
    end
  end

  def initialize(attrs)
    @id = attrs.fetch(:id)
    @name = attrs.fetch(:name)
  end
end


# Now we have a list of ids that we want to query

ids = [4, 8]

# Let's map this

birds = ids.map { |id| Animal.find(id) }
p birds  # => [{:id=>4, :name=>"Platypus"}, {:id=>8, :name=>"Owl"}]

# Let's now add an id that doesn't exist in our database to our array of ids
# Now when we run map on the ids, we should get nil in the resulting array


ids << 48
birds = ids.map { |id| Animal.find(id) }
p birds  # => [{:id=>4, :name=>"Platypus"}, {:id=>8, :name=>"Owl"}, nil]

# However, let's say we don't want it to return 'nil', we want it to be 
# descriptive and return 'no animal' instead when we call #name on each object
# With our current implementation, if you iterate through each element 
# in the array, you'll get:

# birds.each { |bird| bird.name }
# => undefined method `name' for nil:NilClass (NoMethodError)

# Let's say we can't change Animal#find because it belonged to an 
# external framework so we have no control whatsoever on it

# You could do something like this:

birds.each { |bird| bird ? (puts bird.name) : (puts "no animal") }

# However, we can do better
# Let's use the Null Object Pattern to substitute for our missing values

class MissingAnimal
  def name
    "no animal"
  end
end

birds = ids.map { |id| Animal.find(id) || MissingAnimal.new }

# Now when when we call #name on each object

birds.each { |bird| puts bird.name }


# #=> Platypus
#     Owl
#     No animal


# However, Sandi Metz makes the point that everytime you want to do this, we will be calling on 
# twice the amount of objects. Her solution is wrapping up the Null Object into another class 
# which also handles the API of the thing that we have no control over.

class GuaranteedAnimal
  def self.find(id)
    Animal.find(id) || MissingAnimal.new
  end
end


# Now in the rest of the areas of your app, you can now change this:

birds = ids.map { |id| Animal.find(id) || MissingAnimal.new }

# to this↓

birds = ids.map { |id| GuaranteedAnimal.find(id) }

birds.each { |bird| puts bird.name  }
# => Platypus
#    Owl
#    No animal
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2