mongify-mongoid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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