Ruby hash - how to use hash value when populating object? - ruby

For a pack of playing cards:
How can I use the suit hash (below) when creating a pack?
I have:
class PackOfCards
SUITS={H: 'Hearts', S:'Spades', D:'Diamonds', C:'Clubs'}
CARDS=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
attr_accessor :pack_name, :cards
def initialize(pack_name)
#pack_name= pack_name
#cards = []
(0..3).each do |suit|
(0..12).each do |number|
#cards << PlayingCard.new(self, (SUITS[suit].value), CARDS[number])
end
end
end
end
class PlayingCard
attr_accessor :pack, :card_number, :card_suit
def initialize(pack, suit, number)
#card_suit = suit
#card_number = number
end
end
but I get:
pack_of_cards.rb:16:in `block (2 levels) in initialize':
undefined method `value' for
{:H=>"Hearts", :S=>"Spades", :D=>"Diamonds", :C=>"Clubs"}:Hash (NoMethodError)

Your SUITS is invalid expression. Perhaps you wanted to do this:
SUITS = %w[Hearts Spades Diamonds Clubs]
And it is not clear what you are doing, but perhaps you should be doing this:
#cards =
SUITS.flat_map{|suit| CARDS.map{|number| PlayingCard.new(self, suit, number)}}

Here is a corrected version, check the comments :
class PackOfCards
SUITS={H: 'Hearts', S:'Spades', D:'Diamonds', C:'Clubs'} # Use curly braces to define a hash, [] braces will define an array containing one hash
CARDS=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
attr_accessor :pack_name, :cards
def initialize(pack_name)
#pack_name= pack_name
#cards = []
SUITS.each_key do |suit| # each_key is better since it gives you the key of the hash
(0..12).each do |number|
puts PackOfCards::SUITS[suit]
#cards << PlayingCard.new(self, (PackOfCards::SUITS[suit]), PackOfCards::CARDS[number]) # Call the hash with the right key to get the Suit
end
end
end
end
class PlayingCard
attr_accessor :pack, :card_number, :card_suit
def initialize(pack, suit, number)
#card_suit = suit
#card_number = number
end
end

Your Suit definition and lookup don't look valid.
How about something like this (assuming the output is a pack of cards with all suit and numbers) -
class PackOfCards
SUITS = ['Hearts', 'Spades', 'Diamonds', 'Clubs']
CARDS=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
attr_accessor :pack_name, :cards
def initialize(pack_name)
#pack_name= pack_name
#cards = []
(0..3).each do |suit|
(0..12).each do |number|
#cards << PlayingCard.new(self, (SUITS[suit]), CARDS[number])
end
end
end
end
class PlayingCard
attr_accessor :pack, :card_number, :card_suit
def initialize(pack, suit, number)
#card_suit = suit
#card_number = number
end
end

You've actually put a hash in an array. To access the key, value pairs you'd have to access the array element first like this:
SUITS.first[:H]

Related

Creating Ruby builder object with re-usable code

I'm working to create a few Ruby builder objects, and thinking on how I could reuse some of Ruby's magic to reduce the logic of the builder to a single class/module. It's been ~10 years since my last dance with the language, so a bit rusty.
For example, I have this builder:
class Person
PROPERTIES = [:name, :age]
attr_accessor(*PROPERTIES)
def initialize(**kwargs)
kwargs.each do |k, v|
self.send("#{k}=", v) if self.respond_to?(k)
end
end
def build
output = {}
PROPERTIES.each do |prop|
if self.respond_to?(prop) and !self.send(prop).nil?
value = self.send(prop)
# if value itself is a builder, evalute it
output[prop] = value.respond_to?(:build) ? value.build : value
end
end
output
end
def method_missing(m, *args, &block)
if m.to_s.start_with?("set_")
mm = m.to_s.gsub("set_", "")
if PROPERTIES.include?(mm.to_sym)
self.send("#{mm}=", *args)
return self
end
end
end
end
Which can be used like so:
Person.new(name: "Joe").set_age(30).build
# => {name: "Joe", age: 30}
I would like to be able to refactor everything to a class and/or module so that I could create multiple such builders that'll only need to define attributes and inherit or include the rest (and possibly extend each other).
class BuilderBase
# define all/most relevant methods here for initialization,
# builder attributes and object construction
end
module BuilderHelper
# possibly throw some of the methods here for better scope access
end
class Person < BuilderBase
include BuilderHelper
PROPERTIES = [:name, :age, :email, :address]
attr_accessor(*PROPERTIES)
end
# Person.new(name: "Joe").set_age(30).set_email("joe#mail.com").set_address("NYC").build
class Server < BuilderBase
include BuilderHelper
PROPERTIES = [:cpu, :memory, :disk_space]
attr_accessor(*PROPERTIES)
end
# Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").build
I've been able to get this far:
class BuilderBase
def initialize(**kwargs)
kwargs.each do |k, v|
self.send("#{k}=", v) if self.respond_to?(k)
end
end
end
class Person < BuilderBase
PROPERTIES = [:name, :age]
attr_accessor(*PROPERTIES)
def build
...
end
def method_missing(m, *args, &block)
...
end
end
Trying to extract method_missing and build into the base class or a module keeps throwing an error at me saying something like:
NameError: uninitialized constant BuilderHelper::PROPERTIES
OR
NameError: uninitialized constant BuilderBase::PROPERTIES
Essentially the neither the parent class nor the mixin are able to access the child class' attributes. For the parent this makes sense, but not sure why the mixin can't read the values inside the class it was included into. This being Ruby I'm sure there's some magical way to do this that I have missed.
Help appreciated - thanks!
I reduced your sample to the required parts and came up with:
module Mixin
def say_mixin
puts "Mixin: Value defined in #{self.class::VALUE}"
end
end
class Parent
def say_parent
puts "Parent: Value defined in #{self.class::VALUE}"
end
end
class Child < Parent
include Mixin
VALUE = "CHILD"
end
child = Child.new
child.say_mixin
child.say_parent
This is how you could access a CONSTANT that lives in the child/including class from the parent/included class.
But I don't see why you want to have this whole Builder thing in the first place. Would an OpenStruct not work for your case?
Interesting question. As mentioned by #Pascal, an OpenStruct might already do what you're looking for.
Still, it might be more concise to explicitly define the setter methods. It might also be clearer to replace the PROPERTIES constants by methods calls. And since I'd expect a build method to return a complete object and not just a Hash, I renamed it to to_h:
class BuilderBase
def self.properties(*ps)
ps.each do |property|
attr_reader property
define_method :"set_#{property}" do |value|
instance_variable_set(:"##{property}", value)
#hash[property] = value
self
end
end
end
def initialize(**kwargs)
#hash = {}
kwargs.each do |k, v|
self.send("set_#{k}", v) if self.respond_to?(k)
end
end
def to_h
#hash
end
end
class Person < BuilderBase
properties :name, :age, :email, :address
end
p Person.new(name: "Joe").set_age(30).set_email("joe#mail.com").set_address("NYC").to_h
# {:name=>"Joe", :age=>30, :email=>"joe#mail.com", :address=>"NYC"}
class Server < BuilderBase
properties :cpu, :memory, :disk_space
end
p Server.new.set_cpu("i9").set_memory("32GB").set_disk_space("1TB").to_h
# {:cpu=>"i9", :memory=>"32GB", :disk_space=>"1TB"}
I think no need to declare PROPERTIES, we can create a general builder like this:
class Builder
attr_reader :build
def initialize(clazz)
#build = clazz.new
end
def self.build(clazz, &block)
builder = Builder.new(clazz)
builder.instance_eval(&block)
builder.build
end
def set(attr, val)
#build.send("#{attr}=", val)
self
end
def method_missing(m, *args, &block)
if #build.respond_to?("#{m}=")
set(m, *args)
else
#build.send("#{m}", *args, &block)
end
self
end
def respond_to_missing?(method_name, include_private = false)
#build.respond_to?(method_name) || super
end
end
Using
class Test
attr_accessor :x, :y, :z
attr_reader :w, :u, :v
def set_w(val)
#w = val&.even? ? val : 0
end
def add_u(val)
#u = val if val&.odd?
end
end
test1 = Builder.build(Test) {
x 1
y 2
z 3
} # <Test:0x000055b6b0fb2888 #x=1, #y=2, #z=3>
test2 = Builder.new(Test).set(:x, 1988).set_w(6).add_u(2).build
# <Test:0x000055b6b0fb23b0 #x=1988, #w=6>

Ruby method chaining with an Enumerable class

I'm attempting to adapt the method-chaining example cited in this posting (Method chaining and lazy evaluation in Ruby) to work with an object that implements the Enumerable class (Implement a custom Enumerable collection class in Ruby )
Coffee class:
class Coffee
attr_accessor :name
attr_accessor :strength
def initialize(name, strength)
#name = name
#strength = strength
end
def <=>(other_coffee)
self.strength <=> other_coffee.strength
end
def to_s
"<name: #{name}, strength: #{strength}>"
end
end
Criteria class:
class Criteria
def initialize(klass)
#klass = klass
end
def criteria
#criteria ||= {:conditions => {}}
end
# only show coffee w/ this strength
def strength(strength)
criteria[:strength] = strength
self
end
# if there are multiple coffees, choose the first n=limit
def limit(limit)
criteria[:limit] = limit
self
end
# allow collection enumeration
def each(&block)
#klass.collection.select { |c| c[:strength] == criteria[:strength] }.each(&block)
end
end
CoffeeShop class:
class CoffeeShop
include Enumerable
def self.collection
#collection=[]
#collection << Coffee.new("Laos", 10)
#collection << Coffee.new("Angkor", 7)
#collection << Coffee.new("Nescafe", 1)
end
def self.limit(*args)
Criteria.new(self).limit(*args)
end
def self.strength(*args)
Criteria.new(self).strength(*args)
end
end
When I run this code:
CoffeeShop.strength(10).each { |c| puts c.inspect }
I get an error:
criteria.rb:32:in block in each': undefined method '[]' for #<Coffee:0x007fd25c8ec520 #name="Laos", #strength=10>
I'm certain that I haven't defined the Criteria.each method correctly, but I'm not sure how to correct it. How do I correct this?
Moreover, the each method doesn't support the limit as currently written. Is there a better way to filter the array such that it is easier to support both the strength and limit?
Other coding suggestions are appreciated.
Your Coffee class defines method accessors for name and strength. For a single coffee object, you can thus get the attributes with
coffee.name
# => "Laos"
coffee.strength
# => 10
In your Criteria#each method, you try to access the attributes using the subscript operator, i.e. c[:strength] (with c being an Instance of Coffee in this case). Now, on your Coffee class, you have not implemented the subscript accessor which resulting in the NoMethodError you see there.
You could thus either adapt your Criteria#each method as follows:
def each(&block)
#klass.collection.select { |c| c.strength == criteria[:strength] }.each(&block)
end
or you could implement the subscript operators on your Coffee class:
class Coffee
attr_accessor :name
attr_accessor :strength
# ...
def [](key)
public_send(key)
end
def []=(key, value)
public_send(:"#{key}=", value)
end
end
Noe, as an addendum, you might want to extend your each method in any case. A common (and often implicitly expected) pattern is that methods like each return an Enumerator if no block was given. This allows patterns like CoffeeShop.strength(10).each.group_by(&:strength).
You can implement this b a simple on-liner in your method:
def each(&block)
return enum_for(__method__) unless block_given?
#klass.collection.select { |c| c.strength == criteria[:strength] }.each(&block)
end

Pass a block to map function

I was wondering if something like this was possible?
info = arrange_info({|x| [x.name, x.number]}, info_array)
def arrange_info(block, info)
info.map(block).to_h
end
This would allow me to pass different blocks to arrange the array is different ways, how I have it now doesn't work, but is something like this possible?
A block can be passed as a method argument, but it needs to be the last one. You also cannot call a method before it has been defined :
def arrange_info(info, &block)
info.map(&block).to_h
end
info = arrange_info(info_array){|x| [x.name, x.number]}
Here's a small test :
class X
def initialize(name, number)
#name = name
#number = number
end
attr_reader :name, :number
end
def arrange_info(info, &block)
info.map(&block).to_h
end
info_array = [X.new('a', 1), X.new('b', 2)]
p info = arrange_info(info_array) { |x| [x.name, x.number] }
#=> {"a"=>1, "b"=>2}
Adding to Eric's answers.
These are equivalent
def arrange_info(info, &block)
info.map(&block).to_h
end
def arrange_info(info) # takes a block
info.map { |each| yield each }.to_h
end
The latter avoids materializing the block as an object.

A nil class when trying to initialize a deck of cards

The following code is resulting in nil and I can't figure out why. Is there something wrong with my initialization?
class Card
VALUES = %w(2 3 4 5 6 7 8 9 10 J Q K A)
SUITS = %w(S H D C)
def initialize(suit, value)
#suit = suit
#value = value
end
end
class Deck
attr_accessor :cards
def initialize
#cards = []
Card::SUITS.each do |suit|
Card::VALUES.each do |value|
#cards << Card.new(suit, value)
end
end
end
end
Deck.new
p #cards
#cards is not known outside the object. Outside the class Deck it is a instance variable of the top-level scope in Ruby.
You have to use the accessor method to get the content:
class Card
VALUES = %w(2 3 4 5 6 7 8 9 10 J Q K A)
SUITS = %w(S H D C)
def initialize(suit, value)
#suit = suit
#value = value
end
end
class Deck
attr_accessor :cards
def initialize
#cards = []
Card::SUITS.each do |suit|
Card::VALUES.each do |value|
#cards << Card.new(suit, value)
end
end
end
end
deck = Deck.new #<--- Store object in a variable
p deck.cards #<--- Use accessor
or just:
my_deck = Deck.new
p my_deck.cards
In short, instance variables can only be seen by other methods from within the same class. I believe what you're trying to do is:
new_deck = Deck.new
p new_deck.cards
Calling the cards method on new_deck returns #cards.
You create a new object Deck.new, but you don't print the value of its cards - you print a #cards instance variable which in this context is nil.
You probably wanted something like p Deck.new.cards.
Currently your output for #cards array is difficult to read and contains object info. I thought I'd offer an alternative I've just conjoured up, hope it helps:
class Deck
attr_writer :suits, :values
attr_accessor :deck
def initialize
suits
values
generate_deck
shuffle
end
def generate_deck
#deck = []
#suits.each do |suit|
#values.each { |value| #deck << [suit, value] }
end
end
def suits
#suits = ['Clubs', 'Diamonds', 'Hearts', 'Spades' ]
end
def values
#values = ('2'..'10').to_a + ['J','Q','K','A']
end
#just in case you want to shuffle your deck
def shuffle
#deck.shuffle!
end
end
require 'pp' #this makes the output prettier
new_deck = Deck.new
pp new_deck.deck #calls your current deck so you can see it
Output example:
$ ruby yourfilename.rb
[["Hearts", "K"],
["Spades", "5"],
["Clubs", "7"],
#code omitted... (the rest of your cards would be here)
["Clubs", "K"],
["Hearts", "5"],
["Diamonds", "J"],
["Hearts", "7"]]

Calling a method from a method in another class

It's a fairly well know one. Determining the rank of a poker hand. I created the following classes: Card:
class Card
attr_accessor :suite, :rank, :value
def initialize(suite, rank, value)
#suite = suite
#rank = rank
#value = value
end
def to_s
puts "#{#value}, #{#suite}, #{#rank}"
end
end
Deck:
class Deck
def initialize()
#cardsInDeck = 52
#deck = Array.new()
end
def add_card(card)
#deck.push(card)
end
def deck_size
#deck.length
end
def to_s
#deck.each do |card|
"#{card.rank}, #{card.suite}"
end
end
def shuffle_cards
#deck.shuffle!
end
def deal_cards
#Here I create a new hand object, and when popping cards from the deck
# stack I insert the card into the hand. However, when I want to print
# the cards added to the hand I get the following error:
# : undefined method `each' for #<Hand:0x007fa51c02fd50> (NoMethodError)from
# driver.rb:36:in `<main>'
#hand = Hand.new
for i in 0..51 do
card = #deck.pop
#cardsInDeck -= 1
puts "#{card.value}, #{card.rank}, #{card.suite}"
#hand.add_cards(card)
end
#hand.each do |index|
"#{index.value}, #{index.rank}, #{index.suite}"
end
puts "Cards In Deck: #{#cardsInDeck}"
end
end
Hand
require_relative 'deck'
require_relative 'card'
class Hand
def initialize()
#hand = Array.new()
end
def to_s
count = 0
#hand.each do |card|
"#{card.value}, #{card.rank}, #{card.suite}"
count += 1
end
end
def add_cards(card)
#hand.push(card)
end
def hand_size()
#hand.length
end
end
and Driver File:
require 'logger'
require_relative 'card'
require_relative 'deck'
require_relative 'hand'
suite = ["Hearts", "Diamonds", "Clubs", "Spades"]
rank = ["Ace", 2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King"]
deck = Deck.new()
suite.each do |i|
v = 1
rank.each do |j|
deck.add_card(Card.new(i, j, v))
v += 1
end
end
In the Deck class, the deal_card method, I do not understand why looping for an array causes a method error
#hand.each do |index|
"#{index.value}, #{index.rank}, #{index.suite}"
end
puts "Cards In Deck: #{#cardsInDeck}"
#hand is an instance of Hand, and there is no instance method each defined for Hand, so that is why #hand.each is generating an undefined method error.
My answer is not very direct on the error but could hopefully help you in this case.
You method deal_cards is where Dependency Injection can play its role. By the original design Deck has a hard dependency on Hand which is not good and harder to test. You need to change it like
def deal_cards(hand=nil)
#hand = hand || Hand.new
# Others
end
By this you can accept instance outside of Hand, say Foot as long as somebody can play cards by feet!
You can also unit testing this method without writing Hand class at all.
Better to unit test the class instead of manual checking, then you can inject any instance you like into this method during testing.

Resources