I want to define a dynamic union type HobbyType as a constraint to the class method printype argument.
annotation Hobby
end
struct Pizza
end
struct Sport
end
struct Movie
end
class Person
alias HobbyType = String | {{ hobby_types }}
@[Hobby]
@favorite : Movie
@[Hobby]
@eating : Pizza
@name : String
macro hobby_types
{{ "Union(#{@type.instance_vars.select(&.annotation(Hobby))
.map(&.type.id).splat})".id }}
end
def self.printype(hobby : HobbyType)
pp! "#{hobby}"
end
end
But the code doesn’t compile and produce this error.
What you’re trying to do here isn’t directly possible since instance vars are only known within the context of a method, which you can’t define an alias within. You could use some sort of macro to declare the ivars and push their types to some array you use within a macro finished hook to build out the alias if you really wanted tho. Otherwise not sure there’s much you can do.
As long as it can achieve the goal described on the subject of this topic, alternative solution to alias is OK. I’m trying to move this forward but got another error.
annotation Hobby
end
struct Pizza
end
struct Sport
end
struct Movie
end
class Person
#alias HobbyType = String | {{ hobby_types }}
macro add_hobby(hb)
@[Hobby]
@{{ hb.var }} : {{ hb.type }}?
end
@[Hobby]
@favorite : Movie?
@[Hobby]
@eating : Pizza?
@name : String?
macro union_hobby_types
{{ "Union(#{@type.instance_vars.select(&.annotation(Hobby))
.map(&.type.id).splat})".id }}
end
def self.printype(hobby : HobbyType)
pp! "#{hobby}"
end
def hobby_types
{{ union_hobby_types }}
end
end
pp Person.new.hobby_types
Macro defs are called like regular methods. They are not visible in the macro language (inside the curly braces). Just drop the curly braces and it works.
I’m trying to solve a problem in Granite.
If we have a model defined as described in the doc,
enum OrderStatus
Active
Expired
Completed
end
class Order < Granite::Base
connection mysql
table foos
# Other fields
column status : OrderStatus, converter: Granite::Converters::Enum(OrderStatus, String)
end
It will fail when running the code like Order.create(price: 2, product_id: 9, status: OrderStatus::Active) or order.update(status: OrderStatus::Expired), because the implementation of create or update restricts the argument type(ModelArgs) to a list of basic types commonly known to a db. It doesn’t consider the user customized type OrderStatus.
So my code here is just a simplified version showing the issue and my attempt to fix it.
module Model
macro included
HOBBIES = [] of Nil
macro finished
alias HobbyType =
\{% for hb in HOBBIES %}
\{{ hb.type }} |
\{% end %}
String
end
end
end
struct Pizza
end
struct Sport
end
struct Movie
end
class Person
include Model
macro hobby(hb)
{% HOBBIES << hb %}
@{{ hb.var }} : {{ hb.type }}?
end
hobby favorite : Movie
hobby eating : Pizza
@name : String?
macro finished
def self.printype(hobby : HobbyType)
hobby.class
end
end
def hobby_types
HobbyType
end
end
pp Person.new.hobby_types
pp Person.printype(Movie.new)
That solution might work, but it’s pretty fragile. For example, HobbyType isn’t defined until the finished hooks execute. That’s a pretty strong restriction. I don’t think this is really a good idea.
I’m not familiar with the details of Granite, but I’d try to solve this by enhancing the .create/#update methods to allow types that can be converted.
After HobbyType is defined, HOBBIES is actually not needed. HOBBIES.compact! can’t remove any elements. HOBBIES=nil also doesn’t work. So HOBBIES[] is still available during runtime as
Person::HOBBIES # => [nil, nil]
It it possible to use a temorary variable that is only used during compile-time so that it will not consume any runtime memory?
If you never use HOBBIES in runtime code it won’t be initialized, and following that there will not be any code generated.
So your code to print Person::HOBBIES only causes it to be compiled. If you’d omit that, it wouldn’t.
You can make sure to exclude it from code generation by changing the type to Array(_): [] of _ - this will never compile, but it works for macro use.