automigration 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +50 -0
  5. data/Rakefile +18 -0
  6. data/automigration.gemspec +24 -0
  7. data/lib/automigration/base_extention.rb +15 -0
  8. data/lib/automigration/fields/belongs_to.rb +29 -0
  9. data/lib/automigration/fields/boolean.rb +9 -0
  10. data/lib/automigration/fields/date.rb +9 -0
  11. data/lib/automigration/fields/datetime.rb +9 -0
  12. data/lib/automigration/fields/float.rb +9 -0
  13. data/lib/automigration/fields/integer.rb +9 -0
  14. data/lib/automigration/fields/password.rb +9 -0
  15. data/lib/automigration/fields/string.rb +9 -0
  16. data/lib/automigration/fields/sys/base.rb +95 -0
  17. data/lib/automigration/fields/sys/db_column.rb +36 -0
  18. data/lib/automigration/fields/sys/keeper.rb +113 -0
  19. data/lib/automigration/fields/sys/slice_creater.rb +32 -0
  20. data/lib/automigration/fields/text.rb +9 -0
  21. data/lib/automigration/fields/time.rb +9 -0
  22. data/lib/automigration/migrator.rb +189 -0
  23. data/lib/automigration/version.rb +3 -0
  24. data/lib/automigration.rb +42 -0
  25. data/lib/tasks/automigration.rake +8 -0
  26. data/log/.gitkeep +0 -0
  27. data/test/auto_migration_test.rb +186 -0
  28. data/test/belongs_to_test.rb +51 -0
  29. data/test/db_column_test.rb +23 -0
  30. data/test/fields/accessible_test.rb +13 -0
  31. data/test/fields_test.rb +34 -0
  32. data/test/models/accessible_model.rb +6 -0
  33. data/test/models/auto_migration1.rb +14 -0
  34. data/test/models/auto_migration1a.rb +7 -0
  35. data/test/models/auto_migration2.rb +8 -0
  36. data/test/models/auto_migration3.rb +5 -0
  37. data/test/models/belongs_to_model.rb +9 -0
  38. data/test/models/boolean_model.rb +5 -0
  39. data/test/models/form_field.rb +6 -0
  40. data/test/models/form_field2.rb +6 -0
  41. data/test/models/local_name.rb +5 -0
  42. data/test/models/local_name2.rb +5 -0
  43. data/test/models/not_automigrable.rb +2 -0
  44. data/test/models/searchable.rb +6 -0
  45. data/test/models/simple.rb +5 -0
  46. data/test/models/user1.rb +7 -0
  47. data/test/test_helper.rb +34 -0
  48. metadata +131 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ log/*.log
6
+ .rbenv-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in automigration.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Alexey Vakhov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # ActiveRecord automigrations
2
+
3
+ ## Overview
4
+
5
+ ``` ruby
6
+ class User < ActiveRecord::Base
7
+ # attributes created via migration
8
+ migration_attr :secure_password, :auth_token
9
+ migration_attr :salt
10
+
11
+ has_fields do |f|
12
+ f.string :name
13
+ f.integer :login_count
14
+ end
15
+ end
16
+ ```
17
+
18
+ ## Devise support
19
+
20
+ ActiveRecord::Base supports all types of devise fields with prefix devise\_
21
+
22
+ ``` ruby
23
+ class User < ActiveRecord::Base
24
+ devise :database_authenticatable, :rememberable, :trackable, :validatable, :recoverable
25
+
26
+ has_fields do |t|
27
+ t.devise_database_authenticatable :null => false
28
+ t.devise_rememberable
29
+ t.devise_trackable
30
+ t.devise_recoverable
31
+ end
32
+ end
33
+ ```
34
+
35
+ ## Timestamps
36
+
37
+ By default in models with has_fields always columns updated_at and created_at created. To ignore
38
+ use has_fields(:timestamps => false)
39
+
40
+ ## Rake task
41
+
42
+ ```
43
+ rake db:auto # create db, create/delete tables, add/modify/delete columns, clean migration table
44
+ ```
45
+
46
+ ## Changelog
47
+
48
+ ### Automigration 0.2.1 (March 18, 2012)
49
+
50
+ * First public release
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new :test do |t|
5
+ t.libs << 'lib'
6
+ t.libs << 'test'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ namespace :db do
12
+ desc 'Recreate test db'
13
+ task :prepare do
14
+ `dropdb automigration_test`
15
+ `createdb automigration_test`
16
+ end
17
+ end
18
+
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "automigration/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "automigration"
7
+ s.version = Automigration::VERSION
8
+ s.authors = ["Alexey Vakhov"]
9
+ s.email = ["vakhov@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{AR automigration}
12
+ s.description = %q{Store your migrations direct in models}
13
+
14
+ s.rubyforge_project = "automigration"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'rails', '~> 3.1'
22
+ s.add_dependency 'ansi'
23
+ s.add_development_dependency 'pg'
24
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ class Base
3
+ class_attribute :__fields_keeper
4
+ self.__fields_keeper = nil
5
+
6
+ def self.fields_keeper
7
+ self.__fields_keeper ||= ::Automigration::Fields::Sys::Keeper.new(self)
8
+ end
9
+
10
+ class << self
11
+ delegate :has_fields, :add_field, :migration_attr, :to => :fields_keeper
12
+ delegate :get_field, :get_field_safe, :get_fields, :to => :fields_keeper
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ module Automigration
2
+ module Fields
3
+ class BelongsTo < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types(:type => :integer, :name => "#{@name}_id")]
6
+ end
7
+
8
+ def extend_model!(model)
9
+ super
10
+
11
+ model.belongs_to @name,
12
+ :class_name => @options[:class_name],
13
+ :inverse_of => @options[:inverse_of]
14
+
15
+ if @options[:accessible]
16
+ model.attr_accessible "#{@name}_id"
17
+ end
18
+ end
19
+
20
+ def assert_options!
21
+ raise "wrong name '#{@name}'" if @name =~ /_id$/
22
+ end
23
+
24
+ def valid_options_keys
25
+ super + [:class_name, :inverse_of]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Boolean < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types(:default => !!@options[:default])]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Date < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Datetime < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Float < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Integer < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Password < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types(:type => :string)]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class String < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,95 @@
1
+ module Automigration
2
+ module Fields
3
+ module Sys
4
+ class Base
5
+ def self.all
6
+ [
7
+ Fields::BelongsTo,
8
+ Fields::Boolean,
9
+ Fields::Date,
10
+ Fields::Datetime,
11
+ Fields::Float,
12
+ Fields::Integer,
13
+ Fields::Password,
14
+ Fields::String,
15
+ Fields::Text,
16
+ Fields::Time
17
+ ]
18
+ end
19
+
20
+ attr_reader :options, :name
21
+
22
+ def initialize(options)
23
+ options.assert_valid_keys(valid_options_keys)
24
+
25
+ @name = options[:name]
26
+ @options = options.except(:name, :as).reverse_merge(:accessible => true)
27
+
28
+ assert_options!
29
+ end
30
+
31
+ # overload these methods if needed [begin]
32
+ def to_db_columns
33
+ []
34
+ end
35
+
36
+ def valid_options_keys
37
+ [
38
+ :name, :as, # system attributes
39
+ :default, :null, :limit, :scale, :precision, # db columns keys
40
+ :accessible, # mark attribute as accessible
41
+ :ru, :en, :de # languages
42
+ ]
43
+ end
44
+
45
+ def assert_options!
46
+ end
47
+
48
+ def extend_model!(model)
49
+ if @options[:accessible]
50
+ model.attr_accessible @name
51
+ end
52
+ end
53
+ # overload these methods if needed [end]
54
+
55
+ def self.kind
56
+ @kind ||= self.to_s.underscore.sub("automigration/fields/", '').to_sym
57
+ end
58
+
59
+ def self.from_meta(meta)
60
+ all.each do |field_class|
61
+ if field_class.kind == meta[:as]
62
+ return field_class.new(meta)
63
+ end
64
+ end
65
+ raise "wrong meta: #{meta.inspect}"
66
+ end
67
+
68
+ protected
69
+ def db_column_for_standart_types(options = {})
70
+ options.assert_valid_keys(:default, :null, :limit, :scale, :precision, :type, :name)
71
+
72
+ if options[:type]
73
+ type = options[:type]
74
+ else
75
+ type = self.class.kind
76
+ end
77
+
78
+ if options[:name]
79
+ column_name = options[:name]
80
+ else
81
+ column_name = @name
82
+ end
83
+
84
+ DbColumn.new(column_name, type, options.except(:type, :name).reverse_merge({
85
+ :default => @options[:default],
86
+ :null => @options[:null],
87
+ :limit => @options[:limit],
88
+ :scale => @options[:scale],
89
+ :precision => @options[:precision]
90
+ }))
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,36 @@
1
+ module Automigration
2
+ module Fields
3
+ module Sys
4
+ class DbColumn < Struct.new(:name, :type, :options)
5
+ def initialize(name_, type_, options_)
6
+ super
7
+ options_.assert_valid_keys(:default, :null, :limit, :scale, :precision)
8
+ end
9
+
10
+ def self.from_activerecord_column(column)
11
+ out = DbColumn.new(column.name.to_sym, column.type.to_sym, {
12
+ :default => column.default,
13
+ :null => column.null,
14
+ :limit => column.limit,
15
+ :scale => column.scale,
16
+ :precision => column.precision
17
+ })
18
+ end
19
+
20
+ def the_same?(other)
21
+ (__to_array <=> other.send(:__to_array)) == 0
22
+ end
23
+
24
+ def to_options
25
+ options.reject{|k, v| v.nil?}
26
+ end
27
+
28
+ private
29
+ # compare only by 3 values
30
+ def __to_array
31
+ [name.to_s, type.to_s, options[:default].to_s]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,113 @@
1
+ module Automigration
2
+ module Fields
3
+ module Sys
4
+ class Keeper
5
+ attr_reader :fields
6
+ attr_reader :migration_attrs
7
+
8
+ def initialize(model)
9
+ @model = model
10
+ @fields = nil
11
+ @devise_fields = []
12
+ @migration_attrs = []
13
+ @timestamps_added = false
14
+
15
+ @fields_lookup = {}
16
+ end
17
+
18
+ def has_fields(options = {}, &block)
19
+ options.assert_valid_keys(:timestamps)
20
+ options.reverse_merge!(:timestamps => true)
21
+
22
+ slice_creater = SliceCreater.new
23
+ yield slice_creater
24
+
25
+ slice_creater.fields.each do |field|
26
+ Fields::Sys::Base.from_meta(field).extend_model!(@model)
27
+ end
28
+
29
+ @fields ||= []
30
+ @fields += slice_creater.fields
31
+ @devise_fields = slice_creater.devise_fields
32
+
33
+ if !@timestamps_added && options[:timestamps]
34
+ @timestamps_added = true
35
+ @fields << {:as => :datetime, :name => :created_at, :accessible => false}
36
+ @fields << {:as => :datetime, :name => :updated_at, :accessible => false}
37
+ end
38
+ end
39
+
40
+ def add_field(type, name, options = {})
41
+ has_fields(:timestamps => false) do |f|
42
+ f.send type, name, options
43
+ end
44
+ end
45
+
46
+ def migration_attr(*args)
47
+ @migration_attrs += args.flatten.map(&:to_s)
48
+ end
49
+
50
+ def auto_migrable?
51
+ @fields.present?
52
+ end
53
+
54
+ def get_field(field_name)
55
+ @fields_lookup[field_name] ||= begin
56
+ meta = @fields.detect{|meta| meta[:name] == field_name}
57
+ raise "wrong field_name: #{field_name}" unless meta
58
+ Fields::Sys::Base.from_meta(meta)
59
+ end
60
+ end
61
+
62
+ def get_field_safe(field_name)
63
+ get_field(field_name)
64
+ rescue RuntimeError
65
+ end
66
+
67
+ def get_fields
68
+ @get_fields ||= @fields.map do |meta|
69
+ get_field(meta[:name])
70
+ end
71
+ end
72
+
73
+ def db_columns_for_field(field_name)
74
+ meta = @fields.detect{|meta| meta[:name] == field_name}
75
+ if meta
76
+ Fields::Sys::Base.from_meta(meta).to_db_columns
77
+ else
78
+ []
79
+ end
80
+ end
81
+
82
+ def db_columns
83
+ out = []
84
+
85
+ out += @fields.map do |meta|
86
+ Fields::Sys::Base.from_meta(meta).to_db_columns
87
+ end.flatten
88
+
89
+ if defined?(Devise::Schema)
90
+ devise_schema = Class.new do
91
+ include Devise::Schema
92
+
93
+ define_method :apply_devise_schema do |*args|
94
+ opts = args.extract_options!
95
+ raise "wrong arguments" unless args.size == 2
96
+ name = args[0]
97
+ as = args[1].to_s.underscore.to_sym
98
+ as = :datetime if as == :date_time
99
+ out << DbColumn.new(name, as, opts)
100
+ end
101
+ end.new
102
+
103
+ @devise_fields.each do |meta|
104
+ devise_schema.send(meta[:as].to_s.sub(/^devise_/, ''), *meta[:args])
105
+ end
106
+ end
107
+
108
+ out
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,32 @@
1
+ module Automigration
2
+ module Fields
3
+ module Sys
4
+ class SliceCreater
5
+ attr_reader :fields
6
+ attr_reader :devise_fields
7
+
8
+ def initialize
9
+ @fields = []
10
+ @devise_fields = []
11
+ end
12
+
13
+ Fields::Sys::Base.all.each do |field_class|
14
+ define_method field_class.kind do |*args|
15
+ options = args.extract_options!
16
+ raise "wrong amount of args" unless args.size == 1
17
+ name = args[0]
18
+ @fields << {:name => name, :as => field_class.kind}.merge(options)
19
+ end
20
+ end
21
+
22
+ def method_missing(meth, *args, &block)
23
+ if meth.to_s =~ /^devise_(.*)/
24
+ @devise_fields << {:as => meth, :args => args}
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Text < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Automigration
2
+ module Fields
3
+ class Time < Sys::Base
4
+ def to_db_columns
5
+ [db_column_for_standart_types]
6
+ end
7
+ end
8
+ end
9
+ end