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,143 @@
1
+ require 'metarecord/model'
2
+ require 'metarecord/generator_base'
3
+
4
+ class RailsDataGenerator < GeneratorBase
5
+ class << self
6
+ def is_file_based? ; false ; end
7
+ end
8
+
9
+ def model_base_class
10
+ if defined? RAILS_RECORD_BASE
11
+ RAILS_RECORD_BASE
12
+ else
13
+ "ActiveRecord::Base"
14
+ end
15
+ end
16
+
17
+ def reset
18
+ super
19
+ @default_authorized_properties = []
20
+ end
21
+
22
+ def generate_for object
23
+ reset
24
+ indent do
25
+ _append "class #{object[:name]} < #{model_base_class}"
26
+ indent do
27
+ _append "self.abstract_class = true"
28
+ _append ""
29
+ self.instance_eval &object[:block]
30
+ _append ""
31
+ generate_default_authorized_properties
32
+ end
33
+ _append "end"
34
+ end
35
+ @src
36
+ end
37
+
38
+ def generate_default_authorized_properties
39
+ _append "class << self"
40
+ indent do
41
+ _append "def permitted_attributes"
42
+ indent do
43
+ symbols = @default_authorized_properties.collect do |v| v.to_sym.inspect end
44
+ _append "[#{symbols.join ','}]"
45
+ end
46
+ _append "end"
47
+ end
48
+ _append "end"
49
+ end
50
+
51
+ def validation type, name, data
52
+ src = "validates #{name.to_s.inspect}"
53
+ src += ", presence: true" if data[:required] == true
54
+ src += ", allow_blank: false" if data[:required] && type == "std::string"
55
+ src += ", uniqueness: true" if data[:uniqueness] == true
56
+ if !data[:min].nil? || !data[:max].nil?
57
+ src += validation_numericality name, data
58
+ end
59
+ _append src
60
+ end
61
+
62
+ def validation_numericality name, data
63
+ src = ", numericality: { "
64
+ if !data[:min].nil?
65
+ src += "greater_than_or_equal_to: #{data[:min]}"
66
+ end
67
+ if !data[:max].nil?
68
+ src += ", " if !data[:min].nil?
69
+ src += "less_than_or_equal_to: #{data[:max]}"
70
+ end
71
+ src += " }"
72
+ src
73
+ end
74
+
75
+ def visibility value
76
+ end
77
+
78
+ def resource_name name
79
+ _append "RESOURCE_NAME = #{name.to_s.inspect}"
80
+ end
81
+
82
+ def order_by name, flow = nil
83
+ src = if flow.nil?
84
+ name.to_s.inspect
85
+ else
86
+ "#{name}: :#{flow}"
87
+ end
88
+ _append "scope :default_order, -> { order(#{src}) }"
89
+ end
90
+
91
+ def property type, name, options = {}
92
+ has_custom_column_name = !options[:column].nil?
93
+ rails_name = (options[:column] || name).to_s
94
+ if type == 'DataTree'
95
+ _append "store #{rails_name.inspect}, coder: JSON"
96
+ _append "def #{name} ; self.#{rails_name} ; end" if has_custom_column_name
97
+ elsif has_custom_column_name
98
+ _append "def #{name}"
99
+ indent do _append "self.#{options[:column]}" end
100
+ _append "end"
101
+ _append "def #{name}=(value)"
102
+ indent do _append "self.#{options[:column]} = value"end
103
+ _append "end"
104
+ end
105
+ validation type, rails_name, options[:validate] unless options[:validate].nil?
106
+ @default_authorized_properties << name unless options[:read_only]
107
+ end
108
+
109
+ def has_one type, name, options = {}
110
+ db_options = options[:db] || {}
111
+ foreign_key = db_options[:column] || "#{name}_id"
112
+ _append "belongs_to #{name.to_sym.inspect},"
113
+ indent do
114
+ optional = if db_options[:null].nil? then true else db_options[:null] end
115
+ _append "class_name: #{type.to_s.inspect},"
116
+ _append "foreign_key: #{foreign_key.to_s.inspect},"
117
+ _append "optional: #{optional}"
118
+ end
119
+ end
120
+
121
+ def has_many type, name, options = {}
122
+ db_options = options[:db] || {}
123
+ if options[:joined] != false
124
+ suffix = []
125
+ suffix << "class_name: #{type.to_s.inspect}"
126
+ suffix << "dependent: #{options[:dependent].to_sym.inspect}" if options[:dependent] && options[:dependent].to_sym != :unlink
127
+ _append "has_many #{name.to_sym.inspect}, #{suffix.join ','}"
128
+ else
129
+ throw "id based has_many is not supported by the rails generator"
130
+ end
131
+ end
132
+
133
+ class << self
134
+ def extension ; ".rb" ; end
135
+
136
+ def make_file filename, data
137
+ <<RUBY
138
+ module #{METARECORD_NAMESPACE}
139
+ #{data[:bodies].join "\n"}end
140
+ RUBY
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,89 @@
1
+ require 'metarecord/generator_base'
2
+ require 'metarecord/generators/rails/migrations/table_helpers'
3
+
4
+ class RailsMigrationGenerator < GeneratorBase
5
+ include RailsTableHelpers
6
+
7
+ def should_generate_for object
8
+ false
9
+ end
10
+
11
+ def should_generate_from_manifest
12
+ true
13
+ end
14
+
15
+ def generate_manifest old_manifest, new_manifest
16
+ reset
17
+ @indent = 2
18
+ probe_additions old_manifest, new_manifest
19
+ probe_deletions old_manifest, new_manifest
20
+ @indent = 0
21
+ if @src.length > 0
22
+ make_migration
23
+ else
24
+ puts "[metarecord][rails/migration] no migrations to generate"
25
+ end
26
+ end
27
+
28
+ private
29
+ def have_column_changed? old_column, new_column
30
+ old_column["type"] != new_column["type"] || old_column["options"] != new_column["options"]
31
+ end
32
+
33
+ def probe_additions old_manifest, new_manifest
34
+ new_manifest.keys.each do |model_name|
35
+ old_table = old_manifest[model_name]
36
+ new_table = new_manifest[model_name]
37
+
38
+ # If table does not exist, create it
39
+ if old_table.nil?
40
+ create_table model_name, new_table
41
+ # If table exists, check if some of its columns have been modified
42
+ else
43
+ new_table.keys.each do |column_name|
44
+ old_column = old_table[column_name]
45
+ new_column = new_table[column_name]
46
+ # If column does not exist, create it
47
+ if old_column.nil?
48
+ create_column model_name, column_name, new_column
49
+ # If column exists, ensure none of its options have been changed
50
+ elsif have_column_changed? old_column, new_column
51
+ update_column model_name, column_name, new_column
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def probe_deletions old_manifest, new_manifest
59
+ old_manifest.keys.each do |model_name|
60
+ new_table = new_manifest[model_name]
61
+ old_table = old_manifest[model_name]
62
+
63
+ if new_table.nil?
64
+ drop_table model_name
65
+ else
66
+ old_table.keys.each do |column_name|
67
+ new_column = new_table[column_name]
68
+ drop_column model_name, column_name if new_column.nil?
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def make_migration
75
+ now = DateTime.now
76
+ timestamp = DateTime.now.strftime "%Y%m%d%H%M%S"
77
+ filepath = "db/migrate/#{timestamp}_metarecord_generator_#{timestamp}.rb"
78
+ src = <<RUBY
79
+ class MetarecordGenerator#{timestamp} < ActiveRecord::Migration[6.0]
80
+ def change
81
+ #{@src} end
82
+ end
83
+ RUBY
84
+ File.open filepath, 'w' do |f|
85
+ f.write src
86
+ end
87
+ puts "[metarecord][rails/migration] Generated migration file #{filepath}"
88
+ end
89
+ end
@@ -0,0 +1,67 @@
1
+ require 'metarecord/generators/rails/migrations/type_helpers'
2
+
3
+ module RailsTableHelpers
4
+ include RailsTypeHelpers
5
+
6
+ def create_table model_name, columns
7
+ _append "create_table :#{get_table_name model_name} do |t|"
8
+ indent do
9
+ columns.each do |name, options|
10
+ record_type = get_record_type options["type"]
11
+ if record_type.nil?
12
+ on_unsupported_type model_name, name, options["type"]
13
+ next
14
+ end
15
+ src = "t."
16
+ src += rails_type_name record_type
17
+ src += " :#{name}"
18
+ src += type_options_string record_type
19
+ src += database_options_string options
20
+ _append src
21
+ end
22
+ end
23
+ _append "end"
24
+ end
25
+
26
+ def create_column model_name, column_name, data
27
+ column_operation 'add', model_name, column_name, data
28
+ end
29
+
30
+ def update_column model_name, column_name, data
31
+ column_operation 'change', model_name, column_name, data
32
+ end
33
+
34
+ def drop_table model_name
35
+ _append "drop_table :#{get_table_name model_name}"
36
+ end
37
+
38
+ def drop_column model_name, column_name
39
+ _append "remove_column :#{get_table_name model_name}, :#{column_name}"
40
+ end
41
+
42
+ private
43
+ def get_table_name model_name
44
+ get_pluralized_name model_name.underscore
45
+ end
46
+
47
+ def database_options_string data
48
+ str = ""
49
+ data["options"].each do |key, value|
50
+ str += ", #{key}: #{value.to_json}"
51
+ end
52
+ str
53
+ end
54
+
55
+ def column_operation operation, model_name, column_name, data
56
+ record_type = get_record_type data["type"]
57
+ if record_type.nil?
58
+ on_unsupported_type model_name, column_name, data["type"]
59
+ return
60
+ end
61
+ src = "#{operation}_column :#{get_table_name model_name}, :#{column_name}"
62
+ src += ", :#{rails_type_name record_type}"
63
+ src += type_options_string record_type
64
+ src += database_options_string data
65
+ _append src
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ module RailsTypeHelpers
2
+ def get_record_type type
3
+ case type
4
+ when "ODB::id_type" then :bigint
5
+ when "char" then :tinyint
6
+ when "unsigned char" then :smallint
7
+ when "short" then :smallint
8
+ when "unsigned short" then :mediumint
9
+ when "int" then :integer
10
+ when "unsigned int" then :integer
11
+ when "long" then :bigint
12
+ when "long long" then :bigint
13
+ when "unsigned long" then :bigint
14
+ when "unsigned long long" then :bigint
15
+ when "double" then :float
16
+ when "long double" then :float
17
+ when "float" then :float
18
+ when "bool" then :boolean
19
+ when "std::string" then :string
20
+ when "std::time_t" then :timestamp
21
+ else nil
22
+ end
23
+ end
24
+
25
+ def on_unsupported_type model_name, column_name, type_name
26
+ puts "[metarecord][rails-migration] unsupported type #{type_name} for column `#{column_name}` in table `#{model_name}`"
27
+ end
28
+
29
+ def is_integer_type? type
30
+ [:tinyint, :smallint, :mediumint, :bigint, :integer].include? type
31
+ end
32
+
33
+ def rails_type_name type
34
+ if is_integer_type? type then "integer" else type.to_s end
35
+ end
36
+
37
+ def type_options_string type
38
+ if is_integer_type? type
39
+ table = { tinyint: 1, smallint: 2, mediumint: 3, integer: 4, bigint: 8 }
40
+ ", limit: #{table[type]}"
41
+ else
42
+ ""
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ require 'metarecord/generator_base'
2
+ require 'metarecord/model'
3
+ require 'json'
4
+
5
+ class ManifestGenerator < GeneratorBase
6
+ def initialize
7
+ @manifest_data = {}
8
+ end
9
+
10
+ def generate output
11
+ Model.list.each do |model|
12
+ @manifest_data[model[:name]] = @current_manifest_item = Hash.new
13
+ self.instance_eval &model[:block]
14
+ end
15
+ File.open output, 'w' do |f|
16
+ f.write JSON.pretty_generate(@manifest_data)
17
+ end
18
+ end
19
+
20
+ def property type, name, options = {}
21
+ db_options = options[:db] || Hash.new
22
+ column_name = db_options[:column] || name
23
+ db_options.delete :column
24
+ @current_manifest_item[column_name] = { type: type, options: db_options }
25
+ end
26
+
27
+ def has_one type, name, options = {}
28
+ db_options = options[:db] || Hash.new
29
+ column_name = db_options[:column] || "#{name}_id"
30
+ db_options.delete :column
31
+ @current_manifest_item[column_name] = { type: "ODB::id_type", options: db_options }
32
+ end
33
+
34
+ def has_many type, name, options = {}
35
+ if options[:joined] == false
36
+ db_options = options[:db] || Hash.new
37
+ column_name = db_options[:column] || "#{get_singular_name name}_id"
38
+ db_options.delete :column
39
+ @current_manifest_item[column_name] = { type: "INTEGER[]", options: db_options }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ METARECORD_NAMESPACE = "MetaRecord"
2
+
3
+ class Model
4
+ class << self
5
+ attr_accessor :current_file
6
+
7
+ def add name, definition = {}, &block
8
+ @list ||= []
9
+ if definition.kind_of?(Array)
10
+ definition = { classname: definition.first, hpp: definition.last }
11
+ end
12
+ definition[:classname] ||= "::#{name}"
13
+ @list << { name: name, filename: current_file, block: block,
14
+ classname: definition[:classname], header: definition[:hpp] }
15
+ end
16
+
17
+ def list
18
+ @list || []
19
+ end
20
+
21
+ def reset
22
+ @list = nil
23
+ end
24
+ end
25
+ end
26
+
27
+ class Includes
28
+ class << self
29
+ attr_accessor :list
30
+ attr_accessor :headers
31
+
32
+ def reset
33
+ list = headers = nil
34
+ end
35
+ end
36
+ end
37
+
38
+ def add_include path, options = {}
39
+ options = { include_in_header: true } if options.kind_of? TrueClass
40
+ if options[:include_in_header]
41
+ Includes.headers ||= {}
42
+ Includes.headers[Model.current_file] ||= []
43
+ Includes.headers[Model.current_file] << path
44
+ else
45
+ Includes.list ||= {}
46
+ Includes.list[Model.current_file] ||= []
47
+ Includes.list[Model.current_file] << path
48
+ end
49
+ end
50
+
51
+ def collect_includes_for filename, is_header = false
52
+ base = if is_header then Includes.headers else Includes.list end
53
+ return [] if base.nil?
54
+ pre = "#include"
55
+ pre = "# include" if is_header
56
+ base = base[filename]
57
+ ((base || []).collect {|a| "#{pre} \"#{a}\""}).uniq
58
+ end
59
+
60
+ def load_all_models input_dir
61
+ Dir["#{input_dir}/*.rb"].each do |file|
62
+ Model.current_file = file
63
+ load file
64
+ end
65
+ Model.list
66
+ end
@@ -0,0 +1,56 @@
1
+ require 'metarecord/generator_base'
2
+ require 'metarecord/manifest_generator'
3
+
4
+ module MetaRecordRunner
5
+ def manifest_path
6
+ ".metarecord-manifest.json"
7
+ end
8
+
9
+ def run_all
10
+ @generators.each do |generator|
11
+ require "metarecord/generators/#{generator.to_s}_generator"
12
+ end
13
+ `rm -Rf #{@tmpdir} && mkdir -p #{@tmpdir}`
14
+ GeneratorBase.prepare @input, @tmpdir, @base_path
15
+ GeneratorBase.odb_connection = @odb_connection || { object: "Crails::Odb::Connection", include: "crails/odb/connection.hpp" }
16
+ ManifestGenerator.new.generate "#{@tmpdir}/#{manifest_path}"
17
+ @old_manifest = JSON.parse File.read(manifest_path) rescue {}
18
+ @new_manifest = JSON.parse File.read("#{@tmpdir}/#{manifest_path}")
19
+ @generators.each do |generator|
20
+ const_name = generator.to_s.camelcase + "Generator"
21
+ klass = Kernel.const_get const_name
22
+ GeneratorBase.use klass
23
+ if klass.new.should_generate_from_manifest
24
+ klass.new.generate_manifest @old_manifest, @new_manifest
25
+ end
26
+ end
27
+ update_files
28
+ end
29
+
30
+ def update_files
31
+ `cp "#{@tmpdir}/#{manifest_path}" "#{manifest_path}"`
32
+
33
+ Dir["#{@tmpdir}/**/*"].each do |tmp_file|
34
+ next if File.directory? tmp_file
35
+ new_path = "#{@output}/#{tmp_file[@tmpdir.size + 1..tmp_file.size]}"
36
+ if (not File.exists?(new_path)) || File.read(new_path) != File.read(tmp_file)
37
+ puts "[metarecord] generated #{new_path}"
38
+ `mkdir -p '#{File.dirname new_path}'`
39
+ `cp '#{tmp_file}' '#{new_path}'`
40
+ else
41
+ puts "[metarecord] no updates required for #{new_path}"
42
+ end
43
+ end
44
+
45
+ @input.each do |input|
46
+ Dir["#{@output}/#{input}/**/*"].each do |actual_file|
47
+ next if File.directory? actual_file
48
+ tmp_path = "#{@tmpdir}/#{@output[@output.size + 1..@output.size]}"
49
+ if not File.exists?(tmp_path)
50
+ puts "[metarecord] removed #{actual_file}"
51
+ `rm '#{actual_file}'`
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: meta-record
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Martin Moro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-23 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ MetaRecord is a code generator that allows you to define your application
15
+ models using a Ruby-powered DSL, and generates various implementation for
16
+ them, for your web server, client, or mobile application.
17
+ It can generates code for the following targests: Crails, ActiveRecord,
18
+ Comet.cpp, and Aurelia.js.
19
+ email: michael@unetresgrossebite.com
20
+ executables:
21
+ - metarecord-make
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - bin/metarecord-make
26
+ - lib/guard/metarecord.rb
27
+ - lib/metarecord/generator_base.rb
28
+ - lib/metarecord/generators/aurelia_generator.rb
29
+ - lib/metarecord/generators/comet/archive_generator.rb
30
+ - lib/metarecord/generators/comet/data_generator.rb
31
+ - lib/metarecord/generators/comet/edit_generator.rb
32
+ - lib/metarecord/generators/crails/data_generator.rb
33
+ - lib/metarecord/generators/crails/destroy_generator.rb
34
+ - lib/metarecord/generators/crails/edit_generator.rb
35
+ - lib/metarecord/generators/crails/helpers/validations.rb
36
+ - lib/metarecord/generators/crails/query_generator.rb
37
+ - lib/metarecord/generators/crails/view_generator.rb
38
+ - lib/metarecord/generators/rails/data_generator.rb
39
+ - lib/metarecord/generators/rails/migration_generator.rb
40
+ - lib/metarecord/generators/rails/migrations/table_helpers.rb
41
+ - lib/metarecord/generators/rails/migrations/type_helpers.rb
42
+ - lib/metarecord/manifest_generator.rb
43
+ - lib/metarecord/model.rb
44
+ - lib/metarecord/runner.rb
45
+ homepage: https://github.com/crails-framework/meta-record
46
+ licenses:
47
+ - BSD
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.3.7
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: MetaRecord is a database code generator from Crails Framework
68
+ test_files: []