Question about hash to set class arguments

Hello, I have a question, I would like for all of my classes, pass all my arguments by Hash. But is it possible to initialize a single Hash property with differents types of possible value (I mean for example a hash, with some String value, Integer … etc)

For example, I would like to do something like that:

class Thing

    property arguments : Hash
    property id : Integer
    property name : String

    def initialize(@arguments)
    end

end

var = Thing.new({id: 0, name: "Name"})

Technically speaking {id: 0, name: "Name"} is a NamedTuple not a Hash. But in your case you could type the hash property as like Hash(String, String | Int32, Bool, ...) but even that would be kinda meh as you’d prob run into type mismatches in some cases. Could also maybe use generics to handle it. E.g. Generics - Crystal.

Handling nested objects would also be challenging. Plus to set the actual ivars you’d need to do something like:

def iniitalize(arguments)
  @id = arguments["id"].as Int32
  @name = arguments["name"].as String
end

Could possibly use some macro to build that out for you automatically, but :person_shrugging:.

It would be easier if you didn’t store the hash as its kinda pointless given you could just reconstruct the hash based on the other ivars.

What’s the use case for this? I would say its a bit of a smell, esp when you can just do Thing.new id: 0, name: "Name".

The thing is, I just would like to initialize the class members in the order I want. Something flexible

I’m not sure i follow. If you have a class like:

class Thing
  property id : Int32
  property name : String

  def initialize(@id : Int32, @name : String); end
end

You can do both Thing.new id: 10, name: "foo" or Thing.new name: "foo", id: 10. I.e. it doesn’t matter which order you provide the required args when providing them as named arguments.

I didn’t knew that o-o

Why crystal compiler complain when I set the property id as Int64 ? When I initialize my instancied class, I have an error when I set for example the value to 1

Can you share an example of that? It seems to work work fine Carcin.

Sorry, I said a mistake, it’s the opposite:

class Thing

    property id : Int64

    def initialize(@id)
    end

end


class NamedThing < Thing

    property name : String

    def initialize(@id,@name)
        super(@id)
    end

end

var = NamedThing.new name: "Truc", id: 0

puts var.name

Showing last frame. Use --error-trace for full trace.

In NamedThing.cr:7:20

 7 | def initialize(@id,@name)
                    ^--
Error: instance variable '@id' of Thing must be Int64, not Int32

The gist of it is you do not want to use @id within NamedThing, as its typing @id as an Int32 (since there is no type restriction on it). Then when you do the super(@id) it fails since the id property of Thing is typed as Int64 which fails since you’re passing up an Int32.

I.e. you prob want to do like:

def initialize(id,@name)
  super id
end
1 Like

super id is definitely the way to go, but if you run into this in the future in a situation where super doesn’t make sense (like below), then you can also fix it by specifying the method argument type restriction:

Before

class Thing
    property id : Int64

    def initialize(@id)
    end
end

var = Thing.new 0 # => Error: instance variable '@id' of Thing must be Int64, not Int32

puts var.id

After

class Thing
    property id : Int64

    def initialize(@id : Int64) # <--- Add a type restriction
    end
end

var = Thing.new 0

puts var.id # => 0

The reason is that the compiler doesn’t know to automatically cast your Int32 literal (0) into an Int64 unless there’s a type restriction.

Edit:

Actually, even with super, you still need the type restriction. Try this:

class Thing
    property id : Int64

    def initialize(@id : Int64)
    end
end


class NamedThing < Thing
    property name : String

    def initialize(id, @name)
        super id
    end
end

var = NamedThing.new name: "Truc", id: 0

puts var.name # => Truc

I have again an error T_T

class Thing

    property id : Int64

    def initialize(@id : Int64)
    end

end

class NamedThing < Thing

    property name : String

    def initialize(id,@name)
        super id
    end

end

var = NamedThing.new name: "Truc", id: 0

puts var.name

Log:

Showing last frame. Use --error-trace for full trace.

In NamedThing.cr:8:9

 8 | super id
     ^----
Error: no overload matches 'Thing#initialize' with type Int32

Overloads are:
 - Thing#initialize(id : Int64)

What version of Crystal are you using? That exact code works on carc.in with Crystal 1.3.2.

Ref: Crystal 1.3.0 is released! - The Crystal Programming Language

1 Like

I’m on Gentoo, I have the 1.2.2 version

In that case you’ll need to do one of the following:

  1. Upgrade to 1.3.x
  2. Pass in an Int64, like 0_i64
  3. Convert the Int32 to a Int64, like super id.to_i64
  4. Give a property type restriction to id within NamedThing, like def initialize(id : Int64, @name)
1 Like