surikat 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +16 -0
  5. data/.idea/misc.xml +7 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/surikat.iml +50 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.idea/workspace.xml +744 -0
  10. data/.rspec +2 -0
  11. data/.travis.yml +5 -0
  12. data/Gemfile +6 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +399 -0
  15. data/Rakefile +6 -0
  16. data/bin/console +11 -0
  17. data/bin/setup +8 -0
  18. data/exe/surikat +234 -0
  19. data/lib/surikat.rb +421 -0
  20. data/lib/surikat/base_model.rb +35 -0
  21. data/lib/surikat/base_queries.rb +10 -0
  22. data/lib/surikat/base_type.rb +3 -0
  23. data/lib/surikat/configurations.rb +22 -0
  24. data/lib/surikat/new_app.rb +108 -0
  25. data/lib/surikat/routes.rb +67 -0
  26. data/lib/surikat/scaffold.rb +503 -0
  27. data/lib/surikat/session.rb +35 -0
  28. data/lib/surikat/session_manager.rb +92 -0
  29. data/lib/surikat/templates/.rspec.tmpl +1 -0
  30. data/lib/surikat/templates/.standalone_migrations.tmpl +6 -0
  31. data/lib/surikat/templates/Gemfile.tmpl +31 -0
  32. data/lib/surikat/templates/Rakefile.tmpl +2 -0
  33. data/lib/surikat/templates/aaa_queries.rb.tmpl +124 -0
  34. data/lib/surikat/templates/aaa_spec.rb.tmpl +151 -0
  35. data/lib/surikat/templates/application.yml.tmpl +14 -0
  36. data/lib/surikat/templates/base_aaa_model.rb.tmpl +28 -0
  37. data/lib/surikat/templates/base_model.rb.tmpl +6 -0
  38. data/lib/surikat/templates/base_spec.rb.tmpl +148 -0
  39. data/lib/surikat/templates/config.ru.tmpl +61 -0
  40. data/lib/surikat/templates/console.tmpl +14 -0
  41. data/lib/surikat/templates/crud_queries.rb.tmpl +105 -0
  42. data/lib/surikat/templates/database.yml.tmpl +26 -0
  43. data/lib/surikat/templates/hello_queries.rb.tmpl +19 -0
  44. data/lib/surikat/templates/hello_spec.rb.tmpl +39 -0
  45. data/lib/surikat/templates/routes.yml.tmpl +15 -0
  46. data/lib/surikat/templates/spec_helper.rb.tmpl +11 -0
  47. data/lib/surikat/templates/test_helper.rb.tmpl +30 -0
  48. data/lib/surikat/types.rb +45 -0
  49. data/lib/surikat/version.rb +3 -0
  50. data/lib/surikat/yaml_configurator.rb +18 -0
  51. data/surikat.gemspec +47 -0
  52. metadata +199 -0
@@ -0,0 +1,35 @@
1
+ require 'active_record'
2
+ require 'ransack'
3
+
4
+ #require File.join(Gem::Specification.find_by_name("surikat").full_gem_path, "lib/surikat/yaml_configurator")
5
+
6
+ module Surikat
7
+
8
+ class BaseModel < ActiveRecord::Base
9
+ ActiveRecord::Base.establish_connection(Surikat.config.db)
10
+
11
+ self.abstract_class = true
12
+
13
+ # Used when running tests
14
+ def self.create_random
15
+ create random_params
16
+ end
17
+
18
+ def self.random_params
19
+ params = {}
20
+ columns.each do |col|
21
+ next if ['id', 'created_at', 'updated_at'].include?(col.name)
22
+ params[col.name] = case col.type.to_s
23
+ when 'string'
24
+ "Some String #{SecureRandom.hex(4)}"
25
+ when 'float', 'integer'
26
+ rand(100)
27
+ when 'boolean'
28
+ [true, false].sample
29
+ end
30
+ end
31
+ params
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,10 @@
1
+ module Surikat
2
+ class BaseQueries
3
+ def initialize(arguments, session)
4
+ @arguments, @session = arguments, session
5
+ end
6
+
7
+ attr_reader :arguments
8
+ attr_reader :session
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ class BaseType
2
+
3
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveRecordMigrations
2
+ class Configurations
3
+ include Singleton
4
+
5
+ attr_accessor :yaml_config, :database_configuration, :environment, :db_dir, :migrations_paths, :schema_format, :seed_loader
6
+
7
+ def initialize
8
+ @yaml_config = 'config/database.yml'
9
+ @environment = ENV['db'] || Rails.env
10
+ @db_dir = 'db'
11
+ @migrations_paths = ['db/migrate']
12
+ @schema_format = :ruby # or :sql
13
+ @seed_loader = Rails.application
14
+ end
15
+
16
+ alias configure instance_eval
17
+
18
+ def database_configuration
19
+ @database_configuration ||= YAML.load(ERB.new(File.read @yaml_config).result)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,108 @@
1
+ module NewApp
2
+
3
+ # Generate a new Surikat app with a Hello World query in it.
4
+ def new_app(arguments)
5
+ app_name = arguments.first
6
+
7
+ if app_name.nil?
8
+ puts "Usage: surikat new app_name\nCreates a new, empty Surikat app"
9
+ return
10
+ end
11
+
12
+ # Create app directory structure
13
+ create_dirs app_name
14
+
15
+ # Create initial files
16
+ {
17
+ 'Gemfile' => {path: app_name},
18
+ 'Rakefile' => {path: app_name},
19
+ '.standalone_migrations' => {path: app_name},
20
+ '.rspec' => {path: app_name},
21
+ 'config.ru' => {path: app_name, vars: {app_name_capitalized: app_name.capitalize}},
22
+ 'hello_queries.rb' => {path: "#{app_name}/app/queries"},
23
+ 'console' => {path: "#{app_name}/bin"},
24
+ 'routes.yml' => {path: "#{app_name}/config"},
25
+ 'database.yml' => {path: "#{app_name}/config"},
26
+ 'application.yml' => {path: "#{app_name}/config"},
27
+ 'spec_helper.rb' => {path: "#{app_name}/spec"},
28
+ 'test_helper.rb' => {path: "#{app_name}/spec"},
29
+ 'hello_spec.rb' => {path: "#{app_name}/spec"}
30
+ }.each do |name, details|
31
+ print "Create #{name}... "
32
+ copy_template name, details
33
+ puts 'ok'
34
+ end
35
+
36
+ # Post-templating...
37
+ FileUtils.chmod '+x', "#{app_name}/bin/console"
38
+
39
+ `cd #{app_name} && rake db:migrate`
40
+
41
+ # Show help :)
42
+ show_help app_name
43
+ end
44
+
45
+ def create_dirs app_name
46
+ dirs = [nil, '/bin', '/app/models', '/app/queries', '/log', '/tmp/pids', '/config/initializers', '/db', '/spec']
47
+ dirs.each do |dir|
48
+ print "Create directory #{app_name}#{dir}... "
49
+ FileUtils.mkdir_p "#{app_name}#{dir}"
50
+ puts "ok"
51
+ end
52
+ end
53
+
54
+ def show_help app_name
55
+ puts <<-EOT
56
+
57
+ =================================================================
58
+ Done. What next?
59
+
60
+ cd #{app_name}
61
+ bundle install
62
+ rspec
63
+ passenger start
64
+
65
+ =================================================================
66
+ Then...
67
+ Got GraphiQL?
68
+ Open GraphiQL
69
+ Enter the URL: http://localhost:3000/
70
+ Enter the query: {Hello}
71
+
72
+ No GraphQL client yet? Simply try from any browser, or with curl:
73
+ http://localhost:3000/?query=%7BHello%7B
74
+
75
+ And then...?
76
+ Generate your own scaffold:
77
+ surikat generate scaffold Author name:string year_of_birth:integer is_any_good:boolean
78
+
79
+ Generate examples of queries for a specific query:
80
+ surikat exemplify Author get
81
+
82
+ Generate your own models:
83
+ surikat generate model Book title:string author_id:integer
84
+
85
+ Generate a User model with full support for authentication, authorization and access:
86
+ surikat generate aaa
87
+
88
+ List existing routes or GraphQL types:
89
+ surikat list routes|types
90
+
91
+ Create a migration:
92
+ rake db:new_migration name=Bookstore address:string name:string
93
+ or
94
+ surikat generate migration AddNumberToBookstore no:integer
95
+
96
+ Run migrations:
97
+ rake db:migrate
98
+
99
+ (More about migrations: https://github.com/thuss/standalone-migrations )
100
+
101
+ Run generated tests:
102
+ rspec
103
+ (or, more interesting, rspec -f d)
104
+
105
+ EOT
106
+ end
107
+
108
+ end
@@ -0,0 +1,67 @@
1
+ # This is an internal utility class used to manage the GraphQL @routes, which are stored in an object dump called 'routes'.
2
+ # The routes map connections between names of queries and mutations, and methods in the _queries modules.
3
+ #
4
+ # A route has the following format:
5
+ # { query_name => {'module' => name of query module,
6
+ # 'method' => name of method in that module,
7
+ # 'output_type' => type to cast over the result of the method},
8
+ # 'arguments' => hash of argument names and GraphQL types
9
+ # }
10
+ #
11
+ # Examples:
12
+ # 'Author' => {module: 'AuthorQueries', method: 'get', output_type: 'Author', 'arguments' => {'id' => 'ID'}},
13
+ # 'Authors' => {module: 'AuthorQueries', method: 'all', output_type: '[Author]', 'arguments' => {}}
14
+ class Routes
15
+
16
+ require 'oj'
17
+
18
+ def initialize
19
+ @filename = "#{FileUtils.pwd}/config/routes.yml"
20
+ @routes = load
21
+ end
22
+
23
+ def all
24
+ @routes
25
+ end
26
+
27
+ def merge_query route
28
+ @routes['queries'] ||= {}
29
+ @routes['queries'].merge!(route)
30
+ save
31
+ end
32
+
33
+ def merge_mutation route
34
+ @routes['mutations'] ||= {}
35
+ @routes['mutations'].merge!(route)
36
+ save
37
+ end
38
+
39
+ def delete_query query_name
40
+ raise "Query #{query_name} not found" if @routes['queries'].nil? || !(@routes['queries']&.keys&.include?(query_name))
41
+ @routes['queries'].delete query_name
42
+ save
43
+ end
44
+
45
+ def delete_mutation mutation_name
46
+ raise "Mutation #{mutation_name} not found" if @routes['mutations'].nil? || !(@routes['mutations']&.keys&.include?(mutation_name))
47
+ @routes['mutations'].delete mutation_name
48
+ save
49
+ end
50
+
51
+ def clear
52
+ @routes = {}
53
+ save
54
+ end
55
+
56
+ private
57
+ def load
58
+ return {} unless File.exists?(@filename)
59
+ YAML.load_file @filename
60
+ end
61
+
62
+ def save
63
+ File.open(@filename, 'w') { |file| file.write(@routes.to_yaml)}
64
+ end
65
+
66
+
67
+ end
@@ -0,0 +1,503 @@
1
+ module Scaffold
2
+
3
+ require 'fileutils'
4
+
5
+ def generate_routes(model_name, is_aaa: false)
6
+ print "Generating query and migration routes... "
7
+
8
+ routes = Routes.new
9
+
10
+ class_name = ActiveSupport::Inflector.camelize(model_name)
11
+
12
+ begin
13
+ # get one query
14
+ routes.merge_query({class_name => {
15
+ 'class' => "#{class_name}Queries",
16
+ 'method' => 'get',
17
+ 'output_type' => "#{class_name}",
18
+ 'arguments' => {'id' => 'ID'}
19
+ }})
20
+
21
+ # get all query
22
+ class_name_plural = ActiveSupport::Inflector.pluralize(class_name)
23
+ routes.merge_query({class_name_plural => {
24
+ 'class' => "#{class_name}Queries",
25
+ 'method' => 'all',
26
+ 'output_type' => "[#{class_name}]",
27
+ 'arguments' => {'q' => 'String'}
28
+ }})
29
+
30
+ # create mutation
31
+ routes.merge_mutation({class_name => {
32
+ 'class' => "#{class_name}Queries",
33
+ 'method' => 'create',
34
+ 'output_type' => "#{class_name}",
35
+ 'arguments' => {class_name => "#{class_name}Input"}
36
+ }})
37
+
38
+ # update mutation
39
+ routes.merge_mutation({"Update#{class_name}" => {
40
+ 'class' => "#{class_name}Queries",
41
+ 'method' => 'update',
42
+ 'output_type' => "Boolean",
43
+ 'arguments' => {class_name => "#{class_name}Input"}
44
+ }})
45
+
46
+ # delete mutation
47
+ routes.merge_mutation({"Delete#{class_name}" => {
48
+ 'class' => "#{class_name}Queries",
49
+ 'method' => 'delete',
50
+ 'output_type' => "Boolean",
51
+ 'arguments' => {'id' => 'ID'}
52
+ }})
53
+
54
+ if is_aaa # add extra routes for AAA
55
+ routes.merge_query({'Authenticate' => {
56
+ "class" => "AAAQueries",
57
+ "method" => "authenticate",
58
+ "output_type" => "Boolean",
59
+ "arguments" => {
60
+ "email" => "String",
61
+ "password" => "String"
62
+ }
63
+ }})
64
+
65
+ routes.merge_query({'Logout' => {
66
+ "class" => "AAAQueries",
67
+ "method" => "logout",
68
+ "output_type" => "Boolean",
69
+ "permitted_roles" => "any"
70
+ }})
71
+
72
+ routes.merge_query({'CurrentUser' => {
73
+ "class" => "AAAQueries",
74
+ "method" => "current_user",
75
+ "output_type" => "User",
76
+ "permitted_roles" => "any"
77
+ }})
78
+
79
+ routes.merge_query({'LoginAs' => {
80
+ "class" => "AAAQueries",
81
+ "method" => "login_as",
82
+ "output_type" => "Boolean",
83
+ "permitted_roles" => ["superadmin"],
84
+ "arguments" => {
85
+ "user_id" => "ID"
86
+ }
87
+ }})
88
+
89
+ routes.merge_query({'BackFromLoginAs' => {
90
+ "class" => "AAAQueries",
91
+ "method" => "back_from_login_as",
92
+ "output_type" => "Boolean",
93
+ "permitted_roles" => "any"
94
+ }})
95
+
96
+ routes.merge_query({'DemoOne' => {
97
+ "class" => "AAAQueries",
98
+ "method" => "demo_one",
99
+ "output_type" => "String",
100
+ "permitted_roles" => "any"
101
+ }})
102
+
103
+ routes.merge_query({'DemoTwo' => {
104
+ "class" => "AAAQueries",
105
+ "method" => "demo_two",
106
+ "output_type" => "String",
107
+ "permitted_roles" => ["hotdog", "hamburger"]
108
+ }})
109
+
110
+ routes.merge_query({'DemoThree' => {
111
+ "class" => "AAAQueries",
112
+ "method" => "demo_three",
113
+ "output_type" => "String",
114
+ "permitted_roles" => ["worker"]
115
+ }})
116
+ end # if is_aaa
117
+ rescue Exception => e
118
+ puts "fail: #{e.message}, #{e.backtrace.first}"
119
+ false
120
+ else
121
+ puts "ok"
122
+ true
123
+ end
124
+ end
125
+
126
+ def destroy_routes(model_name)
127
+ class_name = ActiveSupport::Inflector.camelize(model_name)
128
+ class_name_plural = ActiveSupport::Inflector.pluralize(class_name)
129
+
130
+ routes = Routes.new
131
+
132
+ print "Destroying query and mutation routes... "
133
+
134
+ begin
135
+ routes.delete_query class_name
136
+ routes.delete_query class_name_plural
137
+ routes.delete_mutation class_name
138
+ routes.delete_mutation "Update#{class_name}"
139
+ routes.delete_mutation "Delete#{class_name}"
140
+ rescue Exception => e
141
+ puts "fail: #{e.message}, #{e.backtrace.first}"
142
+ false
143
+ else
144
+ puts "ok"
145
+ true
146
+ end
147
+ end
148
+
149
+ def generate_queries(model_name)
150
+ var_name = ActiveSupport::Inflector.singularize(model_name.underscore)
151
+ class_name = ActiveSupport::Inflector.camelize(model_name)
152
+ class_name_plural = ActiveSupport::Inflector.pluralize(class_name)
153
+ file_name = ActiveSupport::Inflector.singularize(model_name.underscore) + '_queries.rb'
154
+ file_path = "queries/#{file_name}"
155
+ absolute_path = FileUtils.pwd + '/app/'
156
+ file_absolute_path = absolute_path + file_path
157
+
158
+ FileUtils.mkdir_p absolute_path + 'models'
159
+
160
+ print "Generating query file #{file_path}... "
161
+
162
+ if File.exists?(file_absolute_path)
163
+ puts "exists"
164
+ return true
165
+ end
166
+
167
+ all_types = Types.new.all
168
+
169
+ input_type_detailed = all_types["#{class_name}Input"]['arguments'].map do |field, type|
170
+ " '#{field}' => #{type}"
171
+ end.join("\n")
172
+
173
+ input_type_detailed_no_id = all_types["#{class_name}Input"]['arguments'].map do |field, type|
174
+ next if field == 'id'
175
+ " '#{field}' => #{type}"
176
+ end.compact.join("\n")
177
+
178
+ fields = all_types[class_name]['fields'].keys
179
+
180
+ examples = {
181
+ get: "{\n #{class_name}(id: 123) {\n" + fields.map {|f| " #{f}"}.join("\n") + "\n }\n}",
182
+ list: "{\n #{class_name_plural}(q: \"id_lt=100\") {\n" + fields.map {|f| " #{f}"}.join("\n") + "\n }\n}",
183
+ create: "mutation #{class_name}($#{var_name}: #{class_name}Input) {\n #{class_name}(#{var_name}: $#{var_name}) {\n" + fields.map {|f| " #{f}"}.join("\n") + "\n }\n}",
184
+ update: "mutation Update#{class_name}($#{var_name}: #{class_name}Input) {\n Update#{class_name}(#{var_name}: $#{var_name}) {\n" + fields.map {|f| " #{f}"}.join("\n") + "\n }\n}",
185
+ delete: "mutation Delete#{class_name}($id: ID) {\n Delete#{class_name}(id: $id)\n}"
186
+ }
187
+
188
+ update_vars = {var_name => {}}
189
+ all_types["#{class_name}Input"]['arguments'].each {|a, t| update_vars[var_name][a] = random_values(t)}
190
+
191
+ create_vars = {var_name => {}}
192
+ all_types["#{class_name}Input"]['arguments'].each {|a, t| create_vars[var_name][a] = random_values(t)}
193
+ create_vars[var_name].delete 'id'
194
+
195
+ vars = {
196
+ examples: examples,
197
+ time: Time.now.to_s,
198
+ class_name_downcase: class_name.underscore,
199
+ class_name: class_name,
200
+ class_name_plural: class_name_plural,
201
+ input_type_detailed: input_type_detailed,
202
+ pretty_update_vars: JSON.pretty_generate(update_vars),
203
+ pretty_create_vars: JSON.pretty_generate(create_vars),
204
+ pretty_random_id: JSON.pretty_generate({'id' => random_values('ID')}),
205
+ input_type_detailed_no_id: input_type_detailed_no_id,
206
+ examples_get: examples[:get],
207
+ examples_list: examples[:list],
208
+ examples_create: examples[:create],
209
+ examples_update: examples[:update],
210
+ examples_delete: examples[:delete]
211
+ }
212
+
213
+ copy_template 'crud_queries.rb', {new_name: file_name, path: 'app/queries', vars: vars}
214
+
215
+ puts "ok"
216
+ true
217
+ end
218
+
219
+ def generate_aaa_queries
220
+ file_name = 'aaa_queries.rb'
221
+ file_path = "app/queries/#{file_name}"
222
+ absolute_path = FileUtils.pwd + '/app/queries/'
223
+ file_absolute_path = absolute_path + file_path
224
+
225
+ vars = {
226
+ time: Time.now.to_s
227
+ }
228
+
229
+ print "Creating AAA queries file: #{file_name}... "
230
+
231
+ if File.exists?(file_absolute_path)
232
+ puts "exists"
233
+ return true
234
+ end
235
+
236
+ copy_template file_name, {path: 'app/queries', vars: vars}
237
+
238
+ puts "ok"
239
+ true
240
+ end
241
+
242
+ def destroy_queries(model_name)
243
+ file_name = ActiveSupport::Inflector.singularize(model_name.underscore) + '_queries.rb'
244
+ file_path = "queries/#{file_name}"
245
+
246
+ print "Deleting queries file: #{file_path}... "
247
+
248
+ begin
249
+ File.unlink FileUtils.pwd + '/app/' + file_path
250
+ rescue Exception => e
251
+ puts "fail: #{e.message}"
252
+ false
253
+ else
254
+ puts "ok"
255
+ true
256
+ end
257
+ end
258
+
259
+ def generate_types(model_name, arguments, is_aaa: false)
260
+ type_name = ActiveSupport::Inflector.camelize(model_name)
261
+ types = Types.new
262
+
263
+ fields = {}
264
+ (arguments + ['id:ID']).each do |arg|
265
+ field_name, field_type = arg.split(':').map(&:strip)
266
+
267
+ next if field_name.in?(%w(password hashed_password)) && is_aaa # never expose the hashed password
268
+
269
+ field_type = 'ID' if field_name[-3, 3] == '_id'
270
+
271
+ graphql_type = case field_type
272
+ when 'ID'
273
+ 'ID'
274
+ when 'integer'
275
+ 'Int'
276
+ when 'float'
277
+ 'Float'
278
+ when 'string', 'text'
279
+ 'String'
280
+ when 'boolean'
281
+ 'Boolean'
282
+ else
283
+ 'String'
284
+ end
285
+
286
+ fields[field_name] = graphql_type
287
+ end
288
+
289
+ print "Generating output type... "
290
+
291
+ if types.all.keys.include?(type_name)
292
+ puts "exists"
293
+ else
294
+ begin
295
+ types.merge type_name => {
296
+ 'type' => 'Output',
297
+ 'fields' => fields.clone
298
+ }
299
+ rescue Exception => e
300
+ puts "fail: #{e.message}, #{e.backtrace.first}"
301
+ return false
302
+ else
303
+ puts "ok"
304
+ end
305
+ end
306
+
307
+ print "Generating input type... "
308
+
309
+ if types.all.keys.include?("#{type_name}Input")
310
+ puts "exists"
311
+ true
312
+ else
313
+ input_fields = fields.clone
314
+
315
+ if is_aaa
316
+ input_fields.merge!({'password' => 'String'})
317
+ input_fields.delete('hashed_password')
318
+ end
319
+
320
+ begin
321
+ types.merge "#{type_name}Input" => {
322
+ 'type' => 'Input',
323
+ 'arguments' => input_fields
324
+ }
325
+ rescue Exception => e
326
+ puts "fail: #{e.message}, #{e.backtrace.first}"
327
+ false
328
+ else
329
+ puts "ok"
330
+ true
331
+ end
332
+ end
333
+ end
334
+
335
+ def destroy_types(model_name)
336
+ print "Deleting types... "
337
+
338
+ begin
339
+ type_name = ActiveSupport::Inflector.camelize(model_name)
340
+ types = Types.new
341
+
342
+ types.delete(type_name)
343
+ types.delete("#{type_name}Input")
344
+ rescue Exception => e
345
+ puts "fail: #{e.message}, #{e.backtrace.first}"
346
+ false
347
+ else
348
+ puts "ok"
349
+ true
350
+ end
351
+ end
352
+
353
+ def make_create_migration(migration_name, arguments)
354
+ print "Creating migration #{migration_name}... "
355
+
356
+ arguments << 'created_at:datetime'
357
+ arguments << 'updated_at:datetime'
358
+
359
+ begin
360
+ StandaloneMigrations::Generator.migration migration_name, arguments
361
+ rescue Exception => e
362
+ puts " error: #{e.message}"
363
+ return false
364
+ else
365
+ puts "ok"
366
+ true
367
+ end
368
+ end
369
+
370
+ def generate_model(model_name, arguments, is_aaa: false)
371
+ class_name = ActiveSupport::Inflector.camelize(model_name)
372
+ file_name = ActiveSupport::Inflector.singularize(model_name.underscore) + '.rb'
373
+ file_path = "models/#{file_name}"
374
+ absolute_path = FileUtils.pwd + '/app/'
375
+ file_absolute_path = absolute_path + file_path
376
+
377
+ return false unless make_create_migration("create_#{model_name}", arguments)
378
+
379
+ FileUtils.mkdir_p absolute_path + 'models'
380
+
381
+ print "Creating model file: #{file_name}... "
382
+
383
+ if File.exists?(file_absolute_path)
384
+ puts "exists"
385
+ return true
386
+ end
387
+
388
+ template = is_aaa ? 'base_aaa_model.rb' : 'base_model.rb'
389
+
390
+ copy_template template, {new_name: file_name, path: 'app/models', vars: {class_name: class_name}}
391
+
392
+ puts "ok"
393
+ return true
394
+ end
395
+
396
+ def generate_tests(model_name, arguments, is_aaa: false)
397
+
398
+ class_name = ActiveSupport::Inflector.camelize(model_name)
399
+ class_name_plural = ActiveSupport::Inflector.pluralize(class_name)
400
+ model_name_plural = ActiveSupport::Inflector.pluralize(model_name.underscore)
401
+ file_name = ActiveSupport::Inflector.singularize(model_name.underscore) + '_spec.rb'
402
+ file_path = "spec/#{file_name}"
403
+ absolute_path = FileUtils.pwd + '/app/'
404
+ file_absolute_path = absolute_path + file_path
405
+ columns = (arguments.to_a.map {|a| a.split(':').first} + %w(id created_at updated_at)).uniq
406
+
407
+ columns -= ['hashed_password'] if is_aaa
408
+
409
+ print "Creating rspec tests: #{file_name}... "
410
+
411
+ if File.exists?(file_absolute_path)
412
+ puts "exists"
413
+ return true
414
+ end
415
+
416
+ copy_template 'base_spec.rb', {
417
+ new_name: file_name,
418
+ path: 'spec',
419
+ vars: {class_name: class_name, class_name_plural: class_name_plural,
420
+ model_name: model_name.underscore, model_name_plural: model_name_plural,
421
+ columns_new_line: columns.join("\n "),
422
+ columns_space: columns.map {|c| "'#{c}'"}.join(', ')
423
+ }
424
+ }
425
+
426
+ puts "ok"
427
+
428
+ if is_aaa
429
+ print "Creating AAA rspec tests: spec/aaa_spec.rb... "
430
+ copy_template 'aaa_spec.rb', {path: 'spec'}
431
+ puts "ok"
432
+ end
433
+
434
+ true
435
+ end
436
+
437
+ def generate_scaffold arguments
438
+ model_name = arguments.shift
439
+ if model_name.to_s.empty?
440
+ puts "Syntax: surikat generate scaffold model_name field1:type1 field2:type2..."
441
+ return
442
+ end
443
+
444
+ unless model_name =~ /^[\p{L}_][\p{L}\p{N}@$#_]{0,127}$/
445
+ puts "'#{model_name}' does not appear to be a valid."
446
+ return
447
+ end
448
+
449
+ valid_types = %w(integer float string boolean date datetime decimal binary bigint primary_key references string text time timestamp)
450
+
451
+ arguments.each do |arg|
452
+ field_name, field_type = arg.split(':')
453
+ unless field_name =~ /^[\p{L}_][\p{L}\p{N}@$#_]{0,127}$/
454
+ puts "'#{field_name} does not appear to be valid'"
455
+ return
456
+ end
457
+ unless valid_types.include?(field_type)
458
+ puts "'#{field_type}' does not appear to be valid. Valid field types are: #{valid_types.join(', ')}"
459
+ end
460
+ end
461
+
462
+ if generate_model(model_name, arguments) &&
463
+ generate_types(model_name, arguments) &&
464
+ generate_queries(model_name) &&
465
+ generate_routes(model_name) &&
466
+ generate_tests(model_name, arguments)
467
+ puts "Done."
468
+ else
469
+ puts "Partially done."
470
+ end
471
+
472
+ end
473
+
474
+ def generate_aaa
475
+ model_name = 'user'
476
+ arguments = ['email:string', 'hashed_password:string', 'roleids:string']
477
+
478
+ if generate_model(model_name, arguments, is_aaa: true) &&
479
+ generate_types(model_name, arguments, is_aaa: true) &&
480
+ generate_queries(model_name) &&
481
+ generate_aaa_queries &&
482
+ generate_routes(model_name, is_aaa: true)
483
+ generate_tests(model_name, arguments, is_aaa: true)
484
+
485
+ puts "Done."
486
+ else
487
+ puts "Partially done."
488
+ end
489
+ end
490
+
491
+ def copy_template name, details
492
+ destination_path = details[:path]
493
+ vars = details[:vars] || {}
494
+ file = "#{__dir__}/templates/#{name}.tmpl"
495
+ dest = "#{destination_path}/#{details[:new_name] || name}"
496
+
497
+ text = File.open(file).read
498
+
499
+ File.open(dest, 'w') {|f| f.write(text % vars)}
500
+ end
501
+
502
+
503
+ end