meta-record 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []