enterprise_mti 0.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +32 -0
  4. data/lib/enterprise_mti.rb +9 -0
  5. data/lib/enterprise_mti/class_methods.rb +1 -0
  6. data/lib/enterprise_mti/class_methods/class_methods.rb +175 -0
  7. data/lib/enterprise_mti/migration.rb +1 -0
  8. data/lib/enterprise_mti/migration/migration.rb +34 -0
  9. data/lib/enterprise_mti/migration/sql_factory.rb +2 -0
  10. data/lib/enterprise_mti/migration/sql_factory/postgres_sql_factory.rb +16 -0
  11. data/lib/enterprise_mti/migration/sql_factory/sql_factory.rb +132 -0
  12. data/lib/enterprise_mti/version.rb +3 -0
  13. data/lib/tasks/enterprise_mti_tasks.rake +4 -0
  14. data/spec/class_methods_spec.rb +72 -0
  15. data/spec/dummy/README.rdoc +28 -0
  16. data/spec/dummy/Rakefile +6 -0
  17. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  18. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  19. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  20. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  21. data/spec/dummy/app/models/container.rb +3 -0
  22. data/spec/dummy/app/models/superthing.rb +3 -0
  23. data/spec/dummy/app/models/superthing_subclasses/subthing_one.rb +2 -0
  24. data/spec/dummy/app/models/superthing_subclasses/subthing_two.rb +2 -0
  25. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/spec/dummy/bin/bundle +3 -0
  27. data/spec/dummy/bin/rails +4 -0
  28. data/spec/dummy/bin/rake +4 -0
  29. data/spec/dummy/config.ru +4 -0
  30. data/spec/dummy/config/application.rb +23 -0
  31. data/spec/dummy/config/boot.rb +5 -0
  32. data/spec/dummy/config/database.yml +23 -0
  33. data/spec/dummy/config/environment.rb +5 -0
  34. data/spec/dummy/config/environments/development.rb +29 -0
  35. data/spec/dummy/config/environments/production.rb +80 -0
  36. data/spec/dummy/config/environments/test.rb +36 -0
  37. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  39. data/spec/dummy/config/initializers/inflections.rb +16 -0
  40. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  41. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  42. data/spec/dummy/config/initializers/session_store.rb +3 -0
  43. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  44. data/spec/dummy/config/locales/en.yml +23 -0
  45. data/spec/dummy/config/routes.rb +56 -0
  46. data/spec/dummy/db/migrate/20131029213145_create_superthings.rb +9 -0
  47. data/spec/dummy/db/migrate/20131029213154_create_subthing_ones.rb +9 -0
  48. data/spec/dummy/db/migrate/20131029213203_create_subthing_twos.rb +9 -0
  49. data/spec/dummy/db/migrate/20131029213251_start_enterprise_mti.rb +9 -0
  50. data/spec/dummy/db/migrate/20131031131410_create_containers.rb +9 -0
  51. data/spec/dummy/db/schema.rb +42 -0
  52. data/spec/dummy/log/development.log +35 -0
  53. data/spec/dummy/log/test.log +1683 -0
  54. data/spec/dummy/public/404.html +58 -0
  55. data/spec/dummy/public/422.html +58 -0
  56. data/spec/dummy/public/500.html +57 -0
  57. data/spec/dummy/public/favicon.ico +0 -0
  58. data/spec/dummy/spec/models/container_spec.rb +5 -0
  59. data/spec/dummy/spec/models/subthing_one_spec.rb +5 -0
  60. data/spec/dummy/spec/models/subthing_two_spec.rb +5 -0
  61. data/spec/dummy/spec/models/superthing_spec.rb +5 -0
  62. data/spec/helpers/class_methods_spec_helpers.rb +5 -0
  63. data/spec/helpers/migration_spec_helpers.rb +42 -0
  64. data/spec/migration_spec.rb +74 -0
  65. data/spec/spec_helper.rb +24 -0
  66. metadata +220 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a2b1b8964e6ee113a1a9e254983691b9c26cbcdc
4
+ data.tar.gz: a14f2076d338fc4ee3833c179870e439ef623659
5
+ SHA512:
6
+ metadata.gz: b0d8aa4f7ca07d83f93e4ce7b2920a54d83a1569b7784947ab3820c58a532dba43f217195496bb1d8baf1bfe7765e7e1e354db37cc5e75c9d5562f7d4c874edc
7
+ data.tar.gz: d9c434e75b93e4fbef5f48956e981f18c7512c08af68317e7f112cc2ede9653962f349de4ef75cbcd719d74d4fc839671733a136b7e0b57ec171bacdd9c715de
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Ersin Akinci
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'EnterpriseMti'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,9 @@
1
+ require 'enterprise_mti/class_methods'
2
+ require 'enterprise_mti/migration'
3
+
4
+ module EnterpriseMti
5
+ extend ActiveSupport::Concern
6
+ end
7
+
8
+ ActiveRecord::Base.send :include, ::EnterpriseMti
9
+ ActiveRecord::Migration.send :include, ::EnterpriseMti::Migration
@@ -0,0 +1 @@
1
+ require_relative 'class_methods/class_methods'
@@ -0,0 +1,175 @@
1
+ module EnterpriseMti
2
+ module ClassMethods
3
+ def has_subclass(subclass_symbol, options={})
4
+
5
+ subclass_name = subclass_symbol.to_s.camelize
6
+ subclass = nil
7
+ subclass_qualified_name = (options[:module] || '') + '::' + subclass_name
8
+ superclass = self
9
+ superclass_name = self.name.demodulize.underscore
10
+ superclass_symbol = self.name.demodulize.underscore.to_sym
11
+ table_name = options[:table_name] || subclass_symbol.to_s.pluralize
12
+
13
+ # Get subclass
14
+ Kernel.const_get(options[:module] || 'Kernel').module_eval {
15
+ subclass = const_get subclass_name }
16
+
17
+ subclass.class_eval do
18
+ # Set table name on subclass to prevent it from inheriting
19
+ # superclass' attributes, etc.
20
+ self.table_name = table_name
21
+ has_one superclass_symbol
22
+ attr_accessor :superclass_instance
23
+ validates :superclass_instance, presence: true
24
+
25
+ define_method "#{superclass_name}_transaction" do
26
+ superclass_instance.send "#{subclass_symbol.to_s}_id=", self.id
27
+ superclass_instance.save
28
+ end
29
+ after_save "#{superclass_name}_transaction".to_sym, on: :create
30
+ end
31
+
32
+ # Superclass belongs to subclass
33
+ self.belongs_to subclass_symbol, :class_name => subclass_qualified_name
34
+ end
35
+
36
+ def is_a_superclass
37
+
38
+ # Require subclasses' files as dependencies, otherwise we get circular
39
+ # dependency errors (see https://github.com/rails/rails/issues/3364)
40
+ if descendants.empty?
41
+ module_path_tokens = self.name.underscore.split('/')
42
+ module_path_tokens[-1] += '_subclasses'
43
+ Dir[Rails.root.join('app', 'models', File.join(module_path_tokens), "*.rb")].each do |file|
44
+ require_dependency file
45
+ end
46
+ end
47
+
48
+ # Call has_sublcass on the superclass for each subclass
49
+ descendants.each do |subclass|
50
+ name_tokens = subclass.to_s.split('::')
51
+ subclass_name = name_tokens.last
52
+ module_name = name_tokens[0...-1].join('::') if name_tokens.count > 1
53
+ self.send :has_subclass, subclass_name.underscore.to_sym, :module => module_name
54
+ end
55
+
56
+ # Populate container_classes, an array that contains classes that "have"
57
+ # the superclass (and thus have foreign key constraints on the
58
+ # superclass)
59
+ reflection_class = Proc.new do |r|
60
+ if r.options[:class_name]
61
+ r.options[:class_name].constantize
62
+ else
63
+ prefix = self.name.deconstantize || ''
64
+ "#{prefix}::#{r.name.to_s.camelize}".constantize
65
+ end
66
+ end
67
+
68
+ @container_classes = self.reflect_on_all_associations.keep_if { |r|
69
+ r.macro == :belongs_to &&
70
+ #self.column_names.include?(r.association_foreign_key) &&
71
+ !self.descendants.include?(reflection_class.call(r))
72
+ }.collect { |r| reflection_class.call(r) }
73
+
74
+ # Add read-only access to container_classes
75
+ class << self
76
+ def container_classes
77
+ @container_classes
78
+ end
79
+ end
80
+ end
81
+
82
+ def has_one_superclass(superclass_symbol, options={})
83
+
84
+ superclass_name = superclass_symbol.to_s.camelize
85
+ superclass_qualified_name = (options[:module] || '') + '::' + superclass_name
86
+ superclass = superclass_qualified_name.constantize
87
+ container_class = self
88
+ container_class_camel_name = self.name.demodulize
89
+ container_class_underscore_name = self.name.demodulize.underscore
90
+ superclass_relation_name = superclass_symbol.to_s + "_superclass"
91
+ superclass_relation_symbol = superclass_relation_name.to_sym
92
+
93
+ has_one superclass_relation_symbol, class_name: superclass_qualified_name
94
+
95
+ # Create build and create methods for each subclass
96
+ superclass.descendants.each do |subclass|
97
+ subclass_underscore_name = subclass.name.demodulize.underscore
98
+ actions = [:build, :create, :create!]
99
+
100
+ actions.each do |action|
101
+ action_suffix = nil
102
+ action_name = action.to_s
103
+
104
+ if action.to_s.last(1) == '!'
105
+ action_suffix = '!'
106
+ action_name = action.to_s[0..-2]
107
+ end
108
+
109
+ subclass.define_singleton_method "#{action_name}_with_superclass_instance#{action_suffix}" do |*args, &block|
110
+ self.send("#{action_name}#{action_suffix}", *args, &block).superclass_instance = args.last[:superclass_instance]
111
+ end
112
+
113
+ superclass.class_eval do
114
+
115
+ define_method "#{action_name}_#{subclass_underscore_name}_subclass#{action_suffix}" do |*args, &block|
116
+ args.last[:superclass_instance] = self
117
+ subclass.send "#{action_name}_with_superclass_instance#{action_suffix}", *args, &block
118
+ end
119
+
120
+ define_method "#{subclass_underscore_name}_with_superclass_instance=" do |value|
121
+ value.superclass_instance = self
122
+ self.send "#{subclass_underscore_name}=", value
123
+ end
124
+ end
125
+
126
+ container_class.class_eval do
127
+
128
+ define_method "#{action_name}_#{subclass_underscore_name}#{action_suffix}" do |*args, &block|
129
+ unless superclass_instance = self.send(superclass_relation_symbol)
130
+ superclass_instance = self.send("build_#{superclass_relation_name}")
131
+ end
132
+ superclass_instance.instance_eval do
133
+ send "#{action_name}_#{subclass_underscore_name}_subclass#{action_suffix}", *args, &block
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # Create getter
141
+ define_method superclass_symbol do
142
+
143
+ association_methods = superclass.descendants.collect { |subclass|
144
+ reflection_symbol =
145
+ subclass.to_s.demodulize.underscore.to_sym
146
+ assoc = superclass.reflect_on_association(reflection_symbol)
147
+ assoc ? assoc.name : nil
148
+ }.compact
149
+
150
+ if superclass_model_instance = self.send(superclass_relation_symbol)
151
+ association_methods.collect{ |a|
152
+ superclass_model_instance.send a
153
+ }.inject do |a, b|
154
+ a || b
155
+ end
156
+ end
157
+ end
158
+
159
+ # Create setter
160
+ define_method "#{superclass_symbol.to_s}=" do |value|
161
+ reflection_symbol =
162
+ value.class.name.demodulize.underscore.to_sym
163
+
164
+ unless superclass_instance = self.send(superclass_relation_symbol)
165
+ superclass_instance = self.send("build_#{superclass_relation_name}")
166
+ end
167
+
168
+ reflection_assignment_method =
169
+ superclass.reflect_on_association(reflection_symbol).name.to_s + '_with_superclass_instance='
170
+
171
+ superclass_instance.send reflection_assignment_method, value
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1 @@
1
+ require_relative 'migration/migration'
@@ -0,0 +1,34 @@
1
+ require_relative 'sql_factory'
2
+
3
+ module EnterpriseMti
4
+ module Migration
5
+
6
+ def enterprise_mti_run(opts={})
7
+ if opts[:superclass_table] && opts[:subclass_tables] && opts[:direction]
8
+
9
+ case Rails.configuration.database_configuration[Rails.env]['adapter']
10
+ when 'postgresql'
11
+ sql_factory = SqlFactory::PostgresSqlFactory.new
12
+ end
13
+
14
+ sql_factory.superclass_table = opts[:superclass_table]
15
+ sql_factory.subclass_tables = opts[:subclass_tables]
16
+
17
+ sql = sql_factory.sql_for_up if opts[:direction] == :up
18
+ sql = sql_factory.sql_for_down if opts[:direction] == :down
19
+
20
+ execute sql
21
+ end
22
+ end
23
+
24
+ def enterprise_mti_up(opts={})
25
+ enterprise_mti_run opts.merge!(direction: :up)
26
+ end
27
+
28
+ def enterprise_mti_down(opts={})
29
+ enterprise_mti_run opts.merge!(direction: :down)
30
+ end
31
+ end
32
+ end
33
+
34
+ ActiveRecord::Migration.send :include, EnterpriseMti::Migration
@@ -0,0 +1,2 @@
1
+ require_relative 'sql_factory/sql_factory'
2
+ require_relative 'sql_factory/postgres_sql_factory'
@@ -0,0 +1,16 @@
1
+ module EnterpriseMti
2
+ module Migration
3
+ module SqlFactory
4
+
5
+ class PostgresSqlFactory < SqlFactory
6
+ def integer
7
+ "INTEGER"
8
+ end
9
+
10
+ def id_type
11
+ "BIGINT"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,132 @@
1
+ module EnterpriseMti
2
+ module Migration
3
+ module SqlFactory
4
+
5
+ class SqlFactory
6
+ attr_accessor :superclass_table, :subclass_tables
7
+
8
+ def sql_for_up
9
+ [superclass_table_up, subclass_tables_up].join
10
+ end
11
+
12
+ def sql_for_down
13
+ [subclass_tables_down, superclass_table_down].join
14
+ end
15
+
16
+ ## Up methods (superclass) ##
17
+
18
+ def superclass_table_up
19
+ [superclass_table_foreign_keys_up, superclass_table_xor_constraint_up]
20
+ end
21
+
22
+ def superclass_table_foreign_keys_up
23
+ subclass_tables.map { |subclass_table|
24
+ alter_table superclass_table do
25
+ add_column "#{subclass_table}_id",
26
+ type: id_type,
27
+ nullable: false,
28
+ unique: true,
29
+ references: { table: subclass_table, column: "id" },
30
+ defer: true
31
+ end
32
+ }
33
+ end
34
+
35
+ def superclass_table_xor_constraint_up
36
+ alter_table superclass_table do
37
+ add_constraint "#{superclass_table}_xor", type: :check do
38
+ subclass_tables.map { |subclass_table|
39
+ "(#{subclass_table}_id IS #{not_null})::#{integer}"
40
+ }.join(' + ') << ' = 1'
41
+ end
42
+ end
43
+ end
44
+
45
+ ## Up methods (subclass) ##
46
+
47
+ def subclass_tables_up
48
+ subclass_tables_id_alterations_up
49
+ end
50
+
51
+ def subclass_tables_id_alterations_up
52
+ subclass_tables.map { |subclass_table|
53
+ alter_table subclass_table do
54
+ add_constraint "id_fkey", type: :foreign_key, column: 'id' do
55
+ options_parser references: { table: superclass_table, column: "#{subclass_table}_id" }, defer: true
56
+ end
57
+ end
58
+ }
59
+ end
60
+
61
+ ## Down methods (superclass) ##
62
+
63
+ def superclass_table_down
64
+ [superclass_table_xor_constraint_down, superclass_table_foreign_keys_down]
65
+ end
66
+
67
+ def superclass_table_xor_constraint_down
68
+ alter_table superclass_table do
69
+ drop_constraint "#{superclass_table}_xor"
70
+ end
71
+ end
72
+
73
+ def superclass_table_foreign_keys_down
74
+ subclass_tables.map do |subclass_table|
75
+ alter_table superclass_table do
76
+ drop_column("#{subclass_table}_id")
77
+ end
78
+ end
79
+ end
80
+
81
+ ## Down methods (subclass) ##
82
+
83
+ def subclass_tables_down
84
+ subclass_tables_id_alterations_down
85
+ end
86
+
87
+ def subclass_tables_id_alterations_down
88
+ subclass_tables.map do |subclass_table|
89
+ alter_table subclass_table do
90
+ drop_constraint 'id_fkey'
91
+ end
92
+ end
93
+ end
94
+
95
+ ## DSL ##
96
+
97
+ def options_parser(opts={})
98
+ sql = []
99
+ sql.push opts[:type]
100
+ sql.push not_null if opts[:nullable] == false
101
+ sql.push unique if opts[:unique]
102
+ sql.push references table: opts[:references][:table], column: opts[:references][:column] if opts[:references]
103
+ sql.push defer if opts[:defer]
104
+ sql.join(' ')
105
+ end
106
+ def alter_table(table); "ALTER TABLE #{table} #{yield};"; end
107
+ def add_column(column, opts={})
108
+ "ADD COLUMN #{column} #{options_parser opts}"
109
+ end
110
+ def alter_column(column, opts={})
111
+ "ALTER COLUMN #{column} SET #{options_parser opts}"
112
+ end
113
+ def drop_column(column); "DROP COLUMN #{column}"; end
114
+ def not_null; "NOT NULL"; end
115
+ def unique; "UNIQUE"; end
116
+ def references(opts={}); "REFERENCES #{opts[:table]}(#{opts[:column]})"; end
117
+ def add_constraint(name, opts={})
118
+ case opts[:type]
119
+ when :check
120
+ type = 'CHECK ('
121
+ suffix = ')'
122
+ when :foreign_key
123
+ type = "FOREIGN KEY(#{opts[:column]})"
124
+ end
125
+ "ADD CONSTRAINT #{name} #{type} #{yield} #{suffix}"
126
+ end
127
+ def drop_constraint(name); "DROP CONSTRAINT #{name}"; end
128
+ def defer; "DEFERRABLE INITIALLY DEFERRED"; end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,3 @@
1
+ module EnterpriseMti
2
+ VERSION = "0.0.1"
3
+ end