mini_record 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- MiniRecord is micro extension for our `ActiveRecord` gem.
1
+ MiniRecord is a micro extension for our `ActiveRecord` gem.
2
2
  With it you can add the ability to create columns outside the default schema, directly
3
3
  in your **model** in a similar way that you just know in others projects
4
4
  like DataMapper or MongoMapper.
5
5
 
6
- My inspiration come from this handy [project](https://github.com/pjhyett/auto_migrations)
6
+ My inspiration come from this handy [project](https://github.com/pjhyett/auto_migrations).
7
7
 
8
8
  ## Features
9
9
 
@@ -38,14 +38,33 @@ class Person < ActiveRecord::Base
38
38
  end
39
39
  belongs_to :address
40
40
  end
41
+ Person.auto_upgrade!
41
42
 
43
+ # you can use also this way
42
44
  class Address < ActiveRecord::Base
43
- schema, :id => true do |s| # id => true is not really necessary but as
44
- s.string :city # in +create_table+ you have here the same options
45
- s.string :state
46
- s.integer :number
47
- end
45
+ key.string :city
46
+ key.string :state
47
+ key.integer :number
48
+ has_many :people
48
49
  end
50
+
51
+ # or this
52
+ class Address < ActiveRecord::Base
53
+ col.string :city
54
+ col.string :state
55
+ col.integer :number
56
+ has_many :people
57
+ end
58
+
59
+ # or this
60
+ class Address < ActiveRecord::Base
61
+ property.string :city
62
+ property.string :state
63
+ property.integer :number
64
+ has_many :people
65
+ end
66
+
67
+ Address.auto_upgrade!
49
68
  ```
50
69
 
51
70
  Once you bootstrap your **app**, missing columns and tables will be created on the fly.
@@ -63,6 +82,14 @@ class Person < ActiveRecord::Base
63
82
  end
64
83
  belongs_to :address
65
84
  end
85
+
86
+ # or if you use others ways
87
+ class Person < ActiveRecord::Base
88
+ col.string :name
89
+ col.string :surname # <<- this
90
+ col.integer :address_id
91
+ belongs_to :address
92
+ end
66
93
  ```
67
94
 
68
95
  So now when you start your **webserver** you can see an `ALTER TABLE` statement, this mean that your existing
@@ -72,21 +99,12 @@ records are happy and safe.
72
99
 
73
100
  It's exactly the same, but the column will be _really_ deleted without affect other columns.
74
101
 
75
- ### Changing a column
102
+ ### Change columns
76
103
 
77
104
  It's not possible for us know when/what column you have renamed, but we can know if you changed the `type` so
78
105
  if you change `t.string :name` to `t.text :name` we are be able to perform an `ALTER TABLE`
79
106
 
80
- ### Drop unused tables/indexes
81
-
82
- You can do it by hand but if yours are lazy like mine you can simply invoke:
83
-
84
- ``` rb
85
- ActiveRecord::Base.drop_unused_tables
86
- ActiveRecord::Base.drop_unused_indexes
87
- ```
88
-
89
- # Warning
107
+ ## Warnings
90
108
 
91
109
  This software is not yet tested in a production project, now is only heavy development and if you can
92
110
  pleas fork it, find bug add a spec and then come back with a pull request. Thanks!
@@ -1,19 +1,103 @@
1
- require 'mini_record/auto_migrations'
2
-
3
1
  module MiniRecord
4
2
  module AutoSchema
5
3
  def self.included(base)
6
4
  base.extend(ClassMethods)
7
- base.send(:include, MiniRecord::AutoMigrations)
8
5
  end
9
6
 
10
7
  module ClassMethods
11
- def schema(options={}, &block)
12
- auto_create_table(table_name, options, &block)
13
- reset_column_information
8
+ def table_definition
9
+ @_table_definition ||= begin
10
+ tb = ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
11
+ tb.primary_key(primary_key)
12
+ end
13
+ end
14
+ alias :col :table_definition
15
+ alias :key :table_definition
16
+ alias :property :table_definition
17
+
18
+ def reset_table_definition!
19
+ @_table_definition = nil
20
+ end
21
+
22
+ def schema
23
+ reset_table_definition!
24
+ yield table_definition
14
25
  end
15
26
  alias :keys :schema
16
27
  alias :properties :schema
17
- end
28
+
29
+ def auto_upgrade!
30
+ # Table doesn't exist, create it
31
+ unless connection.tables.include?(table_name)
32
+ # TODO: Add to create_table options
33
+ class << connection; attr_accessor :table_definition; end unless connection.respond_to?(:table_definition=)
34
+ connection.table_definition = table_definition
35
+ connection.create_table(table_name)
36
+ connection.table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
37
+ end
38
+
39
+ # Grab database columns
40
+ fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
41
+ hash[column.name] = column
42
+ hash
43
+ end
44
+
45
+ # Grab new schema
46
+ fields_in_schema = table_definition.columns.inject({}) do |hash, column|
47
+ hash[column.name.to_s] = column
48
+ hash
49
+ end
50
+
51
+ # Remove fields from db no longer in schema
52
+ (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
53
+ column = fields_in_db[field]
54
+ connection.remove_column table_name, column.name
55
+ end
56
+
57
+ # Add fields to db new to schema
58
+ (fields_in_schema.keys - fields_in_db.keys).each do |field|
59
+ column = fields_in_schema[field]
60
+ options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
61
+ options[:default] = column.default if !column.default.nil?
62
+ options[:null] = column.null if !column.null.nil?
63
+ connection.add_column table_name, column.name, column.type.to_sym, options
64
+ end
65
+
66
+ # Change attributes of existent columns
67
+ (fields_in_schema.keys & fields_in_db.keys).each do |field|
68
+ if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
69
+ changed = false # flag
70
+ new_type = fields_in_schema[field].type.to_sym
71
+ new_attr = {}
72
+
73
+ # First, check if the field type changed
74
+ if fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym
75
+ changed = true
76
+ end
77
+
78
+ # Special catch for precision/scale, since *both* must be specified together
79
+ # Always include them in the attr struct, but they'll only get applied if changed = true
80
+ new_attr[:precision] = fields_in_schema[field][:precision]
81
+ new_attr[:scale] = fields_in_schema[field][:scale]
82
+
83
+ # Next, iterate through our extended attributes, looking for any differences
84
+ # This catches stuff like :null, :precision, etc
85
+ fields_in_schema[field].each_pair do |att,value|
86
+ next if att == :type or att == :base or att == :name # special cases
87
+ if !value.nil? && value != fields_in_db[field].send(att)
88
+ new_attr[att] = value
89
+ changed = true
90
+ end
91
+ end
92
+
93
+ # Change the column if applicable
94
+ connection.change_column table_name, field, new_type, new_attr if changed
95
+ end
96
+ end
97
+
98
+ # Reload column information
99
+ reset_column_information
100
+ end
101
+ end # ClassMethods
18
102
  end # AutoSchema
19
103
  end # MiniRecord
@@ -1,3 +1,3 @@
1
1
  module MiniRecord
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,13 +1,16 @@
1
1
  require File.expand_path('../spec_helper.rb', __FILE__)
2
- require File.expand_path('../fake_model.rb', __FILE__)
2
+ require File.expand_path('../models.rb', __FILE__)
3
3
 
4
4
  describe MiniRecord do
5
5
 
6
- it 'works correctly' do
6
+ it 'has #schema inside model' do
7
7
  # For unknown reason separate specs doesn't works
8
+ ActiveRecord::Base.connection.table_exists?(Person.table_name).must_equal false
9
+ Person.auto_upgrade!
8
10
  Person.table_name.must_equal 'people'
9
- Person.db_columns.must_equal %w[id name]
11
+ Person.db_columns.sort.must_equal %w[id name]
10
12
  Person.column_names.must_equal Person.db_columns
13
+ Person.column_names.must_equal Person.schema_columns
11
14
  person = Person.create(:name => 'foo')
12
15
  person.name.must_equal 'foo'
13
16
  proc { person.surname }.must_raise NoMethodError
@@ -19,12 +22,13 @@ describe MiniRecord do
19
22
  p.string :surname
20
23
  end
21
24
  end
25
+ Person.auto_upgrade!
22
26
  Person.count.must_equal 1
23
27
  person = Person.last
24
28
  person.name.must_equal 'foo'
25
29
  person.surname.must_be_nil
26
30
  person.update_attribute(:surname, 'bar')
27
- Person.db_columns.must_equal %w[id name surname]
31
+ Person.db_columns.sort.must_equal %w[id name surname]
28
32
  Person.column_names.must_equal Person.db_columns
29
33
 
30
34
  # Remove a column without lost data
@@ -33,11 +37,13 @@ describe MiniRecord do
33
37
  p.string :name
34
38
  end
35
39
  end
40
+ Person.auto_upgrade!
36
41
  person = Person.last
37
42
  person.name.must_equal 'foo'
38
43
  proc { person.surname }.must_raise NoMethodError
39
- Person.db_columns.must_equal %w[id name]
44
+ Person.db_columns.sort.must_equal %w[id name]
40
45
  Person.column_names.must_equal Person.db_columns
46
+ Person.column_names.must_equal Person.schema_columns
41
47
 
42
48
  # Change column without lost data
43
49
  Person.class_eval do
@@ -49,9 +55,30 @@ describe MiniRecord do
49
55
  person.name.must_equal 'foo'
50
56
  end
51
57
 
52
- it 'should remove no tables, since Person is still defined' do
53
- ActiveRecord::Base.drop_unused_tables
54
- ActiveRecord::Base.connection.tables.must_equal %w[people]
55
- Person.count.must_equal 1
58
+ it 'has #key,col,property inside model' do
59
+ ActiveRecord::Base.connection.table_exists?(Post.table_name).must_equal false
60
+ ActiveRecord::Base.connection.table_exists?(Category.table_name).must_equal false
61
+ Post.auto_upgrade!; Category.auto_upgrade!
62
+ Post.column_names.must_equal Post.db_columns
63
+ Category.column_names.must_equal Category.schema_columns
64
+
65
+ # Check default properties
66
+ category = Category.create(:title => 'category')
67
+ post = Post.create(:title => 'foo', :body => 'bar', :category_id => category.id)
68
+ post = Post.first
69
+ post.title.must_equal 'foo'
70
+ post.body.must_equal 'bar'
71
+ post.category.must_equal category
72
+
73
+ # Remove a column
74
+ Post.reset_table_definition!
75
+ Post.class_eval do
76
+ key.string :name
77
+ key.references :category
78
+ end
79
+ Post.auto_upgrade!
80
+ post = Post.first
81
+ post.name.must_be_nil
82
+ post.category.must_equal category
56
83
  end
57
84
  end
data/spec/models.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'logger'
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
+ # ActiveRecord::Base.connection.tables.each { |t| ActiveRecord::Base.connection.drop_table(t) }
5
+ # ActiveRecord::Base.logger = Logger.new($stdout)
6
+
7
+ module SpecHelper
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def db_columns
14
+ connection.columns(table_name).map(&:name)
15
+ end
16
+
17
+ def schema_columns
18
+ table_definition.columns.map { |c| c.name.to_s }
19
+ end
20
+ end
21
+ end
22
+
23
+ class Person < ActiveRecord::Base
24
+ include SpecHelper
25
+ schema do |s|
26
+ s.string :name
27
+ end
28
+ end
29
+
30
+ class Post < ActiveRecord::Base
31
+ include SpecHelper
32
+
33
+ key.string :title
34
+ key.string :body
35
+ key.references :category
36
+ belongs_to :category
37
+ end
38
+
39
+ class Category < ActiveRecord::Base
40
+ include SpecHelper
41
+
42
+ key.string :title
43
+ has_many :posts
44
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_record
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Davide D'Agostino
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-05 00:00:00 +02:00
18
+ date: 2011-09-06 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -52,13 +52,12 @@ files:
52
52
  - README.md
53
53
  - Rakefile
54
54
  - lib/mini_record.rb
55
- - lib/mini_record/auto_migrations.rb
56
55
  - lib/mini_record/auto_schema.rb
57
56
  - lib/mini_record/version.rb
58
57
  - mini_record.gemspec
59
58
  - rakefile.compiled.rbc
60
- - spec/fake_model.rb
61
59
  - spec/mini_record_spec.rb
60
+ - spec/models.rb
62
61
  - spec/spec_helper.rb
63
62
  has_rdoc: true
64
63
  homepage: https://github.com/DAddYE/mini_record
@@ -95,6 +94,6 @@ signing_key:
95
94
  specification_version: 3
96
95
  summary: MiniRecord is a micro gem that allow you to write schema inside your model as you can do in DataMapper.
97
96
  test_files:
98
- - spec/fake_model.rb
99
97
  - spec/mini_record_spec.rb
98
+ - spec/models.rb
100
99
  - spec/spec_helper.rb
@@ -1,108 +0,0 @@
1
- require 'active_support/core_ext/class/attribute_accessors'
2
-
3
- module MiniRecord
4
-
5
- module AutoMigrations
6
- def self.included(base)
7
- base.extend ClassMethods
8
- class << base
9
- cattr_accessor :tables_in_schema, :indexes_in_schema
10
- self.tables_in_schema, self.indexes_in_schema = [], []
11
- end
12
- end
13
-
14
- module ClassMethods
15
- def auto_create_table(table_name, options, &block)
16
-
17
- (self.tables_in_schema ||= []) << table_name
18
-
19
- # Table doesn't exist, create it
20
- unless connection.tables.include?(table_name)
21
- return connection.create_table(table_name, options, &block)
22
- end
23
-
24
- # Grab database columns
25
- fields_in_db = connection.columns(table_name).inject({}) do |hash, column|
26
- hash[column.name] = column
27
- hash
28
- end
29
-
30
- # Grab schema columns (lifted from active_record/connection_adapters/abstract/schema_statements.rb)
31
- table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
32
- primary_key = options[:primary_key] || "id"
33
- table_definition.primary_key(primary_key) unless options[:id] == false
34
-
35
- # Return the table definition
36
- yield table_definition
37
-
38
- # Grab new schema
39
- fields_in_schema = table_definition.columns.inject({}) do |hash, column|
40
- hash[column.name.to_s] = column
41
- hash
42
- end
43
-
44
- # Remove fields from db no longer in schema
45
- (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
46
- column = fields_in_db[field]
47
- connection.remove_column table_name, column.name
48
- end
49
-
50
- # Add fields to db new to schema
51
- (fields_in_schema.keys - fields_in_db.keys).each do |field|
52
- column = fields_in_schema[field]
53
- options = {:limit => column.limit, :precision => column.precision, :scale => column.scale}
54
- options[:default] = column.default if !column.default.nil?
55
- options[:null] = column.null if !column.null.nil?
56
- connection.add_column table_name, column.name, column.type.to_sym, options
57
- end
58
-
59
- # Change attributes of existent columns
60
- (fields_in_schema.keys & fields_in_db.keys).each do |field|
61
- if field != primary_key #ActiveRecord::Base.get_primary_key(table_name)
62
- changed = false # flag
63
- new_type = fields_in_schema[field].type.to_sym
64
- new_attr = {}
65
-
66
- # First, check if the field type changed
67
- if fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym
68
- changed = true
69
- end
70
-
71
- # Special catch for precision/scale, since *both* must be specified together
72
- # Always include them in the attr struct, but they'll only get applied if changed = true
73
- new_attr[:precision] = fields_in_schema[field][:precision]
74
- new_attr[:scale] = fields_in_schema[field][:scale]
75
-
76
- # Next, iterate through our extended attributes, looking for any differences
77
- # This catches stuff like :null, :precision, etc
78
- fields_in_schema[field].each_pair do |att,value|
79
- next if att == :type or att == :base or att == :name # special cases
80
- if !value.nil? && value != fields_in_db[field].send(att)
81
- new_attr[att] = value
82
- changed = true
83
- end
84
- end
85
-
86
- # Change the column if applicable
87
- connection.change_column table_name, field, new_type, new_attr if changed
88
- end
89
- end
90
- end
91
-
92
- def drop_unused_tables
93
- (connection.tables - tables_in_schema - %w(schema_info schema_migrations)).each do |table|
94
- connection.drop_table table
95
- end
96
- end
97
-
98
- def drop_unused_indexes
99
- tables_in_schema.each do |table_name|
100
- indexes_in_db = connection.indexes(table_name).map(&:name)
101
- (indexes_in_db - indexes_in_schema & indexes_in_db).each do |index_name|
102
- connection.remove_index table_name, :name => index_name
103
- end
104
- end
105
- end
106
- end # ClassMethods
107
- end # Migrations
108
- end # ActiveKey
data/spec/fake_model.rb DELETED
@@ -1,21 +0,0 @@
1
- require 'logger'
2
-
3
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
- # ActiveRecord::Base.connection.tables.each { |t| ActiveRecord::Base.connection.drop_table(t) }
5
- # ActiveRecord::Base.logger = Logger.new($stdout)
6
-
7
- class Person < ActiveRecord::Base
8
- schema do |s|
9
- s.string :name
10
- end
11
-
12
- # Testing purpose
13
- def self.db_columns
14
- connection.columns(table_name).map(&:name)
15
- end
16
-
17
- def self.schema_columns
18
- table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(connection)
19
- table_definition.columns.map(&:name)
20
- end
21
- end