mini_record 0.0.1 → 0.1.0

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.
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