meta-record 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ require 'metarecord/generators/crails/edit_generator'
2
+
3
+ class CometEditGenerator < CrailsEditGenerator
4
+ def _append_macro str
5
+ @src ||= ""
6
+ @src += str + "\n"
7
+ end
8
+
9
+ def generate_json_methods object
10
+ super
11
+ @rendering_from_json = true
12
+ _append_macro "#ifdef #{CometDataGenerator.client_define}"
13
+ _append "void #{@klassname}::from_json(Data data)"
14
+ _append "{"
15
+ @indent += 1
16
+ _append "id = data[\"id\"].defaults_to<#{id_type}>(#{null_id});"
17
+ _append "edit(data);"
18
+ @indent -= 1
19
+ _append "}"
20
+ _append_macro "#endif"
21
+ @rendering_from_json = false
22
+ end
23
+
24
+ def validation type, name, data
25
+ if data[:uniqueness] == true
26
+ _append_macro "#ifndef #{CometDataGenerator.client_define}"
27
+ _append validate_uniqueness type, name
28
+ _append_macro "#endif"
29
+ data[:uniqueness] = false
30
+ end
31
+ super type, name, data
32
+ end
33
+
34
+ def has_one_getter type, name, options
35
+ type = get_type type
36
+ tptr = ptr_type type
37
+ _append_macro "#ifndef #{CometDataGenerator.client_define}"
38
+ super
39
+ _append_macro "#else"
40
+ _append "#{tptr} #{@klassname}::get_#{name}() const"
41
+ _append "{"
42
+ _append " #{tptr} model = std::make_shared<#{type}>();"
43
+ _append " model->set_id(get_#{name}_id());"
44
+ _append " model->fetch();"
45
+ _append " return model;"
46
+ _append "}"
47
+ _append_macro "#endif"
48
+ end
49
+
50
+ def joined_has_one_edit type, name, options
51
+ data_id = "data[\"#{name}_id\"]"
52
+ inline_data = "data[\"#{name}\"]"
53
+ _append_macro "#ifndef #{CometDataGenerator.client_define}"
54
+ super
55
+ _append_macro "#else"
56
+ _append "{"
57
+ _append " if (#{data_id} == 0)"
58
+ _append " set_#{name}(nullptr);"
59
+ _append " else if (!get_#{name}() || #{data_id} != get_#{name}()->get_id())"
60
+ _append " {"
61
+ _append " auto linked_resource = std::make_shared<#{type}>();"
62
+ _append " linked_resource->set_id(#{data_id}.as<#{id_type}>());"
63
+ _append " set_#{name}(linked_resource);"
64
+ _append " }"
65
+ _append "}"
66
+ _append "else if (#{inline_data}.exists())"
67
+ _append "{"
68
+ _append " auto linked_resource = std::make_shared<#{type}>();"
69
+ _append " linked_resource->from_json(#{inline_data});"
70
+ _append " set_#{name}(linked_resource);"
71
+ _append "}"
72
+ _append_macro "#endif"
73
+ end
74
+
75
+ def has_many_fetch type, name, options
76
+ tptr = ptr_type type
77
+ singular_name = get_singular_name name
78
+
79
+ _append_macro "#ifndef #{CometDataGenerator.client_define}"
80
+ super type, name, options
81
+ _append_macro "#else"
82
+ _append "Comet::Promise #{@klassname}::fetch_#{name}()"
83
+ _append "{"
84
+ @indent += 1
85
+ _append "std::vector<Comet::Promise> promises;\n"
86
+ _append "for (auto id : #{singular_name}_ids)"
87
+ _append "{"
88
+ @indent += 1
89
+ _append "#{tptr} model;\n"
90
+ _append "model->set_id(id);"
91
+ _append "promises.push_back(model->fetch());"
92
+ _append "#{name}.push_back(model);"
93
+ _append "#{name}_fetched = true;"
94
+ @indent -= 1
95
+ _append "}"
96
+ _append "return Comet::Promise::all(promises);"
97
+ @indent -= 1
98
+ _append "}"
99
+ _append_macro "#endif"
100
+ end
101
+
102
+ def property type, name, options = {}
103
+ if options[:ready_only] == true && rendering_edit?
104
+ options[:read_only] = false
105
+ _append_macro "#ifdef #{CometDataGenerator.client_define}"
106
+ super
107
+ _append_macro "#endif"
108
+ else
109
+ super
110
+ end
111
+ end
112
+
113
+ def has_many type, name, options = {}
114
+ if options[:read_only] == true && rendering_edit?
115
+ options[:read_only] = false
116
+ _append_macro "#ifdef #{CometDataGenerator.client_define}"
117
+ super
118
+ _append_macro "#endif"
119
+ else
120
+ super
121
+ end
122
+ end
123
+
124
+ def has_one type, name, options = {}
125
+ if options[:read_only] == true && rendering_edit?
126
+ options[:read_only] = false
127
+ _append_macro "#ifdef #{CometDataGenerator.client_define}"
128
+ super
129
+ _append_macro "#endif"
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ class << self
136
+ def generate_includes
137
+ <<CPP
138
+ #ifndef #{CometDataGenerator.client_define}
139
+ #{super}
140
+ #else
141
+ # include <crails/comet/mvc/helpers.hpp>
142
+ #endif
143
+ CPP
144
+ end
145
+
146
+ def sourcefile_to_destfile sourcefile
147
+ base = super sourcefile
148
+ basepath = Pathname.new base
149
+ parentdir = basepath.dirname.to_s + "/shared"
150
+ "#{parentdir}/#{basepath.basename}"
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,289 @@
1
+ require 'metarecord/model'
2
+ require 'metarecord/generator_base'
3
+
4
+ class CrailsDataGenerator < GeneratorBase
5
+ attr_accessor :forward_declarations
6
+
7
+ def reset
8
+ super
9
+ @forward_declarations = []
10
+ @src = {}
11
+ end
12
+
13
+ def _append str, opts = {}
14
+ @src[@current_visibility] ||= ""
15
+ @src[@current_visibility] += " " * (@indent * @tab_size)
16
+ @src[@current_visibility] += str + "\n" unless opts[:no_line_return]
17
+ end
18
+
19
+ def _append_macro str
20
+ @src[@current_visibility] ||= ""
21
+ @src[@current_visibility] += str + "\n"
22
+ end
23
+
24
+ def get_headers
25
+ forward_source = ""
26
+ @forward_declarations.each do |type|
27
+ parts = type.split('::').reject {|part| part.empty?}
28
+ last_part = parts.last
29
+ if parts.length > 1
30
+ parts[0...parts.size-1].each do |part|
31
+ forward_source += "namespace #{part} { "
32
+ end
33
+ forward_source += "class #{last_part};"
34
+ (parts.size - 1).times do forward_source += " } " end
35
+ forward_source += "\n"
36
+ else
37
+ forward_source += "class #{last_part};\n"
38
+ end
39
+ end
40
+ forward_source
41
+ end
42
+
43
+ def generate_class_head object
44
+ _append "#pragma db object abstract"
45
+ _append "struct #{object[:name]}"
46
+ end
47
+
48
+ def generate_for object
49
+ reset
50
+ @forward_declarations << object[:classname] unless object[:classname].nil?
51
+ @current_visibility = :_preblock
52
+ @indent += 1
53
+ generate_class_head object
54
+ _append "{"
55
+ @indent += 1
56
+ _append "friend class odb::access;"
57
+ _append "#pragma db transient"
58
+ _append "DataTree errors;"
59
+ @current_visibility = :public
60
+ edit_resource_declaration
61
+ prepare_order_by
62
+ self.instance_eval &object[:block]
63
+ @current_visibility = :_postblock
64
+ @indent -= 1
65
+ _append "};"
66
+ @indent -= 1
67
+ render
68
+ end
69
+
70
+ def edit_resource_declaration
71
+ _append "virtual std::vector<std::string> find_missing_parameters(Data) const;"
72
+ _append "virtual void edit(Data);"
73
+ _append "virtual std::string to_json() const;"
74
+ _append "virtual bool is_valid();"
75
+ _append "virtual void on_dependent_destroy(#{id_type});"
76
+ _append ""
77
+ end
78
+
79
+ def prepare_order_by
80
+ _append "template<typename QUERY>"
81
+ _append "static QUERY default_order_by(QUERY query)"
82
+ _append "{"
83
+ _append " return query; // order_by"
84
+ _append "}\n"
85
+ end
86
+
87
+ def render
88
+ @current_visibility = :_result
89
+ result = @src[:_preblock]
90
+ [:public, :protected, :private].each do |key|
91
+ unless @src[key].nil?
92
+ result += " #{key}:\n"
93
+ result += @src[key]
94
+ end
95
+ end
96
+ result += @src[:_postblock]
97
+ result
98
+ end
99
+
100
+ def visibility value
101
+ @current_visibility = value
102
+ end
103
+
104
+ def with_visibility value, &block
105
+ old_visibility = @current_visibility
106
+ visibility value
107
+ self.instance_eval &block
108
+ visibility old_visibility
109
+ end
110
+
111
+ def resource_name name
112
+ visibility :public
113
+ _append "static const std::string scope;"
114
+ _append "static const std::string plural_scope;"
115
+ _append "static const std::string view;"
116
+ end
117
+
118
+ def order_by name, flow = nil
119
+ src = "return query + \"ORDER BY\" + QUERY::#{name}"
120
+ src += " + \"#{flow.to_s.upcase}\"" unless flow.nil?
121
+ src += ";"
122
+ @src[:public] = @src[:public].gsub /^(\s*).*\/\/ order_by$/, '\1' + src
123
+ end
124
+
125
+ def datatree_property name, options
126
+ with_visibility :public do
127
+ virt = if options[:allow_override] == true then "virtual " else "" end
128
+ _append "#{virt}DataTree& get_#{name}() { return #{name}; }"
129
+ _append "#{virt}const DataTree& get_#{name}() const { return #{name}; }"
130
+ _append "#{virt}void set_#{name}(Data value) { this->#{name}.clear(); this->#{name}.as_data().merge(value); }"
131
+ end
132
+ end
133
+
134
+ def property type, name, options = {}
135
+ if type == 'DataTree'
136
+ datatree_property name, options
137
+ else
138
+ with_visibility :public do
139
+ virt = if options[:allow_override] == true then "virtual " else "" end
140
+ _append "#{virt}#{type} get_#{name}() const { return #{name}; }"
141
+ _append "#{virt}void set_#{name}(#{type} value) { this->#{name} = value; }"
142
+ end
143
+ end
144
+ make_pragma_db options[:db] unless options[:db].nil?
145
+ if options[:default].nil?
146
+ _append "#{type} #{name};"
147
+ else
148
+ _append "#{type} #{name} = #{get_value options[:default]};"
149
+ end
150
+ end
151
+
152
+ def make_pragma_db options, authorized_options = []
153
+ is_option_available = lambda {|a|
154
+ (not options[a].nil?) && (authorized_options.size == 0 || authorized_options.include?(a))
155
+ }
156
+ src = ""
157
+ src += "transient " if is_option_available.call(:transient) && options[:transient] == true
158
+ src += "type(\"#{options[:type]}\") " if is_option_available.call :type
159
+ src += "column(\"#{options[:column]}\") " if is_option_available.call :column
160
+ src += "default(#{get_value options[:default]}) " if is_option_available.call :default
161
+ src += "null " if is_option_available.call(:null) && options[:null] == true
162
+ _append "#pragma db #{src}" if src.size > 0
163
+ end
164
+
165
+ def has_one type, name, options = {}
166
+ @forward_declarations << type
167
+ type = get_type type
168
+ tptr = ptr_type type
169
+ virt = if options[:allow_override] == true then "virtual " else "" end
170
+ if options[:joined] != false
171
+ with_visibility :public do
172
+ _append "#{virt}#{tptr} get_#{name}() const { return #{name}; }"
173
+ _append "#{virt}void set_#{name}(#{tptr} v) { this->#{name} = v; }"
174
+ _append "#{virt}#{id_type} get_#{name}_id() const;"
175
+ end
176
+ _append "#{tptr} #{name};"
177
+ else
178
+ with_visibility :public do
179
+ _append "#{virt}#{tptr} get_#{name}() const;"
180
+ _append "#{virt}void set_#{name}(#{tptr} v);"
181
+ _append "#{id_type} get_#{name}_id() const { return #{name}_id; }"
182
+ _append "void set_#{name}_id(#{id_type} v) { #{name}_id = v; }"
183
+ end
184
+ make_pragma_db options[:db] unless options[:db].nil?
185
+ _append "#{id_type} #{name}_id = #{null_id};"
186
+ end
187
+ with_visibility :public do
188
+ _append <<CPP
189
+
190
+ template<typename ARRAY>
191
+ static void collect_#{name}(ARRAY& array, std::map<#{id_type}, #{tptr} >& results)
192
+ {
193
+ for (auto model : array)
194
+ {
195
+ if (results.find(model.get_#{name}_id()) == results.end())
196
+ results[model.get_#{name}_id()] = model.get_#{name}();
197
+ }
198
+ }
199
+
200
+ CPP
201
+ end
202
+ end
203
+
204
+ def has_many type, name, options = {}
205
+ @forward_declarations << type
206
+ type = get_type type
207
+ tptr = ptr_type type
208
+ list_type = "std::list<#{tptr} >"
209
+ singular_name = get_singular_name name
210
+ with_visibility :public do
211
+ virt = if options[:allow_override] == true then "virtual " else "" end
212
+ _append "#{virt}bool update_#{name}(Data);"
213
+ _append "#{virt}void add_#{singular_name}(#{tptr});"
214
+ _append "#{virt}void remove_#{singular_name}(const #{type}&);"
215
+ _append "#{virt}void collect_#{name}(std::map<#{id_type}, #{tptr} >&);"
216
+ end
217
+ if options[:joined] != false
218
+ _join_based_has_many list_type, name, options
219
+ else
220
+ _id_based_has_many list_type, name, options
221
+ end
222
+ end
223
+
224
+ def _join_based_has_many list_type, name, options
225
+ singular_name = get_singular_name name
226
+ with_visibility :public do
227
+ _append "const #{list_type}& get_#{name}() const { return #{name}; }"
228
+ _append "std::vector<#{id_type}> get_#{singular_name}_ids() const;"
229
+ end
230
+ _append "#{list_type} #{name};"
231
+ end
232
+
233
+ def has_many_fetch list_type, name, options
234
+ with_visibility :public do
235
+ _append "void fetch_#{name}();"
236
+ end
237
+ end
238
+
239
+ def _id_based_has_many list_type, name, options
240
+ singular_name = get_singular_name name
241
+ store_type = "std::vector<#{id_type}>"
242
+ with_visibility :public do
243
+ _append "const #{store_type}& get_#{singular_name}_ids() const { return #{singular_name}_ids; }"
244
+ _append "const #{list_type}& get_#{name}();"
245
+ _append "void set_#{singular_name}_ids(const #{store_type}& val) { #{singular_name}_ids = val; #{name}_fetched = false; }"
246
+ end
247
+ has_many_fetch list_type, name, options
248
+ options[:db] ||= {}
249
+ options[:db][:type] ||= "INTEGER[]"
250
+ make_pragma_db options[:db], [:type, :column, :null]
251
+ _append "#{store_type} #{singular_name}_ids;"
252
+ make_pragma_db transient: true
253
+ _append "#{list_type} #{name};"
254
+ make_pragma_db transient: true
255
+ _append "bool #{name}_fetched = false;"
256
+ end
257
+
258
+ class << self
259
+ def extension ; ".hpp" ; end
260
+
261
+ def generate_includes
262
+ <<CPP
263
+ # include <vector>
264
+ # include <list>
265
+ # include <map>
266
+ # include <string>
267
+ # include <crails/datatree.hpp>
268
+ # include <crails/odb/id_type.hpp>
269
+ CPP
270
+ end
271
+
272
+ def make_file filename, data
273
+ file_define = "_#{filename[0...-3].upcase.gsub "/", "_"}_HPP"
274
+ source = <<CPP
275
+ #ifndef #{file_define}
276
+ # define #{file_define}
277
+ #{generate_includes}
278
+ #{collect_includes_for(filename, true).join "\n"}
279
+ namespace odb { class access; }
280
+ #{data[:headers].join ""}
281
+ namespace #{METARECORD_NAMESPACE}
282
+ {
283
+ #{data[:bodies].join "\n"}}
284
+
285
+ #endif
286
+ CPP
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,178 @@
1
+
2
+ require 'metarecord/model'
3
+ require 'metarecord/generator_base'
4
+
5
+ class CrailsDestroyPreparator < GeneratorBase
6
+ attr_accessor :data
7
+
8
+ def prepare
9
+ @data = {}
10
+ Model.list.each do |model|
11
+ @model = model
12
+ self.instance_eval &model[:block]
13
+ end
14
+ end
15
+
16
+ def has_many type, name, options = {}
17
+ unless options[:dependent].nil?
18
+ @data[type] ||= []
19
+ @data[type] << ({
20
+ type: "has_many", class: @model[:classname],
21
+ field: name, options: options
22
+ })
23
+ end
24
+ end
25
+
26
+ def has_one type, name, options = {}
27
+ unless options[:dependent].nil?
28
+ @data[type] ||= []
29
+ @data[type] << ({
30
+ type: "has_one", class: @model[:classname],
31
+ field: name, options: options
32
+ })
33
+ end
34
+ end
35
+ end
36
+
37
+ class CrailsDestroyGenerator < GeneratorBase
38
+ class << self
39
+ attr_accessor :destroy_data
40
+
41
+ def my_prepare
42
+ destroy_preparator = CrailsDestroyPreparator.new
43
+ destroy_preparator.prepare
44
+ @destroy_data = destroy_preparator.data
45
+ end
46
+
47
+ def extension ; ".destroy.cpp" ; end
48
+
49
+ def make_file filename, data
50
+ include = "lib/" + filename[0...-2] + "hpp"
51
+ source = "#include \"#{include}\"\n"
52
+ source += "#include \"lib/#{filename[0...-2]}queries.hpp\"\n"
53
+ source += "#include <crails/odb/helpers.hpp>\n"
54
+ source += "#include <crails/odb/any.hpp>\n"
55
+ source += "#include <#{GeneratorBase.odb_connection[:include]}>\n"
56
+ source += "#include \"lib/odb/application-odb.hxx\"\n"
57
+ source += (collect_includes_for filename).join "\n"
58
+ source += "\n" + (data[:bodies].join "\n")
59
+ end
60
+ end
61
+
62
+ def generate_for object
63
+ reset
64
+ CrailsDestroyGenerator.my_prepare if CrailsDestroyGenerator.destroy_data.nil?
65
+ @finalclass = object[:classname]
66
+ @klassname = get_classname(object)
67
+ _append "void #{@klassname}::on_dependent_destroy(#{id_type} self_id)"
68
+ _append "{"
69
+ unless object[:classname].nil?
70
+ @indent += 1
71
+ depended_by = CrailsDestroyGenerator.destroy_data[object[:classname]]
72
+ if depended_by.class == Array
73
+ _append "auto& database = *#{GeneratorBase.odb_connection[:object]}::instance;"
74
+ depended_by.each do |relation|
75
+ _append "// #{relation[:class]}"
76
+ _append "{"
77
+ @indent += 1
78
+ if relation[:type] == "has_one"
79
+ one_to_many relation
80
+ elsif relation[:type] == "has_many"
81
+ many_to_many relation
82
+ end
83
+ @indent -= 1
84
+ _append "}\n"
85
+ end
86
+ end
87
+ @indent -= 1
88
+ end
89
+ _append "}"
90
+ @src
91
+ end
92
+
93
+ def one_to_many relation
94
+ _append "typedef odb::query<::#{relation[:class]}> Query;"
95
+ _append "odb::result<::#{relation[:class]}> models;"
96
+ _append "Query query;\n"
97
+
98
+ if relation[:options][:joined] != false
99
+ _append "query = Query::#{relation[:field]}->id == self_id;"
100
+ else
101
+ _append "query = Query::#{relation[:field]}_id == self_id;"
102
+ end
103
+ _append "database.find<::#{relation[:class]}>(models, query);"
104
+ _append "for (auto model : odb::to_vector<::#{relation[:class]}>(models))"
105
+ if relation[:options][:dependent] == :destroy
106
+ _append " database.destroy(model);"
107
+ elsif relation[:options][:dependent] == :unlink
108
+ _append "{"
109
+ if relation[:options][:joined] != false
110
+ _append " model.set_#{relation[:field]}(nullptr);"
111
+ else
112
+ _append " model.set_#{relation[:field]}_id(0);"
113
+ end
114
+ _append " database.save(model);"
115
+ _append "}"
116
+ end
117
+ end
118
+
119
+ def many_to_many relation
120
+ if relation[:options][:joined] != false
121
+ outer_name = (relation[:field].split(/-|_|\s/).each do |i| i.capitalize! end).join
122
+ view_klass = "#{relation[:class].split("::").join}By#{outer_name}"
123
+ klassname = @klassname.split("::").last
124
+ singular_name = get_singular_name relation[:field]
125
+ _append "typedef odb::query<#{view_klass}> Query;"
126
+ _append "odb::result<#{view_klass}> models;"
127
+ _append "Query query;\n"
128
+ _append "query = Query::#{klassname}::id == self_id;"
129
+ _append "database.find<#{view_klass}>(models, query);"
130
+ _append "for (auto model : odb::to_vector<#{relation[:class]}, #{view_klass}>(models))"
131
+ _append "{"
132
+ @indent += 1
133
+ if relation[:options][:dependent] == :destroy
134
+ _append "if (model.get_#{relation[:field]}().size() == 1)"
135
+ _append " database.destroy(model);"
136
+ _append "else"
137
+ _append "{"
138
+ _append " model.remove_#{singular_name}(*static_cast<#{@finalclass}*>(this));"
139
+ _append " database.save(model);"
140
+ _append "}"
141
+ else relation[:options][:dependent] == :unlink
142
+ _append "model.remove_#{singular_name}(*static_cast<#{@finalclass}*>(this));"
143
+ _append "database.save(model);"
144
+ end
145
+ @indent -= 1
146
+ _append "}"
147
+ else
148
+ _append "typedef odb::query<#{relation[:class]}> Query;"
149
+ _append "std::vector<#{id_type}> ids = { self_id };"
150
+ _append "odb::result<#{relation[:class]}> models;"
151
+ _append "Query query;\n"
152
+ id_field = "#{get_singular_name relation[:field]}_ids"
153
+ _append "query = Query::#{id_field} + \"@>\" + ODB::array_to_string(ids, \"int\");"
154
+ _append "database.find<#{relation[:class]}>(models, query);"
155
+ _append "for (auto model : odb::to_vector<#{relation[:class]}>(models))"
156
+ _append "{"
157
+ @indent += 1
158
+ if relation[:options][:dependent] == :destroy
159
+ _append "auto id_list = model.get_#{id_field}();\n"
160
+ _append "if (id_list.size() == 1)"
161
+ _append " database.destroy(model);"
162
+ _append "else"
163
+ _append "{"
164
+ _append " id_list.erase(std::find(id_list.begin(), id_list.end(), self_id));"
165
+ _append " model.set_#{id_field}(id_list);"
166
+ _append " database.save(model);"
167
+ _append "}"
168
+ elsif relation[:options][:dependent] == :unlink
169
+ _append "auto id_list = model.get_#{id_field}();\n"
170
+ _append "id_list.erase(std::find(id_list.begin(), id_list.end(), self_id));"
171
+ _append "model.set_#{id_field}(id_list);"
172
+ _append "database.save(model);"
173
+ end
174
+ @indent -= 1
175
+ _append "}"
176
+ end
177
+ end
178
+ end