mongify-mongoid 1.0.0
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.
- data/.gitignore +20 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +17 -0
- data/bin/mongify_mongoid +18 -0
- data/lib/mongify/mongoid.rb +16 -0
- data/lib/mongify/mongoid/cli.rb +9 -0
- data/lib/mongify/mongoid/cli/application.rb +60 -0
- data/lib/mongify/mongoid/cli/command/help.rb +22 -0
- data/lib/mongify/mongoid/cli/command/version.rb +21 -0
- data/lib/mongify/mongoid/cli/command/worker.rb +51 -0
- data/lib/mongify/mongoid/cli/options.rb +87 -0
- data/lib/mongify/mongoid/exceptions.rb +30 -0
- data/lib/mongify/mongoid/generator.rb +117 -0
- data/lib/mongify/mongoid/model.rb +137 -0
- data/lib/mongify/mongoid/model/field.rb +79 -0
- data/lib/mongify/mongoid/model/relation.rb +60 -0
- data/lib/mongify/mongoid/printer.rb +58 -0
- data/lib/mongify/mongoid/templates/mongoid.rb.erb +33 -0
- data/lib/mongify/mongoid/ui.rb +64 -0
- data/lib/mongify/mongoid/version.rb +6 -0
- data/mongify-mongoid.gemspec +34 -0
- data/spec/files/translation.rb +44 -0
- data/spec/lib/mongify/mongoid/cli/command/worker_spec.rb +45 -0
- data/spec/lib/mongify/mongoid/generator_spec.rb +123 -0
- data/spec/lib/mongify/mongoid/model/field_spec.rb +31 -0
- data/spec/lib/mongify/mongoid/model/relation_spec.rb +42 -0
- data/spec/lib/mongify/mongoid/model_spec.rb +146 -0
- data/spec/lib/mongify/mongoid/printer_spec.rb +57 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/custom_matchers.rb +47 -0
- metadata +214 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Mongoid
|
3
|
+
# Base Mongify Error
|
4
|
+
class Error < RuntimeError; end
|
5
|
+
|
6
|
+
# Not Implemented Error from Mongify
|
7
|
+
class NotImplementedError < Error; end
|
8
|
+
|
9
|
+
# Would overwrite existing file
|
10
|
+
class OverwritingFolder < Error; end
|
11
|
+
|
12
|
+
# File Not Found Exception
|
13
|
+
class FileNotFound < Error; end
|
14
|
+
|
15
|
+
# Raised when Translation file is missing
|
16
|
+
class TranslationFileNotFound < FileNotFound; end
|
17
|
+
|
18
|
+
# Raised when an invalid relation is created
|
19
|
+
class InvalidRelation < Error; end
|
20
|
+
|
21
|
+
# Raised when an invalid field is created
|
22
|
+
class InvalidField < Error; end
|
23
|
+
|
24
|
+
# Raised when application has no root folder set
|
25
|
+
class RootMissing < Error; end
|
26
|
+
|
27
|
+
# Raised when an invalid option is passed via CLI
|
28
|
+
class InvalidOption < Error; end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Mongoid
|
3
|
+
#
|
4
|
+
# Generator - Processes translation file and generates output based on models
|
5
|
+
#
|
6
|
+
class Generator
|
7
|
+
attr_reader :models
|
8
|
+
def initialize(translation_file, output_dir)
|
9
|
+
@translation_file = translation_file
|
10
|
+
@output_dir = output_dir
|
11
|
+
@models = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Process translation file and generate output files
|
15
|
+
# @return [nil]
|
16
|
+
def process
|
17
|
+
unless File.exists?(@translation_file)
|
18
|
+
raise Mongify::Mongoid::TranslationFileNotFound, "Unable to find Translation File at #{@translation_file}"
|
19
|
+
end
|
20
|
+
|
21
|
+
generate_models
|
22
|
+
process_fields
|
23
|
+
generate_embedded_relations
|
24
|
+
write_models_to_file
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generate models based on traslation tables
|
29
|
+
# @return [Array] List of tables that models were generated from
|
30
|
+
def generate_models
|
31
|
+
(translation.tables + translation.polymorphic_tables).each do |table|
|
32
|
+
build_model(table)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Goes through all the models and adds fields based on columns in the given table
|
37
|
+
# @return [Array] List of models that fields were added to
|
38
|
+
def process_fields
|
39
|
+
models.each do |key, model|
|
40
|
+
table = translation.find(model.table_name)
|
41
|
+
model = generate_fields_for model, table if table
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Goes through embedded relationships
|
46
|
+
# @return [Array] List of tables that were used to extra embedded relations
|
47
|
+
def generate_embedded_relations
|
48
|
+
translation.embed_tables.each do |table|
|
49
|
+
extract_embedded_relations(table)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Writes models to files
|
54
|
+
# @return [Printer] Printer class used to write output
|
55
|
+
def write_models_to_file
|
56
|
+
Printer.new(models, @output_dir).tap do |p|
|
57
|
+
p.write
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns model based on table name
|
62
|
+
# @param [String] name Name of table
|
63
|
+
# @return [Model, nil] Found model or nil
|
64
|
+
def find_model(name)
|
65
|
+
@models[name.to_s.downcase.to_sym]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns Mongify translation class for given translation file
|
69
|
+
# @return [Mongify::Translation] Translation file
|
70
|
+
def translation
|
71
|
+
@translation ||= Mongify::Translation.parse(@translation_file)
|
72
|
+
end
|
73
|
+
|
74
|
+
#######
|
75
|
+
private
|
76
|
+
#######
|
77
|
+
|
78
|
+
# Generates fields for given model and it's table
|
79
|
+
# @param [Model] model
|
80
|
+
# @param [Mongify::Database::Table] table
|
81
|
+
# @return [Array] List of columns from the table
|
82
|
+
def generate_fields_for(model, table)
|
83
|
+
table.columns.each do |column|
|
84
|
+
if column.options['references'] && parent_model = find_model(column.options['references'])
|
85
|
+
model.add_relation(Model::Relation::BELONGS_TO, parent_model.class_name.downcase)
|
86
|
+
#TODO: Look into if there is there a way to figure out a has_one relationship?
|
87
|
+
parent_model.add_relation(Model::Relation::HAS_MANY, model.table_name)
|
88
|
+
else
|
89
|
+
model.add_field(column.name, column.type.to_s.classify, column.options)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Extracts embedded relationships for given table
|
95
|
+
# @param [Mongify::Database::Table] table
|
96
|
+
# @return [Model] Model for the given table
|
97
|
+
def extract_embedded_relations(table)
|
98
|
+
model = find_model(table.name)
|
99
|
+
parent_model = find_model(table.embed_in)
|
100
|
+
|
101
|
+
model.add_relation(Model::Relation::EMBEDDED_IN, table.embed_in)
|
102
|
+
parent_model.add_relation((table.embedded_as_object? ? Model::Relation::EMBEDS_ONE : Model::Relation::EMBEDS_MANY), model.table_name)
|
103
|
+
model
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns build model based on a table
|
107
|
+
# @param [Mongify::Database::Table] table
|
108
|
+
def build_model(table)
|
109
|
+
model = Mongify::Mongoid::Model.new(class_name: table.name.classify, table_name: table.name)
|
110
|
+
model.polymorphic_as = table.polymorphic_as if table.polymorphic?
|
111
|
+
#TODO: Might need to check that model doesn't already exist in @models
|
112
|
+
@models[table.name.downcase.to_sym] = model
|
113
|
+
model
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "mongify/mongoid/model/field"
|
2
|
+
require "mongify/mongoid/model/relation"
|
3
|
+
|
4
|
+
module Mongify
|
5
|
+
module Mongoid
|
6
|
+
#
|
7
|
+
# Class that will be used to define a mongoid model
|
8
|
+
#
|
9
|
+
class Model
|
10
|
+
#default created at field name
|
11
|
+
CREATED_AT_FIELD = 'created_at'
|
12
|
+
#default update at field name
|
13
|
+
UPDATED_AT_FIELD = 'updated_at'
|
14
|
+
|
15
|
+
#List of fields to exclude from the model
|
16
|
+
EXCLUDED_FIELDS = [
|
17
|
+
'id',
|
18
|
+
CREATED_AT_FIELD,
|
19
|
+
UPDATED_AT_FIELD
|
20
|
+
]
|
21
|
+
attr_accessor :class_name, :table_name, :fields, :relations, :polymorphic_as
|
22
|
+
|
23
|
+
#Returns true if it has any timestamps
|
24
|
+
def has_timestamps?
|
25
|
+
has_created_at_timestamp? || has_updated_at_timestamp?
|
26
|
+
end
|
27
|
+
|
28
|
+
#Returns true both timestamps are present (created_at and updated_at)
|
29
|
+
def has_both_timestamps?
|
30
|
+
has_created_at_timestamp? && has_updated_at_timestamp?
|
31
|
+
end
|
32
|
+
|
33
|
+
#Returns true created_at timestamp exists
|
34
|
+
def has_created_at_timestamp?
|
35
|
+
!!@created_at
|
36
|
+
end
|
37
|
+
|
38
|
+
#Returns true updated_at timestamp exists
|
39
|
+
def has_updated_at_timestamp?
|
40
|
+
!!@updated_at
|
41
|
+
end
|
42
|
+
|
43
|
+
#Returns true it is a polymorphic model
|
44
|
+
def polymorphic?
|
45
|
+
!!@polymorphic_as
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(options = {})
|
49
|
+
@class_name = options[:class_name].to_s
|
50
|
+
@table_name = options[:table_name].to_s
|
51
|
+
self.polymorphic_as = options[:polymorphic_as]
|
52
|
+
|
53
|
+
@fields = {}
|
54
|
+
@relations = []
|
55
|
+
end
|
56
|
+
alias :name :class_name
|
57
|
+
|
58
|
+
# Adds a field definition to the class,
|
59
|
+
# e.g:
|
60
|
+
# add_field("field_name", "String", {rename_to: "name"})
|
61
|
+
def add_field(name, type, options={})
|
62
|
+
options.stringify_keys!
|
63
|
+
return if options['ignore'] || options['references'] || polymorphic_field?(name)
|
64
|
+
check_for_timestamp(name)
|
65
|
+
return if EXCLUDED_FIELDS.include?(name.to_s.downcase)
|
66
|
+
name = options['rename_to'] if options['rename_to'].present?
|
67
|
+
begin
|
68
|
+
@fields[name.to_sym] = Field.new(name, type, options)
|
69
|
+
rescue InvalidField => e
|
70
|
+
raise InvalidField, "Unkown field type #{type} for #{name} in #{class_name}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Adds a relationship definition to the class,
|
75
|
+
# e.g:
|
76
|
+
# add_relation("embedded_in", "users")
|
77
|
+
#
|
78
|
+
# @note Embedded relations will overpower related relations
|
79
|
+
# @return [Relation] New generated relation
|
80
|
+
def add_relation(relation_name, association, options={})
|
81
|
+
if existing = find_relation_by(association)
|
82
|
+
if relation_name =~ /^embed/
|
83
|
+
delete_relation_for association
|
84
|
+
else
|
85
|
+
return
|
86
|
+
end
|
87
|
+
end
|
88
|
+
Relation.new(relation_name.to_s, association, options).tap do |r|
|
89
|
+
@relations << r
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns binding for ERB template
|
94
|
+
# @return [Binding]
|
95
|
+
def get_binding
|
96
|
+
return binding()
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns an improved inspection output including fields and relations
|
100
|
+
# @return [String] String representation of the model
|
101
|
+
def to_s
|
102
|
+
"#<Mongify::Mongoid::Model::#{name} fields=#{@fields.keys} relations=#{@relations.map{|r| "#{r.name} :#{r.association}"}}>"
|
103
|
+
end
|
104
|
+
|
105
|
+
#######
|
106
|
+
private
|
107
|
+
#######
|
108
|
+
|
109
|
+
# Checks if given field name is a known timestamp field
|
110
|
+
# @return [nil]
|
111
|
+
def check_for_timestamp name
|
112
|
+
@created_at = true if name == CREATED_AT_FIELD
|
113
|
+
@updated_at = true if name == UPDATED_AT_FIELD
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns true if given field name follows polymorphic rules
|
118
|
+
# @return [Boolean]
|
119
|
+
def polymorphic_field? name
|
120
|
+
return unless polymorphic?
|
121
|
+
name == "#{polymorphic_as}_type" || name == "#{polymorphic_as}_id"
|
122
|
+
end
|
123
|
+
|
124
|
+
# Finds a relation by association
|
125
|
+
# @return [Relation, nil] The found relation or nil
|
126
|
+
def find_relation_by association
|
127
|
+
@relations.find{|r| r.association == association || r.association == association.singularize}
|
128
|
+
end
|
129
|
+
|
130
|
+
# Deletes given relations based on association name
|
131
|
+
# @return [nil]
|
132
|
+
def delete_relation_for association
|
133
|
+
@relations.reject!{ |r| r.association == association || r.association == association.singularize}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Mongoid
|
3
|
+
class Model
|
4
|
+
#
|
5
|
+
# Field for a Mongoid Model
|
6
|
+
#
|
7
|
+
class Field
|
8
|
+
#List of accepted types
|
9
|
+
ACCEPTED_TYPES = [
|
10
|
+
"Array",
|
11
|
+
"BigDecimal",
|
12
|
+
"Boolean",
|
13
|
+
"Date",
|
14
|
+
"DateTime",
|
15
|
+
"Float",
|
16
|
+
"Hash",
|
17
|
+
"Integer",
|
18
|
+
"Moped::BSON::ObjectId",
|
19
|
+
"Moped::BSON::Binary",
|
20
|
+
"Range",
|
21
|
+
"Regexp",
|
22
|
+
"String",
|
23
|
+
"Symbol",
|
24
|
+
"Time",
|
25
|
+
"TimeWithZone"
|
26
|
+
]
|
27
|
+
|
28
|
+
#Hash of translations for different types
|
29
|
+
TRANSLATION_TYPES = {
|
30
|
+
array: "Array",
|
31
|
+
bigdecimal: "BigDecimal",
|
32
|
+
boolean: "Boolean",
|
33
|
+
date: "Date",
|
34
|
+
datetime: "DateTime",
|
35
|
+
float: "Float",
|
36
|
+
hash: "Hash",
|
37
|
+
integer: "Integer",
|
38
|
+
objectid: "Moped::BSON::ObjectId",
|
39
|
+
binary: "Moped::BSON::Binary",
|
40
|
+
range: "Range",
|
41
|
+
regexp: "Regexp",
|
42
|
+
string: "String",
|
43
|
+
symbol: "Symbol",
|
44
|
+
time: "Time",
|
45
|
+
text: "String",
|
46
|
+
timewithzone: "TimeWithZone"
|
47
|
+
}
|
48
|
+
|
49
|
+
attr_accessor :name, :type, :options
|
50
|
+
|
51
|
+
def initialize(name, type, options={})
|
52
|
+
type = translate_type(type)
|
53
|
+
check_field_type(type)
|
54
|
+
@name = name
|
55
|
+
@type = type
|
56
|
+
@options = options
|
57
|
+
end
|
58
|
+
|
59
|
+
#######
|
60
|
+
private
|
61
|
+
#######
|
62
|
+
|
63
|
+
# Tries to find a translation for a SQL type to a Mongoid Type
|
64
|
+
# @param [String] name Name of type
|
65
|
+
# @return [String] Translated field name or the same name if no translation is found
|
66
|
+
def translate_type(name)
|
67
|
+
TRANSLATION_TYPES[name.to_s.downcase.to_sym] || name
|
68
|
+
end
|
69
|
+
|
70
|
+
# Raises InvalidField if field type is unknown
|
71
|
+
# @param [String] name Name of type
|
72
|
+
# @raise InvalidField if name is not an accepted type
|
73
|
+
def check_field_type (name)
|
74
|
+
raise InvalidField, "Unknown field type #{name}" unless ACCEPTED_TYPES.map(&:downcase).include? name.to_s.downcase
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Mongify
|
2
|
+
module Mongoid
|
3
|
+
class Model
|
4
|
+
#
|
5
|
+
# This class defines a relation for an association on a mongoid model
|
6
|
+
#
|
7
|
+
class Relation
|
8
|
+
# Embeds one relation name
|
9
|
+
EMBEDS_ONE = "embeds_one"
|
10
|
+
# Embeds many relation name
|
11
|
+
EMBEDS_MANY = "embeds_many"
|
12
|
+
# Embedded in relation name
|
13
|
+
EMBEDDED_IN = "embedded_in"
|
14
|
+
# Has one relation name
|
15
|
+
HAS_ONE = "has_one"
|
16
|
+
# Has many relation name
|
17
|
+
HAS_MANY = "has_many"
|
18
|
+
# Has and belongs to many relation name
|
19
|
+
HAS_AND_BELONGS_TO_MANY = "has_and_belongs_to_many"
|
20
|
+
# Belongs to relation name
|
21
|
+
BELONGS_TO = "belongs_to"
|
22
|
+
|
23
|
+
# Holds a list of all allowed relations
|
24
|
+
VALID_RELATIONS = [
|
25
|
+
EMBEDS_ONE,
|
26
|
+
EMBEDS_MANY,
|
27
|
+
EMBEDDED_IN,
|
28
|
+
HAS_ONE,
|
29
|
+
HAS_MANY,
|
30
|
+
HAS_AND_BELONGS_TO_MANY,
|
31
|
+
BELONGS_TO
|
32
|
+
]
|
33
|
+
|
34
|
+
# List of fields that need to be singularized
|
35
|
+
SINGULARIZE_RELATIONS = [
|
36
|
+
BELONGS_TO,
|
37
|
+
EMBEDDED_IN,
|
38
|
+
EMBEDS_ONE
|
39
|
+
]
|
40
|
+
|
41
|
+
# Valid Option key values
|
42
|
+
# currently not used
|
43
|
+
OPTION_KEYS = %w(class_name inverse_of)
|
44
|
+
|
45
|
+
attr_accessor :name, :association, :options
|
46
|
+
|
47
|
+
def initialize(name, association, options = {})
|
48
|
+
@name, @association, @options = name.to_s, association.to_s, options
|
49
|
+
unless VALID_RELATIONS.include?(@name)
|
50
|
+
raise Mongify::Mongoid::InvalidRelation, "Mongoid does not support the relation #{name} for model associations"
|
51
|
+
end
|
52
|
+
|
53
|
+
#Singularize association if belongs_to or embedded_in
|
54
|
+
self.association = association.singularize if SINGULARIZE_RELATIONS.include? name
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Mongify
|
4
|
+
module Mongoid
|
5
|
+
#
|
6
|
+
# Class that writes the models to individule mongoid model files
|
7
|
+
#
|
8
|
+
class Printer
|
9
|
+
#Template file location
|
10
|
+
TEMPLATE_FILE = File.expand_path('templates/mongoid.rb.erb', File.dirname(__FILE__))
|
11
|
+
attr_accessor :models, :output_directory
|
12
|
+
def initialize(models, output_directory)
|
13
|
+
@models = models
|
14
|
+
@output_directory = output_directory
|
15
|
+
end
|
16
|
+
|
17
|
+
# Writes models to given output directory
|
18
|
+
# @return [nil]
|
19
|
+
def write
|
20
|
+
models.each do |key, model|
|
21
|
+
write_file(model)
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
#######
|
27
|
+
private
|
28
|
+
#######
|
29
|
+
|
30
|
+
# Returns ERB template file
|
31
|
+
# @return [String] Template file content
|
32
|
+
def template
|
33
|
+
@template ||= File.read(TEMPLATE_FILE)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Writes given model to output file
|
37
|
+
# @param [Model] model Model
|
38
|
+
# @return [String] The written output
|
39
|
+
def write_file model
|
40
|
+
output = render_file model
|
41
|
+
save_file output, model.name
|
42
|
+
output
|
43
|
+
end
|
44
|
+
|
45
|
+
# Renders ERB template for given model
|
46
|
+
# @return [String] rendered template string
|
47
|
+
def render_file model
|
48
|
+
ERB.new(template, nil, '-').result(model.get_binding)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Saveds text output into a file (output_directory/model_name.rb)
|
52
|
+
# @return [File] saved file
|
53
|
+
def save_file output, file_name
|
54
|
+
File.open(File.join(output_directory, "#{file_name.downcase}.rb"), 'w') {|f| f.write(output) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|