any way capture the name of a constant on declaration? - ruby

I have a class. Let's call it SomeClass:
class SomeClass
end
Instead of defining instances of this class the normal way, I would like to define them all using a constant:
MyConstant = SomeClass.new
I want to be able to capture the name of the constant that some class was set too, in much the same way that standard ruby classes do with the .class method.
MyConstant.name #-> "MyConstant"
I want to be able to do this to render better error messages from all instances of some class, like so:
class SomeClass
def display_error_message
"Error, some class #{self.name} has a problem"
end
end
MyConstant.display_error_message
#-> "Error, some class MyConstant has a problem"
Any way to accomplish this?
EDIT
Here's an example to clarify what I'm shooting for.
(Enum is the name of the class I'm creating, which is meant to act similar to Swifts 'Enum' type. Basically it sets a predefined list of options (:pepperoni, :sausage, :mushroom) with a raw_value ("Pepperoni", "Sausage", "Mushroom".) Obviously in this example a hash or a simple algorithm for converting a symbol to an UpperCamel case string could work, but in reality the enum class will do a lot more, but this example shows the gist of it.
class Pizza
attr_reader :topping
Toppings = Enum.new do
option(:pepperoni).set("Pepperoni")
option(:sausage).set("Sausage")
option(:mushrooms).set("Mushrooms")
end
def set_topping(symbol)
#topping = Toppings[symbol]
end
end
pizza = Pizza.new
### Happy Case
pizza.set_topping(:pepperoni)
### Sad Case (Error message shown below is what I'm trying to figure out)
pizza.set_topping(:spinach)
#-> Error. enum Toppings has no option spinach

A variable is just a way of referring to an object, and the variable's name is irrelevant. If you say X = Y and Y happens to be a class, then the Y class already has the name "Y", so you can't change that.
As far as Ruby is concerned X and Y are indistinguishable.
If you want to alter the name you can make a subclass even if that subclass doesn't do anything different:
X = Class.new(Y)
X.name
# => "X"
Z = X
Z.name
# => "X"
That way preserves the name properly but only in the context of the initialization. I think Ruby does something sneaky and if a new class is being assigned to a constant it assigns a name, but for ordinary variables it does not:
x = Class.new(Y)
x.name
# => nil
So this is a special case.
The key here is that there's a huge difference between a subclass, which does impact the name, and a variable reference, which doesn't.
There's some other strange stuff going on here as it seems like the class somehow "knows" when it's being assigned to something and if that something is a constant it steals the constant's name for itself:
z = Class.new
z.name
# => nil
Z = z
z.name
# => "Z"
As they say in programming: "Wat?"

Your Enum class could look something like this:
class Enum
def initialize(name, &blk)
#defined_options = {}
#name = name.freeze
instance_eval(&blk)
#defined_options.freeze
end
def [](key)
if #defined_options.key? key
#defined_options[key].value
else
unfound_option = Option.new(#name, key)
raise "Option #{unfound_option} not found."
end
end
def to_s
"#{#name}"
end
def inspect
keys = #defined_options.keys.join(',')
"#<#{self}::{#{keys}}>"
end
class Option
attr_reader :value
def initialize(enum_name, key)
#value_initialized = false
#enum_name = enum_name
#key = key
end
def set(value)
if #value_initialized
raise "Value for #{self} can't be set to #{value} " +
"because it is already initialized to #{#value}"
else
#value_initialized = true
#value = value.freeze
end
end
def to_s
"#{#enum_name}::#{#key}"
end
def inspect
"#<#{self}>"
end
end
private
def option(sym)
unless #defined_options.key? sym
option = Option.new(#name, sym)
#defined_options[sym] = option
end
#defined_options[sym]
end
end
Now you can almost keep the syntax you have in your question, and do the following:
class Pizza
attr_reader :topping
# I had to add the name in the initializer for better error reporting.
Toppings = Enum.new('Toppings') do
option(:pepperoni).set("Pepperoni")
option(:sausage).set("Sausage")
option(:mushrooms).set("Mushrooms")
end
def set_topping(symbol)
#topping = Toppings[symbol]
end
end
pizza = Pizza.new
### Happy Case
pizza.set_topping(:pepperoni)
#=> "Pepperoni"
### Sad Case (Error message shown below is what I'm trying to figure out)
pizza.set_topping(:spinach)
#=> RuntimeError: Option Toppings::spinach not found.
This might give you an idea how to solve this issue. This is just a rough sketch of the class and could probably be tweaked to your needs.

Related

Create hash of instance methods references

I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here

Ruby class initialize override module initialize

I am using super to pass arguments to the parent initialize method, which is not called by default. This is what it looks like. (Notice the use of super on the last two arguments)
module Pet
def initialize name, is_pet
#is_pet = is_pet
if is_pet
#name = name
else
#name = "Unnamed"
end
end
def pet?
return #is_pet
end
def get_name
return #name
end
end
class Dog
include Pet
def initialize tricks, name, is_pet
#tricks = tricks
super name, is_pet
end
def get_tricks
return #tricks
end
end
Here's what I can do with it:
d = Dog.new ["roll", "speak", "play dead"], "Spots", true
d.pet? #=> true
d.get_tricks #=> ["roll", "speak", "play dead"]
d.get_name #=> "Spots"
It works fine, but I'm just wondering if there's a better way to do this.
It is not a good programming practice to hard code a fixed string like "Unnamed" as the value for #name. In such case, you should assign nil, and do whatever modification to it when you print it. Suppose you do this.
Then is_pet can be deduced from whether name is nil or not, so it is redundant to have that as an instance variable. You can simply apply !! to name in order to get is_pet. Therefore, you should get rid of such instance variable.
You have get_ prefixes as getter methods, but in Ruby, it is a better practice to have the same name as the instance variables (without the atmark) as the getter name.
This will give you:
module Pet
attr_reader :name
def initialize name; #name = name end
end
class Dog
include Pet
attr_reader :tricks
def initialize tricks, name
#tricks = tricks
super(name)
end
end
d = Dog.new ["roll", "speak", "play dead"], "Spots"
d.tricks #=> ["roll", "speak", "play dead"]
d.name #=> "Spots"
!!d.name #=> true (= `is_pet`)
Do not write code that calls super to get into an included module. Don't write modules that will expect children to call super. That's not the point of modules.
It's good object oriented style to not ask about what things are. Look up "tell, don't ask" and duck typing in general.
If you want to provide a default initialize method, you probably want inheritance. But there are occasionally valid use cases for overriding initialize in a module. The idiomatic thing to do here is a hook method:
module Pet
def initialize(options = {})
#name = options[:name]
post_initialize(options)
end
def post_initialize(options = {})
# can be overridden in including modules
end
end
class Dog
include Pet
def post_initialize(options = {})
#tricks = options[:tricks]
end
end
dog = Dog.new(name: "Fido", tricks: ["play dead", "roll over"])
A module is for including some shared behavior among many different things. It's good to consider it like an adjective describing what you might do with a class that includes it. Words that end in "-able" (like Enumerable or Comparable), describing a receiving class, or "-or" (Iterator, Interactor), describing a doing class, are good candidates for being modules.

Making a Yhatzee game, array won't show up on screen

Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result

Accessing the name of an anonymous class in superclass' self.inherited

I would like to access a class' name in its superclass MySuperclass' self.inherited method. It works fine for concrete classes as defined by class Foo < MySuperclass; end but it fails when using anonymous classes. I tend to avoid creating (class-)constants in tests; I would like it to work with anonymous classes.
Given the following code:
class MySuperclass
def self.inherited(subclass)
super
# work with subclass' name
end
end
klass = Class.new(MySuperclass) do
def self.name
'FooBar'
end
end
klass#name will still be nil when MySuperclass.inherited is called as that will be before Class.new yields to its block and defines its methods.
I understand a class gets its name when it's assigned to a constant, but is there a way to set Class#name "early" without creating a constant?
I prepared a more verbose code example with failing tests to illustrate what's expected.
Probably #yield has taken place after the ::inherited is called, I saw the similar behaviour with class definition. However, you can avoid it by using ::klass singleton method instead of ::inherited callback.
def self.klass
#klass ||= (self.name || self.to_s).gsub(/Builder\z/, '')
end
I am trying to understand the benefit of being able to refer to an anonymous class by a name you have assigned to it after it has been created. I thought I might be able to move the conversation along by providing some code that you could look at and then tell us what you'd like to do differently:
class MySuperclass
def self.inherited(subclass)
# Create a class method for the subclass
subclass.instance_eval do
def sub_class() puts "sub_class here" end
end
# Create an instance method for the subclass
subclass.class_eval do
def sub_instance() puts "sub_instance here" end
end
end
end
klass = Class.new(MySuperclass) do
def self.name=(name)
#name = Object.const_set(name, self)
end
def self.name
#name
end
end
klass.sub_class #=> "sub_class here"
klass.new.sub_instance #=> "sub_instance here"
klass.name = 'Fido' #=> "Fido"
kn = klass.name #=> Fido
kn.sub_class #=> "sub_class here"
kn.new.sub_instance #=> "sub_instance here"
klass.name = 'Woof' #=> "Woof"
kn = klass.name #=> Fido (cannot change)
There is no way in pure Ruby to set a class name without assigning it to a constant.
If you're using MRI and want to write yourself a very small C extension, it would look something like this:
VALUE
force_class_name (VALUE klass, VALUE symbol_name)
{
rb_name_class(klass, SYM2ID(symbol_name));
return klass;
}
void
Init_my_extension ()
{
rb_define_method(rb_cClass, "force_class_name", force_class_name, 1);
}
This is a very heavy approach to the problem. Even if it works it won't be guaranteed to work across various versions of ruby, since it relies on the non-API C function rb_name_class. I'm also not sure what the behavior will be once Ruby gets around to running its own class-naming hooks afterward.
The code snippet for your use case would look like this:
require 'my_extension'
class MySuperclass
def self.inherited(subclass)
super
subclass.force_class_name(:FooBar)
# work with subclass' name
end
end

What is the difference between def func(var) and def func=(var)?

In a class definition, what is the difference between these two methods?
def func(var)
...
end
def func=(var)
...
end
Is there any, or is one of them not valid?
Both of them are valid method definitions. But the second one is defining a 'setter' method - you can call this method with the following syntax:
obj.func = 123
This statement will be translated into
obj.func=(123)
You can take a look at this answer where I explain this syntax in a bit more detail.
To explain some things about reader/writer AKA getter/setter methods in Ruby:
Ruby doesn't force us to use = in the method definition for a setter. We get to choose whether the method has one.
Consider this:
class Foo
# automagically creates:
# .v
# .v=
attr_accessor :v
def initialize(v)
puts "inside initialize(#{ v })"
#v = v
end
def setter(v)
puts "inside setter(#{ v })"
#v = v
end
def setter=(v)
puts "inside setter=(#{ v })"
#v = v
end
end
f = Foo.new(1)
puts f.v
f.setter(2)
puts f.v
f.setter = 3
puts f.v
f.setter=(4)
puts f.v
f.v = 5
puts f.v
f.v=(6)
puts f.v
Running the code outputs:
inside initialize(1)
1
inside setter(2)
2
inside setter=(3)
3
inside setter=(4)
4
5
6
The = is simply another letter in the method name because Ruby is smart enough to know if it sees f.setter = 3 it should use the setter=(v) method.
Ruby doesn't force using = to set a variable, you can decide if it makes more sense to you when you define the method. It is idiomatic that we use = because it helps make a setter look like an assignment, removing the urge to name all the setters something like set_v(v).
These are defining the getter and setter methods if you will. Say you have a Person class with a phone attribute.
class Person
def phone
#phone
end
def phone=(number)
#phone = number
end
end
Now you could change the phone attribute (managed internally in the #phone) by simply setting the property which will invoke the phone= method.
john = Person.new
john.phone = "123-456-7890"
It looks like a property assignment on the outside. Other characters that you can stack at the end of a method name are ? for boolean getters, ! for destructive operations. Again, these are just conventions and you're free to use these three characters as you want. However, code simply looks more natural with these symbols around. For example,
question.closed?
document.destroy!

Resources