forked from luckyframework/avram
/
needy_initializer_and_delete_methods.cr
184 lines (159 loc) · 6 KB
/
needy_initializer_and_delete_methods.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
module Avram::NeedyInitializerAndDeleteMethods
macro included
OPERATION_NEEDS = [] of Nil
macro inherited
inherit_needs
end
end
macro needs(type_declaration)
{% OPERATION_NEEDS << type_declaration %}
property {{ type_declaration.var }} : {{ type_declaration.type }}
end
macro inherit_needs
\{% if !@type.constant(:OPERATION_NEEDS) %}
OPERATION_NEEDS = [] of Nil
\{% end %}
\{% if !@type.ancestors.first.abstract? %}
\{% for type_declaration in @type.ancestors.first.constant :OPERATION_NEEDS %}
\{% OPERATION_NEEDS << type_declaration %}
\{% end %}
\{% end %}
macro inherited
inherit_needs
end
macro finished
# This is called at the end so @type will be of the subclass,
# and not the parent abstract class.
generate_initializer_and_delete_methods
end
end
macro generate_initializer_and_delete_methods
# Build up a list of method arguments
#
# These method arguments can be used in macros for generating delete/new
#
# This way everything has a name and type and we don't have to rely on
# **named_args. **named_args** are easy but you get horrible type errors.
#
# attribute_method_args would look something like:
#
# name : String | Avram::Nothing = Avram::Nothing.new,
# email : String | Nil | Avram::Nothing = Avram::Nothing.new
#
# This can be passed to macros as a string, and then the macro can call .id
# on it to output the string as code!
{% attribute_method_args = "" %}
# Build up a list of params so you can use the method args
#
# This looks something like:
#
# name: name,
# email: email
{% attribute_params = "" %}
{% if @type.constant :COLUMN_ATTRIBUTES %}
{% for attribute in COLUMN_ATTRIBUTES.uniq %}
{% attribute_method_args = attribute_method_args + "#{attribute[:name]} : #{attribute[:type]} | Avram::Nothing" %}
{% if attribute[:nilable] %}{% attribute_method_args = attribute_method_args + " | Nil" %}{% end %}
{% attribute_method_args = attribute_method_args + " = Avram::Nothing.new,\n" %}
{% attribute_params = attribute_params + "#{attribute[:name]}: #{attribute[:name]},\n" %}
{% end %}
{% end %}
{% for attribute in ATTRIBUTES %}
{% attribute_method_args = attribute_method_args + "#{attribute.var} : #{attribute.type} | Avram::Nothing = Avram::Nothing.new,\n" %}
{% attribute_params = attribute_params + "#{attribute.var}: #{attribute.var},\n" %}
{% end %}
generate_initializer({{ attribute_method_args }}, {{ attribute_params }})
generate_delete_methods({{ attribute_method_args }}, {{ attribute_params }})
end
macro hash_is_not_allowed_helpful_error(method, additional_args = nil)
{% raise <<-ERROR
You can't pass a Hash directly to #{method.id}. Instead pass named arguments, or convert the hash to params.
Try this...
* Use named arguments (recommended) - #{@type}.#{method.id}(#{additional_args.id if additional_args}title: "My Title")
* Convert hash to params (not as safe) - Avram::Params.new({"title" => "My Title"})
ERROR
%}
end
macro generate_delete(attribute_method_args, attribute_params, with_params, with_bang)
def self.delete{% if with_bang %}!{% end %}(
record : T,
params : Hash,
**named_args
)
{% if with_bang %}
{% else %}
yield nil, nil
{% end %}
hash_is_not_allowed_helpful_error(:delete{% if with_bang %}!{% end %}, additional_args: "record, ")
end
def self.delete{% if with_bang %}!{% end %}(
record : T,
{% if with_params %}params,{% end %}
{% for type_declaration in OPERATION_NEEDS %}
{{ type_declaration }},
{% end %}
{{ attribute_method_args.id }}
)
operation = new(
record,
{% if with_params %}params,{% end %}
{% for type_declaration in OPERATION_NEEDS %}
{{ type_declaration.var }},
{% end %}
{{ attribute_params.id }}
)
{% if with_bang %}
operation.delete!
{% else %}
if operation.delete
yield operation, operation.record.not_nil!
else
yield operation, nil
end
{% end %}
end
end
macro generate_delete_methods(attribute_method_args, attribute_params)
generate_delete({{ attribute_method_args }}, {{ attribute_params }}, with_params: true, with_bang: true)
generate_delete({{ attribute_method_args }}, {{ attribute_params }}, with_params: true, with_bang: false)
generate_delete({{ attribute_method_args }}, {{ attribute_params }}, with_params: false, with_bang: true)
generate_delete({{ attribute_method_args }}, {{ attribute_params }}, with_params: false, with_bang: false)
end
macro generate_initializer(attribute_method_args, attribute_params)
{% needs_method_args = "" %}
{% for type_declaration in OPERATION_NEEDS %}
{% needs_method_args = needs_method_args + "@#{type_declaration},\n" %}
{% end %}
def initialize(
@record : T,
@params : Avram::Paramable,
{{ needs_method_args.id }}
{{ attribute_method_args.id }}
)
set_attributes({{ attribute_params.id }})
end
def initialize(
@record : T,
{{ needs_method_args.id }}
{{ attribute_method_args.id }}
)
@params = Avram::Params.new
set_attributes({{ attribute_params.id }})
end
def set_attributes({{ attribute_method_args.id }})
{% if @type.constant :COLUMN_ATTRIBUTES %}
{% for attribute in COLUMN_ATTRIBUTES.uniq %}
unless {{ attribute[:name] }}.is_a? Avram::Nothing
self.{{ attribute[:name] }}.value = {{ attribute[:name] }}
end
{% end %}
{% end %}
{% for attribute in ATTRIBUTES %}
unless {{ attribute.var }}.is_a? Avram::Nothing
self.{{ attribute.var }}.value = {{ attribute.var }}
end
{% end %}
extract_changes_from_params
end
end
end