data_plan 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Pavel Vasev
2
+ Copyright (c) 2007 PJ Hyett
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,57 @@
1
+ == Data Plan
2
+
3
+ A tool for easy migration generation. Works with Sinatra/ActiveRecord and with Rails too.
4
+
5
+ == Usage
6
+
7
+ Before use in Sinatra, install 'sinatra-activerecord' gem.
8
+ It extends Sinatra with extension methods and Rake tasks for dealing with an SQL database using the ActiveRecord ORM.
9
+ Follow it's instructions and create a Rakefile with migration commands.
10
+ After that you can invoke 'rake db:create_migration' and get migrations.
11
+
12
+ And then, magic begins.
13
+ * Add to Rakefile:
14
+ require 'data_plan/generators/migration/sinatra'
15
+ * Create db/plan.rb file and describe desired db schema in it.
16
+
17
+ Now, when you create a migration, DataPlan will generate a new migration and put all differencies
18
+ between plan.rb and your current database schema! You can migrate immediately, or add some
19
+ more tweaks to your migration, or something else.
20
+
21
+ Why is it great? Because you don't need to remember all that syntax like
22
+ change_column, add_column, etc - you just modify your schema (in the plan.rb).
23
+
24
+ Nice bonus: migrations are automatically named (of course you may override name suggested).
25
+
26
+ == How to use with Rails
27
+ Copy from the gem the folder
28
+ GEMROOT/lib/data_plan/generators/migration/*
29
+ to the Rails project as follows:
30
+ YOURAPPROOT/lib/generators/migration/*
31
+ Thats it, Data Plan now in your rails app, and you can invoke 'script/generate migration'
32
+ and get migrations auto-generated as described above.
33
+
34
+ == How to install
35
+
36
+ gem install data_plan
37
+
38
+ == Todo
39
+
40
+ Indices are partially supported at the moment.
41
+
42
+ == Changelog
43
+
44
+ Workaround on views. Thanks to Cyril Boswell.
45
+
46
+ == Legal
47
+
48
+ Found a bug? Fix it and email pull request to me: pavel.vasev@gmail.com
49
+
50
+ Want to add a feature? Implement it and email the patch!
51
+
52
+ Pavel Vasev [ pavel.vasev@gmail.com ]
53
+
54
+
55
+ Released under the MIT license (included)
56
+
57
+ p.s. Schema-Definition plugin is hugely based on Auto-Migrations by PJ Hyett [ pjhyett@gmail.com ]
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -0,0 +1,216 @@
1
+ module AutoMigrations
2
+
3
+ def self.run
4
+ # Turn off schema_info code for auto-migration
5
+ class << ActiveRecord::Schema
6
+ alias :old_define :define
7
+ def define(info={}, &block) instance_eval(&block) end
8
+ end
9
+
10
+ load(File.join(DB_PROJECT_ROOT, 'db', 'plan.rb'))
11
+ ActiveRecord::Migration.drop_unused_tables
12
+ ActiveRecord::Migration.drop_unused_views
13
+ ActiveRecord::Migration.drop_unused_indexes
14
+
15
+ class << ActiveRecord::Schema
16
+ alias :define :old_define
17
+ end
18
+ end
19
+
20
+ def self.schema_to_migration
21
+ schema = File.read(File.join(DB_PROJECT_ROOT, "db", "plan.rb")) rescue begin
22
+ puts "Please copy your schema.rb file to plan.rb before generating migrations!"
23
+ raise
24
+ end
25
+ schema.gsub!(/#(.)+\n/, '')
26
+ schema.sub!(/ActiveRecord::Schema.define(.+)do[ ]?\n/, '')
27
+ schema.gsub!(/^/, ' ')
28
+ schema = "class InitialSchema < ActiveRecord::Migration\n def self.up\n" + schema
29
+ schema << "\n def self.down\n"
30
+ schema << (ActiveRecord::Base.connection.tables - ["schema_info"]).map do |table|
31
+ " drop_table :#{table}\n"
32
+ end.join
33
+ schema << " end\nend\n"
34
+ migration_file = File.join(DB_PROJECT_ROOT, "db", "migrate", "001_initial_schema.rb")
35
+ File.open(migration_file, "w") { |f| f << schema }
36
+ puts "Migration created at db/migrate/001_initial_schema.rb"
37
+ end
38
+
39
+ def self.included(base)
40
+ base.extend ClassMethods
41
+ class << base
42
+ cattr_accessor :tables_in_schema, :indexes_in_schema, :views_in_schema
43
+ self.tables_in_schema, self.indexes_in_schema, self.views_in_schema = [], [], []
44
+ alias_method_chain :method_missing, :auto_migration
45
+ end
46
+ end
47
+
48
+ module ClassMethods
49
+
50
+ def method_missing_with_auto_migration(method, *args, &block)
51
+ case method
52
+ when :create_table
53
+ auto_create_table(method, *args, &block)
54
+ when :create_view
55
+ auto_create_view(method, *args, &block)
56
+ when :add_index
57
+ auto_add_index(method, *args, &block)
58
+ else
59
+ method_missing_without_auto_migration(method, *args, &block)
60
+ end
61
+ end
62
+
63
+ def auto_create_table(method, *args, &block)
64
+ table_name = args.shift.to_s
65
+ options = args.pop || {}
66
+
67
+ (self.tables_in_schema ||= []) << table_name
68
+
69
+ # Table doesn't exist, create it
70
+ unless ActiveRecord::Base.connection.tables.include?(table_name)
71
+ return method_missing_without_auto_migration(method, *[table_name, options], &block)
72
+ end
73
+
74
+ # Grab database columns
75
+ fields_in_db = ActiveRecord::Base.connection.columns(table_name).inject({}) do |hash, column|
76
+ hash[column.name] = column
77
+ hash
78
+ end
79
+
80
+ # Grab schema columns (lifted from active_record/connection_adapters/abstract/schema_statements.rb)
81
+ table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(ActiveRecord::Base.connection)
82
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
83
+ yield table_definition
84
+ fields_in_schema = table_definition.columns.inject({}) do |hash, column|
85
+ hash[column.name.to_s] = column
86
+ hash
87
+ end
88
+
89
+ # Add fields to db new to schema
90
+ (fields_in_schema.keys - fields_in_db.keys).each do |field|
91
+ column = fields_in_schema[field]
92
+ add_column table_name, column.name, column.type.to_sym, :limit => column.limit, :precision => column.precision,
93
+ :scale => column.scale, :default => column.default, :null => column.null
94
+ end
95
+
96
+ # Remove fields from db no longer in schema
97
+ (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
98
+ column = fields_in_db[field]
99
+ remove_column table_name, column.name
100
+ end
101
+
102
+ # Change field type if schema is different from db
103
+ (fields_in_schema.keys & fields_in_db.keys).each do |field|
104
+ # TYPE
105
+ if (field != 'id') && (fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym)
106
+ change_column table_name, fields_in_schema[field].name.to_sym, fields_in_schema[field].type.to_sym
107
+ end
108
+ # DEFAULT VALUE
109
+ if (field != 'id') && (fields_in_schema[field].default != fields_in_db[field].default)
110
+ change_column_default table_name, fields_in_schema[field].name.to_sym, fields_in_schema[field].default
111
+ end
112
+ end
113
+ end
114
+
115
+ def auto_create_view(method, *args, &block)
116
+ # we are working on it with Cyril Boswell
117
+ return true
118
+ # so when we'll find an idea, we'll remove this return true.
119
+ view_name = args.shift.to_s
120
+ select_query = args.pop
121
+ options = args.pop || {}
122
+
123
+ (self.views_in_schema ||= []) << view_name
124
+
125
+ # View doesn't exist, create it
126
+ unless ActiveRecord::Base.connection.views.include?(view_name)
127
+ return method_missing_without_auto_migration(method, *[view_name, options], &block)
128
+ end
129
+
130
+ # Grab database view select
131
+ db_select = ActiveRecord::Base.connection.view_select_statement( view_name )
132
+
133
+ # Grab database columns
134
+ fields_in_db = ActiveRecord::Base.connection.columns(view_name).inject({}) do |hash, column|
135
+ hash[column.name] = column
136
+ hash
137
+ end
138
+
139
+ # Grab schema columns (lifted from active_record/connection_adapters/abstract/schema_statements.rb)
140
+ view_definition = RailsSqlViews::ConnectionAdapters::ViewDefinition.new(ActiveRecord::Base.connection,select_query)
141
+
142
+ yield view_definition
143
+
144
+ fields_in_schema = view_definition.columns.inject({}) do |hash, column|
145
+ hash[column.to_s] = column
146
+ hash
147
+ end
148
+
149
+ not_same = false
150
+ # Add fields to db new to schema
151
+ (fields_in_schema.keys - fields_in_db.keys).each do |field|
152
+ not_same = true
153
+ # column = fields_in_schema[field]
154
+ # add_column view_name, column
155
+ end
156
+
157
+
158
+ # Remove fields from db no longer in schema
159
+ (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
160
+ not_same = true
161
+ # column = fields_in_db[field]
162
+ # remove_column view_name, column.name
163
+ end
164
+
165
+ puts "DB_SELECT="+db_select
166
+ puts "QUERY="+select_query
167
+ puts "NOT SAME="+not_same.to_s
168
+ if not_same or (db_select != select_query)
169
+ drop_view view_name
170
+ return method_missing_without_auto_migration(method, *[view_name, options], &block)
171
+ end
172
+
173
+ end
174
+
175
+
176
+ def auto_add_index(method, *args, &block)
177
+ table_name = args.shift.to_s
178
+ fields = Array(args.shift).map(&:to_s)
179
+ options = args.shift
180
+
181
+ index_name = options[:name] if options
182
+ index_name ||= "index_#{table_name}_on_#{fields.join('_and_')}"
183
+
184
+ (self.indexes_in_schema ||= []) << index_name
185
+
186
+ unless ActiveRecord::Base.connection.indexes(table_name).detect { |i| i.name == index_name }
187
+ method_missing_without_auto_migration(method, *[table_name, fields, options], &block)
188
+ end
189
+ end
190
+
191
+ def drop_unused_tables
192
+ (ActiveRecord::Base.connection.tables - tables_in_schema - ["schema_info"]).each do |table|
193
+ drop_table table if table != "schema_migrations"
194
+ end
195
+ end
196
+
197
+ def drop_unused_views
198
+ # temporary
199
+ return true
200
+ (ActiveRecord::Base.connection.views - views_in_schema - ["schema_info"]).each do |view|
201
+ drop_view view
202
+ end
203
+ end
204
+
205
+ def drop_unused_indexes
206
+ tables_in_schema.each do |table_name|
207
+ indexes_in_db = ActiveRecord::Base.connection.indexes(table_name).map(&:name) rescue next
208
+ (indexes_in_db - indexes_in_schema & indexes_in_db).each do |index_name|
209
+ remove_index table_name, :name => index_name
210
+ end
211
+ end
212
+ end
213
+
214
+ end
215
+
216
+ end
@@ -0,0 +1,58 @@
1
+ module SexyStatementsHook
2
+
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ class << base
6
+ cattr_accessor :sexy_up, :sexy_down, :desired_migration_name
7
+ self.sexy_up, self.sexy_down, self.desired_migration_name = "","",""
8
+ alias_method_chain :method_missing, :sexy_statements
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def add_to_migration_name(what)
15
+ if self.desired_migration_name.length < 100
16
+ self.desired_migration_name << "_" unless self.desired_migration_name.blank?
17
+ self.desired_migration_name << what
18
+ else
19
+ self.desired_migration_name << "and_more" unless self.desired_migration_name.index("and_more")
20
+ end
21
+ end
22
+
23
+ def method_missing_with_sexy_statements(method, *args, &block)
24
+ case method
25
+ when :create_table, :create_view
26
+ table_name = args.shift.to_s
27
+ lines = IO.readlines(File.join(DB_PROJECT_ROOT, 'db', 'plan.rb'))
28
+ inside_table = false
29
+ self.sexy_up << "\n"
30
+ for line in lines do
31
+ clean_line = line.gsub(/#.*/,"")
32
+ if clean_line =~ /create_(table|view)\W+#{table_name}(\W+|$)/i
33
+ inside_table = true
34
+ end
35
+ self.sexy_up << " " + line if inside_table
36
+ if inside_table and (clean_line =~ /\send\s*/i || clean_line =~ /^end\s*/i)
37
+ break
38
+ end
39
+ end
40
+
41
+ add_to_migration_name( "create_#{table_name}" )
42
+
43
+ else
44
+ add_to_migration_name( method.to_s.split('_')[0] + ("_"+args[0].to_s rescue "") + ("_"+args[1].to_s rescue "") )
45
+ self.sexy_up << " " + method.to_s + " " + args.collect{ |a| a.inspect }.join(",") + "\n"
46
+ end
47
+
48
+ # auto_create_table(method, *args, &block)
49
+ # when :add_index
50
+ # auto_add_index(method, *args, &block)
51
+ # else
52
+ # method_missing_without_auto_migration(method, *args, &block)
53
+ # end
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,28 @@
1
+ # This is for Rails
2
+
3
+ #require 'rails_generator'
4
+ #require 'rails_generator/base'
5
+ #require 'railties/lib/rails_generator/base'
6
+
7
+ DB_PROJECT_ROOT = RAILS_ROOT
8
+ require File.join( File.expand_path(File.dirname(__FILE__)),'','migration_generator_core' )
9
+
10
+ class MigrationGenerator < Rails::Generator::Base
11
+
12
+ def initialize(runtime_args, runtime_options = {})
13
+ super
14
+ @migration_arg_name = runtime_args.first
15
+ end
16
+
17
+ def manifest
18
+ mg = MigrationGeneratorCore.new
19
+ up,down,hints,migration_name = mg.calculate_migration( @migration_arg_name )
20
+
21
+ record do |m|
22
+ m.migration_template 'migration.rb', 'db/migrate',
23
+ :assigns => { :up => up, :down => down, :hints => hints, :migration_name => migration_name.camelize },
24
+ :migration_file_name => migration_name
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,76 @@
1
+ # you should set DB_PROJECT_ROOT constant to some path before including this file
2
+
3
+ class MigrationGeneratorCore
4
+
5
+ DB_PROJECT_ROOT = "." if not defined?(DB_PROJECT_ROOT)
6
+
7
+ def migration_default_name
8
+ i = Dir["#{DB_PROJECT_ROOT}/db/migrate/*definition_migration*"].length
9
+ "definition_migration_#{i+1}"
10
+ end
11
+
12
+ def calculate_migration(predefined_migration_name=nil)
13
+ require File.join( File.expand_path(File.dirname(__FILE__)),'lib','auto_migrations.rb')
14
+ require File.join( File.expand_path(File.dirname(__FILE__)),'lib','sexy_statements_hook.rb')
15
+ ActiveRecord::Migration.send :include, SexyStatementsHook
16
+ ActiveRecord::Migration.send :include, AutoMigrations
17
+
18
+ AutoMigrations.run
19
+
20
+ print "Migration generated. Self.up is:\n" + ActiveRecord::Migration.sexy_up
21
+
22
+ migration_name = predefined_migration_name
23
+ if migration_name.blank?
24
+ name = ActiveRecord::Migration.desired_migration_name
25
+ name = migration_default_name if name.blank?
26
+ migration_name = input("\nEnter filename [#{name}]:").strip.gsub(' ', '_')
27
+ migration_name = name if migration_name.blank?
28
+ end
29
+
30
+ up = ActiveRecord::Migration.sexy_up + " "
31
+ down = ActiveRecord::Migration.sexy_down
32
+
33
+ hints = <<HINT
34
+
35
+ # Hints on migrations syntax.
36
+ #
37
+ # add_column(table_name, column_name, type, options = {})
38
+ # Types are: :primary_key, :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean.
39
+ #
40
+ # Other stuff:
41
+ # change_column(table_name, column_name, type, options = {}) - options as in add_column
42
+ # change_column_default(table_name, column_name, default)
43
+ # remove_column(table_name, column_name)
44
+ # rename_column(table_name, column_name, new_column_name)
45
+ #
46
+ # Sample: Don't add a primary key column
47
+ # create_table(:categories_suppliers, :id => false) do |t|
48
+ # t.column :category_id, :integer
49
+ # t.column :supplier_id, :integer
50
+ # end
51
+ #
52
+ # Sample: reload columns for a model from DB
53
+ # Person.reset_column_information
54
+ #
55
+ # More samples here: http://guides.rubyonrails.org/migrations.html
56
+ HINT
57
+
58
+ #return { :up => up, :down => down, :hints => hints, :migration_name => migration_name.camelize, :migration_file_name => migration_name }
59
+ return up,down,hints,migration_name
60
+
61
+ end
62
+
63
+
64
+ def input(prompt, options=nil)
65
+ print(prompt + " ")
66
+ if options
67
+ while !(response = STDIN.readline.strip.downcase).in?(options);
68
+ print(prompt + " ")
69
+ end
70
+ response
71
+ else
72
+ STDIN.readline
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,49 @@
1
+ # This is for Sinatra
2
+
3
+ require 'active_record'
4
+ require 'active_support/core_ext/string/strip'
5
+ require 'fileutils'
6
+
7
+ DB_PROJECT_ROOT = "."
8
+ require File.join( File.expand_path(File.dirname(__FILE__)),'migration_generator_core' )
9
+
10
+ module Sinatra
11
+ module ActiveRecordTasks
12
+ extend self
13
+
14
+ def create_migration(migration_name, version = nil)
15
+
16
+ mg = MigrationGeneratorCore.new
17
+ up,down,hints,migration_name = mg.calculate_migration( migration_name )
18
+
19
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
20
+ migration_file = File.join(migrations_dir, "#{migration_number}_#{migration_name}.rb")
21
+
22
+ FileUtils.mkdir_p(migrations_dir)
23
+
24
+ File.open(migration_file, 'w') do |file|
25
+ file.write <<-MIGRATION.strip_heredoc
26
+ class #{ migration_name.camelize } < ActiveRecord::Migration
27
+ def self.up
28
+ #{ up }
29
+ end
30
+
31
+ def self.down
32
+ #{ down }
33
+ end
34
+ end
35
+
36
+ #{ hints }
37
+ MIGRATION
38
+ end
39
+
40
+ # puts "done"
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ # no need to load because already loaded from sinatra_activerecord
49
+ # load 'sinatra/activerecord/tasks.rake'
@@ -0,0 +1,11 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ <%= up %>
4
+ end
5
+
6
+ def self.down
7
+ <%= down %>
8
+ end
9
+ end
10
+
11
+ <%= hints %>
@@ -0,0 +1,10 @@
1
+ require 'test/unit'
2
+
3
+ class DataPlanTest < Test::Unit::TestCase
4
+
5
+ def test_this
6
+ puts "so test is good"
7
+ #flunk
8
+ end
9
+
10
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: data_plan
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Pavel Vasev
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-11-28 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Edit you db schema in db/plan.rb file. It has same syntax as rails schema.rb, but you can also leave comments and so on. Then, generate migration, and it will be prefilled with diff between plan.rb and your current db.
22
+ email: pavel.vasev@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/data_plan/generators/migration/lib/auto_migrations.rb
31
+ - lib/data_plan/generators/migration/lib/sexy_statements_hook.rb
32
+ - lib/data_plan/generators/migration/migration_generator.rb
33
+ - lib/data_plan/generators/migration/migration_generator_core.rb
34
+ - lib/data_plan/generators/migration/sinatra.rb
35
+ - lib/data_plan/generators/migration/templates/migration.rb
36
+ - Rakefile
37
+ - README
38
+ - LICENSE
39
+ - test/test_data_plan.rb
40
+ homepage: http://rubygems.org/gems/data_plan
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.24
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Sinatra/ActiveRecord tool for easy migration generation. Works with Rails too.
73
+ test_files: []
74
+