databasion 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ### 0.2.0
2
+
3
+ * [FIXED] Fields flagged with an index were not properly referenced.
4
+ * [CHANGE] There is now an Environment spreadsheet to define environments, and each environment replaces the old Database spreadsheet. Databasion data is also broken down by directory, named after the relevant environment. Specific environments are called with the new -e switch.
5
+ * [NEW] Version control is now handled in the Environment spreadsheet, and works via the -r switch. Ideal for near-realtime updates using crontab.
6
+ * [NEW] Multiple primary keys can now be assigned by adding a comma delimited _primary_ to a field.
7
+ * [NEW] The _options_ field in a spreadsheet environment definition now works according to the Rails Migration rules.
data/README.md CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  ## Google Spreadsheet/Excel -> YAML -> Ruby Migration -> Database Management Tool
4
4
 
5
- A database management tool. The theory is that a designer/planner can edit application data and a programmer can setup the database and it's fields, all in one happy little place. As tables are added and data is changed, if the script is run once again it will update the target database.
5
+ A database management tool. The theory is that a designer/planner can edit application data while a programmer setups up the database schema and it's fields, all in one happy little place. As tables are added and data is changed, if the script is run once again it will update the target database.
6
6
 
7
7
  TODO: While this system uses Rails Migrations, it isn't taking full advantage of them (i.e. tracking changes, allowing for rollbacks, etc).
8
8
 
9
+ NOTE: The system is currently undergoing major changes, and will not remain backwards compatible up until roughly a 0.9 release.
10
+
9
11
  ## Requirements
10
12
 
11
13
  ### Ruby
@@ -16,7 +18,7 @@ TODO: While this system uses Rails Migrations, it isn't taking full advantage of
16
18
 
17
19
  * ActiveRecord >= 2.3.5
18
20
  * ActiveSupport >= 2.3.5
19
- * Google Spreadsheet >= 0.1.1
21
+ * Google Spreadsheet >= 0.1.2
20
22
  * Spreadsheet >= 0.6.4.1
21
23
 
22
24
  ## Installation
@@ -29,42 +31,67 @@ TODO: While this system uses Rails Migrations, it isn't taking full advantage of
29
31
 
30
32
  None of this would really work if there weren't some conventions in place. The following explains how the worksheet needs to be formatted, how the data spreadsheets themselves needs to be formatted, what fields are required, and what fields can be ignored.
31
33
 
32
- At the highest level there needs to be a worksheet named _Database_. This is simply a master list of related spreadsheets, and what database they correspond to (for split table designs). The column names are required.
34
+ ### Environment and Version Control
35
+
36
+ In order to manage your _environments_ and _versions_ of the data for each environment, an __Environments__ spreadsheet is required.
37
+
38
+ #### Keywords
39
+
40
+ Row0 is reserved for keywords, which are listed below.
41
+
42
+ * environment - Defines your environment. These follow the standard _development, test, production_ breakdown. Anything outside of these three are considered special case environments.
43
+ * version - The current version this environment is at in regards to the databasion spreadsheet. This is also used for auto-updates via crontab.
44
+
45
+ A typical spreadsheet might look like the following.
46
+
47
+ | environment | version
48
+ |:------------|:-------
49
+ | development | 50
50
+ | test | 49
51
+ | production | 45
52
+ | brian_test | 55
53
+ | bojo_test | 50
54
+
55
+ Versions are tracked by a locally written version file when databasion is ran with the _--cron_ switch.
56
+
57
+ You will also need to create spreadsheets named after the typical _development, test, production_ keywords as listed in the Environments spreadsheet. Within each spreadsheet, database descriptions need to be defined as written below. The column names are required.
58
+
59
+ Note: Non-standard environments can also be created, however, they are treated as special case and not part of the environment chain.
33
60
 
34
- ### Database
61
+ ### Example 'Development' Spreadsheet
35
62
 
36
63
  | spreadsheet | dbname| database| username| password| adapter| host | port| options
37
64
  |:------------|:------|:--------|:--------|:--------|:-------|:---------|:----|:-------
38
65
  | superheroes | db1 | db_test | dbuser | dbuser | mysql | 127.0.0.1| |
39
66
 
40
- The options column currently support's _force_, which tells the database to drop and recreate the table.
67
+ The options column currently supports any SQL commands that can typically be passed to a Rails Migration.
41
68
 
42
69
  Next we define the actual table spreadsheets.
43
70
 
44
- ### Superheroes
45
-
46
- | column0 | | |
47
- |:---------|:------------|:-------------|:-------------------
48
- | ignore | | |
49
- | comment | | |
50
- | table | superheroes | |
51
- | index | yes | |
52
- | field | id | name | power
53
- | type | integer | string, 20 | string, 20, Wimp
54
- | | 1 | Brian Jones | Ruby Hacker
55
- | | 2 | Superman | Invincible
56
- | | 3 | Batman | Rich
57
-
58
- ### Keywords
59
-
60
- * ignore - Anything written in this column will cause this column and it's data to be ignored.
71
+ ### Example 'Superheroes' Table
72
+
73
+ | column0 | | | |
74
+ |:---------|:------------|:-------------|:-------------------|:--------
75
+ | ignore | | | | testing
76
+ | comment | | | |
77
+ | table | superheroes | | |
78
+ | index | yes | | |
79
+ | field | id | name | power | cape
80
+ | type | integer | string, 20 | string, 20, Wimp | boolean
81
+ | | 1 | Brian Jones | Ruby Hacker | false
82
+ | | 2 | Superman | Invincible | true
83
+ | | 3 | Batman | Rich | true
84
+ | testing | 4 | Hulk | Huge | false
85
+
86
+ ### Table Spreadsheet Keywords
87
+
88
+ * ignore - Anything written in this column will cause this column and it's data to be ignored, with the exception of _environment names_. See the Environment and Version Control section below for further usage.
89
+ * comment - Ideally a description of the field, what the values means, etc.
61
90
  * table - The name of the table, and an optional comma delimited 'false' if the table name should not be auto-pluralized.
62
- * index - If something is written in a columns field here, it will get flagged as an index and created via add_index(table, [fields]).
63
- * field - The name of the table column.
91
+ * index - This will create an index on the designated field. If a multi-index is required, indices will be grouped by unique names. Multiple multi-indices are possible.
92
+ * field - The name of the table column, with an optional comma delimited 'auto' or 'primary' parameter. Auto is strictly limited to an 'id' field, and enables auto incrementation.
64
93
  * type - A comma delimited list giving the type of the column (using Ruby migration terms), optional size, and optional default value.
65
94
 
66
- Note: If an 'id' column is specified, then it is assumed the id's are supplied by hand. Auto-incrementation is disabled, and 'id' is the primary key.
67
-
68
95
  __Ruby Migration Types__
69
96
 
70
97
  * binary
@@ -79,13 +106,13 @@ __Ruby Migration Types__
79
106
  * time
80
107
  * timestamp
81
108
 
82
- ### Columns
109
+ #### Columns
83
110
 
84
111
  Currently column0 is reserved for keywords and comments.
85
112
 
86
113
  If something besides a keyword is written in column0, that row is ignored and will not be used. This is useful if you need to edit out some data.
87
114
 
88
- ### Rows
115
+ #### Rows
89
116
 
90
117
  Row0 isn't technically reserved, but should ideally be saved for use with the _ignore_ flag. If any text is written in a column (with the exception of column0), that column will be ignored. This is useful for editing out columns that one doesn't currently want in the database.
91
118
 
@@ -103,16 +130,18 @@ Edit _config/google.yml_. Then run the scripts.
103
130
  databasion --load
104
131
  databasion --svn
105
132
  databasion --git
133
+ databasion --cron
134
+ databasion --env
106
135
 
107
136
  Or run them all in order.
108
137
 
109
- databasion --google --migrate --load --git
138
+ databasion --google --migrate --load --git --env development
110
139
 
111
140
  You can supply a different config path as well.
112
141
 
113
142
  databasion -g -m -l -i --config config/my.other.config.yml
114
143
 
115
- Someone administrating a production database with this tool would definitely want to run each script sequentially by hand.
144
+ The environment switch defaults to _development_.
116
145
 
117
146
  ### YAML Configuration
118
147
 
@@ -133,27 +162,51 @@ If the currently created databasion project is committed to SVN, running the _--
133
162
 
134
163
  Much like SVN, if the project is commited to a GIT repo, the _--git_ switch will auto-add and commit all the project files. If there isn't a repository, it will also initialize a new one for you.
135
164
 
136
- ## Version Control
165
+ ## Keyword Environment Management (Currently not implemented)
166
+
167
+ Keywords are also supported in the _ignore_ columns and rows of table definitions. This allows us to not inadvertently add columns or data to systems which aren't configured to use them yet. The following is an example.
168
+
169
+ | column0 | | | | |
170
+ |:-------------|:------------|:-------------|:-------------------|:--------|:----------------------
171
+ | ignore | | | | test | brian_test, bojo_test
172
+ | comment | | | | |
173
+ | table | superheroes | | | |
174
+ | index | yes | | | |
175
+ | field | id | name | power | cape | mask
176
+ | type | integer | string, 20 | string, 20, Wimp | boolean | boolean
177
+ | | 1 | Brian Jones | Ruby Hacker | false | false
178
+ | | 2 | Superman | Invincible | true | false
179
+ | | 3 | Batman | Rich | true | true
180
+ | brian_test | 4 | Hulk | Huge | false | false
181
+ | brian_test | 5 | Spawn | Demonic | true | true
182
+
183
+ Environments are updated in the following order: development -> test -> production. Special case environments are ignored with the exception of themselves.
184
+
185
+ The above definition states that the _cape_ field should only be deployed _up to test_, and no further. The _mask_ field should *only* be deployed to both brian_test and bojo_test since they are not common environment names. Everything else will be updated clear up to production.
186
+
187
+ It also follows that the field with the id's 4 and 5 should only be deployed to brian_test.
188
+
189
+ ### Version Control
137
190
 
138
191
  There is now a system in place to do crontab driven auto-updates. This allows the data to be updated without anyone having to access any systems.
139
192
 
140
- First update _config/google.yml_'s cron section to reflect your project settings. The _options_ are your standard databasion switches. Make sure to supply a location for the version file so that it can be read at a later date.
193
+ First update _config/google.yml_'s environment section to reflect your project settings for each given environment. The _options_ are standard databasion switches.
141
194
 
142
- Next, add a _Version_ spreadsheet to the master worksheet. In the A1 field use whichever number works best to start versioning with. When using this to manage the project, if the number is higher than before then the system will be updated. Currently version controlled rollbacks are not implemented.
195
+ Next, add an entry for your environment in the _Environments_ spreadsheet, along with the starting version number. When using this to manage the project, if the number is higher than before then the system will be updated. Currently version controlled rollbacks are not implemented.
143
196
 
144
197
  Finally, add the databasion script to crontab.
145
198
 
146
199
  Example crontab:
147
200
 
148
- */1 * * * * cd /home/my_user/project && databasion -r
201
+ */1 * * * * cd /home/my_user/project && databasion -r -e test
149
202
 
150
- This checks the Version spreadsheet once a minute, and if the version has changed runs databasion with the supplied options.
203
+ This checks the Environment spreadsheet once a minute, and if the version for the target system (test in this case) has changed, runs databasion with the supplied options.
151
204
 
152
205
  Note: This could easily be used from the commandline as well, and not just crontab.
153
206
 
154
- ## Roadmap
207
+ ## Planned Features
155
208
 
156
- Long and winding.
209
+ Plugins - It would be nice to be able to build plugin support so people can do pre/post processing of the data, and/or export out to other formats besides YAML, etc. I also plan on designing this in such a way so that it isn't strictly RDMBS centric.
157
210
 
158
211
  ## Testing
159
212
 
@@ -165,3 +218,5 @@ __Brian Jones__ - Server Engineer, [Istpika](http://www.istpika.com)
165
218
 
166
219
  * Work: <brian.jones@istpika.com>
167
220
  * Personal: <mojobojo@gmail.com>
221
+
222
+ Twitter: [mojobojo](http://twitter.com/mojobojo) - If you are using databasion give me a shoutout, I'm curious to see who is using this system.
data/Rakefile CHANGED
@@ -8,11 +8,11 @@ Jeweler::Tasks.new do |gem|
8
8
  gem.email = "mojobojo@gmail.com"
9
9
  gem.homepage = "http://github.com/boj/databasion"
10
10
  gem.authors = ["Brian Jones", "Istpika"]
11
- gem.version = "0.1.1"
11
+ gem.version = "0.2.0"
12
12
 
13
13
  gem.add_dependency('activerecord', '>= 2.3.5')
14
14
  gem.add_dependency('activesupport', '>= 2.3.5')
15
- gem.add_dependency('google-spreadsheet-ruby', '>= 0.1.1')
15
+ gem.add_dependency('google-spreadsheet-ruby', '>= 0.1.2')
16
16
  gem.add_dependency('spreadsheet', '>= 0.6.4.1')
17
17
  end
18
18
 
@@ -1,10 +1,12 @@
1
+ project_base: /home/user/project
2
+
1
3
  login:
2
4
  username: fakeuser
3
5
  password: fakepass
4
6
 
5
7
  sheets:
6
8
  - name: non_existant_spreadsheet
7
- key: key_google_doc_key
9
+ key: google_doc_key
8
10
 
9
11
  output:
10
12
  yaml_path: 'data'
@@ -18,9 +20,11 @@ svn:
18
20
 
19
21
  git:
20
22
  bin: /usr/local/bin/git
21
-
22
- cron:
23
- project_base: /home/user/project
24
- version:
25
- file: /home/user/project/config/version
26
- options: -g -m -l -i
23
+
24
+ environments:
25
+ development:
26
+ cron_options: -g -m -l -i
27
+ test:
28
+ cron_options: -l
29
+ production:
30
+ cron_options: -l
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{databasion}
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brian Jones", "Istpika"]
12
- s.date = %q{2010-11-04}
12
+ s.date = %q{2010-12-03}
13
13
  s.default_executable = %q{databasion}
14
14
  s.email = %q{mojobojo@gmail.com}
15
15
  s.executables = ["databasion"]
@@ -18,7 +18,8 @@ Gem::Specification.new do |s|
18
18
  "README.md"
19
19
  ]
20
20
  s.files = [
21
- "LICENSE",
21
+ "CHANGELOG.md",
22
+ "LICENSE",
22
23
  "README.md",
23
24
  "Rakefile",
24
25
  "bin/databasion",
@@ -68,18 +69,18 @@ Gem::Specification.new do |s|
68
69
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
69
70
  s.add_runtime_dependency(%q<activerecord>, [">= 2.3.5"])
70
71
  s.add_runtime_dependency(%q<activesupport>, [">= 2.3.5"])
71
- s.add_runtime_dependency(%q<google-spreadsheet-ruby>, [">= 0.1.1"])
72
+ s.add_runtime_dependency(%q<google-spreadsheet-ruby>, [">= 0.1.2"])
72
73
  s.add_runtime_dependency(%q<spreadsheet>, [">= 0.6.4.1"])
73
74
  else
74
75
  s.add_dependency(%q<activerecord>, [">= 2.3.5"])
75
76
  s.add_dependency(%q<activesupport>, [">= 2.3.5"])
76
- s.add_dependency(%q<google-spreadsheet-ruby>, [">= 0.1.1"])
77
+ s.add_dependency(%q<google-spreadsheet-ruby>, [">= 0.1.2"])
77
78
  s.add_dependency(%q<spreadsheet>, [">= 0.6.4.1"])
78
79
  end
79
80
  else
80
81
  s.add_dependency(%q<activerecord>, [">= 2.3.5"])
81
82
  s.add_dependency(%q<activesupport>, [">= 2.3.5"])
82
- s.add_dependency(%q<google-spreadsheet-ruby>, [">= 0.1.1"])
83
+ s.add_dependency(%q<google-spreadsheet-ruby>, [">= 0.1.2"])
83
84
  s.add_dependency(%q<spreadsheet>, [">= 0.6.4.1"])
84
85
  end
85
86
  end
@@ -1,7 +1,7 @@
1
1
  Feature: Crontab
2
2
  Allow a user to run databasion updates via crontab
3
3
 
4
- Scenario: User configures the Version spreadsheet
5
- Given a version spreadsheet in Google Docs
4
+ Scenario: User configures the Environments spreadsheet
5
+ Given an environment spreadsheet in Google Docs
6
6
  When the cron system is ran and the version changes
7
7
  Then databasion should have been ran
@@ -2,11 +2,17 @@ def load_parse_data
2
2
  {
3
3
  'name' => 'mock',
4
4
  'plural' => true,
5
- 'indexes' => ["id"],
5
+ 'auto' => false,
6
+ 'indexes' => {:default => [0]},
7
+ 'primaries' => ["id", "power"],
6
8
  'fields' => ["id", "name", "power"],
7
- 'types' => ["integer", "string, 20", "string, 40"],
9
+ 'types' => ["integer", "string, 20", "string, 40, Wuss"],
8
10
  'data' => [[1, "Brian Jones", "Super Intelligence"], [2, "Superman", "Invincible"], [3, "Batman", "Strength"]],
9
11
  'ignore_cols' => [2],
10
12
  'connection' => {'database' => 'moon', 'dbname' => 'db1'}
11
13
  }
12
14
  end
15
+
16
+ def load_opts
17
+ { :env => 'development' }
18
+ end
@@ -2,21 +2,19 @@ require 'lib/databasion'
2
2
 
3
3
  Given /a set of YAML data/ do
4
4
  @config = YAML.load(File.open('config/google.yml'))
5
-
5
+ @opts = load_opts
6
6
  @parse_data = load_parse_data
7
- Databasion::YamlBuilder.run(@parse_data, @config['output']['yaml_path'])
7
+ Databasion::YamlBuilder.run(@parse_data, @config, @opts)
8
8
  end
9
9
 
10
10
  When /we run the BuildMigration system/ do
11
- Databasion::BuildMigration.run(Dir['%s/**.yml' % @config['output']['yaml_path']], @config)
11
+ Databasion::BuildMigration.run(Dir['%s/%s/**.yml' % [@opts[:env], @config['output']['yaml_path']]], @config, @opts)
12
12
  end
13
13
 
14
14
  Then /it should build a migration, model, and database file/ do
15
- File.exist?('%s/%s/100_%s_migration.rb' % [@config['output']['migrations']['path'], @parse_data['connection']['dbname'], @parse_data['name']]).should == true
16
- File.exist?('%s/%s.rb' % [@config['output']['migrations']['models'], @parse_data['name']]).should == true
17
- File.exist?('config/database.yml').should == true
18
- FileUtils.rm_rf @config['output']['migrations']['path']
19
- FileUtils.rm_rf @config['output']['migrations']['models']
20
- FileUtils.rm_rf @config['output']['yaml_path']
21
- FileUtils.rm 'config/database.yml'
15
+ File.exist?('%s/%s/%s/100_%s_migration.rb' % [@opts[:env], @config['output']['migrations']['path'], @parse_data['connection']['dbname'], @parse_data['name']]).should == true
16
+ File.exist?('%s/%s/%s.rb' % [@opts[:env], @config['output']['migrations']['models'], @parse_data['name']]).should == true
17
+ File.exist?('config/database.%s.yml' % @opts[:env]).should == true
18
+ FileUtils.rm_rf @opts[:env]
19
+ FileUtils.rm_rf 'config/database.%s.yml' % @opts[:env]
22
20
  end
@@ -1,17 +1,19 @@
1
1
  require 'lib/databasion'
2
2
 
3
- Given /a version spreadsheet in Google Docs/ do
3
+ Given /an environment spreadsheet in Google Docs/ do
4
4
  @config = YAML.load(File.open('config/google.yml'))
5
+ @opts = load_opts
5
6
  end
6
7
 
7
8
  When /the cron system is ran and the version changes/ do
8
9
  Databasion::CronSystem.config = @config
9
- Databasion::CronSystem.run
10
+ Databasion::CronSystem.run(@opts)
10
11
  end
11
12
 
12
13
  Then /databasion should have been ran/ do
13
14
  Databasion::GoogleLoader.config = @config
14
- version = Databasion::GoogleLoader.run_version
15
- File.open(@config['cron']['version']['file']).readline.strip.should == version
16
- FileUtils.rm_rf @config['cron']['version']['file']
15
+ version = Databasion::GoogleLoader.run_version(@opts)
16
+ version_file = "%s/config/version_%s" % [@config['project_base'], @opts[:env]]
17
+ File.open(version_file).readline.strip.should == version
18
+ FileUtils.rm_rf version_file
17
19
  end
@@ -2,15 +2,16 @@ require 'lib/databasion'
2
2
 
3
3
  Given /a complete set of YAML definitions/ do
4
4
  @config = YAML.load(File.open('config/google.yml'))
5
-
5
+ @opts = load_opts
6
6
  @parse_data = load_parse_data
7
7
  end
8
8
 
9
9
  When /the YAML files are parsed/ do
10
- Databasion::YamlBuilder.run(@parse_data, @config['output']['yaml_path'])
10
+ Databasion::YamlBuilder.run(@parse_data, @config, @opts)
11
11
  end
12
12
 
13
13
  Then /the result is Ruby migration files/ do
14
- File.exist?("%s/%s.yml" % [@config['output']['yaml_path'], @parse_data['name']]).should == true
15
- FileUtils.rm_rf @config['output']['yaml_path']
14
+ path = @opts[:env] + "/" + @config['output']['yaml_path']
15
+ File.exist?("%s/%s.yml" % [path, @parse_data['name']]).should == true
16
+ FileUtils.rm_rf @opts[:env]
16
17
  end
@@ -14,37 +14,37 @@ module Databasion
14
14
 
15
15
  @@config = nil
16
16
 
17
- def self.run(system, config=nil, opts=nil)
17
+ def self.run(system, opts=nil)
18
18
  LOGGER.level = Logger::INFO
19
19
 
20
- raise DatabasionError, 'Databasion requires a YAML config file path.' if config.nil?
21
- @@config = YAML.load(File.open(config))
20
+ raise DatabasionError, 'Databasion requires a YAML config file path.' if opts[:config].nil?
21
+ @@config = YAML.load(File.open(opts[:config]))
22
22
 
23
23
  case system
24
24
  when "google"
25
- run_google
25
+ run_google(opts)
26
26
  when "migrate"
27
- run_migrate
27
+ run_migrate(opts)
28
28
  when "load"
29
29
  run_load(opts)
30
30
  when "svn"
31
- run_svn
31
+ run_svn(opts)
32
32
  when "git"
33
- run_git
33
+ run_git(opts)
34
34
  when "cron"
35
- run_cron
35
+ run_cron(opts)
36
36
  end
37
37
  end
38
38
 
39
39
  private
40
- def self.run_google
40
+ def self.run_google(opts)
41
41
  Databasion::GoogleLoader.config = @@config
42
- Databasion::GoogleLoader.run
42
+ Databasion::GoogleLoader.run(opts)
43
43
  end
44
44
 
45
- def self.run_migrate
45
+ def self.run_migrate(opts)
46
46
  Databasion::Migrate.config = @@config
47
- Databasion::Migrate.run
47
+ Databasion::Migrate.run(opts)
48
48
  end
49
49
 
50
50
  def self.run_load(opts)
@@ -52,19 +52,19 @@ module Databasion
52
52
  Databasion::LoadData.run(opts)
53
53
  end
54
54
 
55
- def self.run_svn
55
+ def self.run_svn(opts)
56
56
  Databasion::SvnCommitter.config = @@config
57
57
  Databasion::SvnCommitter.commit
58
58
  end
59
59
 
60
- def self.run_git
60
+ def self.run_git(opts)
61
61
  Databasion::GitCommitter.config = @@config
62
62
  Databasion::GitCommitter.commit
63
63
  end
64
64
 
65
- def self.run_cron
65
+ def self.run_cron(opts)
66
66
  Databasion::CronSystem.config = @@config
67
- Databasion::CronSystem.run
67
+ Databasion::CronSystem.run(opts)
68
68
  end
69
69
 
70
70
  def self.set_ar_logger
@@ -23,6 +23,7 @@ module Databasion
23
23
  opt :svn, "Auto commit the project files (assuming it has been committed to SVN)"
24
24
  opt :git, "Auto commit the project files (assuming a working git repo)"
25
25
  opt :cron, "Run the version control system via crontab and update on version changes"
26
+ opt :env, "Define the environment with which to run. Default: development", :type => String
26
27
  end
27
28
  if opts[:config].nil? and opts[:create].nil?
28
29
  config = "config/google.yml"
@@ -33,6 +34,10 @@ module Databasion
33
34
  Trollop::die :config, "A YAML config must be specified"
34
35
  end
35
36
  end
37
+
38
+ if !opts[:env]
39
+ opts[:env] = 'development'
40
+ end
36
41
 
37
42
  if opts[:create]
38
43
  create_project(opts)
@@ -43,22 +48,22 @@ module Databasion
43
48
 
44
49
  def self.execute_databasion(opts)
45
50
  if opts[:cron]
46
- Databasion.run('cron', opts[:config], opts)
51
+ Databasion.run('cron', opts)
47
52
  end
48
53
  if opts[:google]
49
- Databasion.run('google', opts[:config], opts)
54
+ Databasion.run('google', opts)
50
55
  end
51
56
  if opts[:migrate]
52
- Databasion.run('migrate', opts[:config], opts)
57
+ Databasion.run('migrate', opts)
53
58
  end
54
59
  if opts[:load]
55
- Databasion.run('load', opts[:config], opts)
60
+ Databasion.run('load', opts)
56
61
  end
57
62
  if opts[:svn]
58
- Databasion.run('svn', opts[:config], opts)
63
+ Databasion.run('svn', opts)
59
64
  end
60
65
  if opts[:git]
61
- Databasion.run('git', opts[:config], opts)
66
+ Databasion.run('git', opts)
62
67
  end
63
68
  end
64
69
 
@@ -10,18 +10,18 @@ module Databasion
10
10
 
11
11
  @@migration_start = 100
12
12
 
13
- def self.run(file_list=[], config=nil)
13
+ def self.run(file_list=[], config=nil, opts=nil)
14
14
  raise BuildMigrationError, 'Databasion::BuildMigration requires an array list of files. Try GoogleLoading first.' if file_list.empty?
15
15
  raise BuildMigrationError, 'Databasion::BuildMigration requires a parsed YAML config.' if config.nil?
16
16
  @@config = config
17
-
17
+ @@environment = opts[:env]
18
18
  parse(file_list)
19
19
  end
20
20
 
21
21
  private
22
22
  def self.configure_start(dbname)
23
23
  @@migration_start = 100
24
- files = Dir[@@config['output']['migrations']['path'] + "/%s/*.rb" % dbname].collect { |file| file.split("/").pop }.sort
24
+ files = Dir[@@environment + "/" + @@config['output']['migrations']['path'] + "/%s/*.rb" % dbname].collect { |file| file.split("/").pop }.sort
25
25
  @@migration_start = files[files.size-1].split("_")[0].to_i + 1 if files.size > 0
26
26
  end
27
27
 
@@ -47,10 +47,13 @@ module Databasion
47
47
  def self.migration_class(meta)
48
48
  template = ''
49
49
  File.open(File.expand_path(File.dirname(__FILE__)) + '/templates/migration.erb', 'r') { |f| template = f.read }
50
- class_name = meta['name'].camelize
51
- table_name = meta['name']
52
- indexes = meta['indexes']
53
- fields = meta['fields']
50
+ class_name = meta['name'].camelize
51
+ table_name = meta['name']
52
+ options = meta['connection']['options']
53
+ indexes = meta['indexes'] ? meta['indexes'] : []
54
+ primaries = meta['primaries'] ? meta['primaries'] : []
55
+ fields = meta['fields']
56
+ auto = meta['auto']
54
57
 
55
58
  migration = ERB.new(template, nil, ">")
56
59
  migration.result(binding)
@@ -85,7 +88,7 @@ module Databasion
85
88
  end
86
89
 
87
90
  def self.write_migration(migration, file_name, sub_path)
88
- path = @@config['output']['migrations']['path'] + "/" + sub_path
91
+ path = @@environment + "/" + @@config['output']['migrations']['path'] + "/" + sub_path
89
92
  check_output_path(path)
90
93
  unless migration_exists?(file_name)
91
94
  f = File.new("%s/%s_%s_migration.rb" % [path, @@migration_start, file_name], 'w')
@@ -100,8 +103,9 @@ module Databasion
100
103
  end
101
104
 
102
105
  def self.write_ruby(model, file_name)
103
- check_output_path(@@config['output']['migrations']['models'])
104
- f = File.new("%s/%s.rb" % [@@config['output']['migrations']['models'], file_name], 'w')
106
+ path = @@environment + "/" + @@config['output']['migrations']['models']
107
+ check_output_path(path)
108
+ f = File.new("%s/%s.rb" % [path, file_name], 'w')
105
109
  f.write(model)
106
110
  f.close
107
111
  end
@@ -113,19 +117,18 @@ module Databasion
113
117
  config.delete('dbname')
114
118
  output[dbname] = config
115
119
  end
116
- f = File.open("config/database.yml", 'w')
120
+ f = File.open("config/database.%s.yml" % @@environment, 'w')
117
121
  f.write(YAML.dump(output))
118
122
  f.close
119
123
  Databasion::LOGGER.info "Wrote database config..."
120
124
  end
121
125
 
122
126
  def self.migration_exists?(file_name)
123
- return true if find_migration_file(file_name)
124
- false
127
+ find_migration_file(file_name) ? true : false
125
128
  end
126
129
 
127
130
  def self.find_migration_file(file_name)
128
- files = Dir[@@config['output']['migrations']['path'] + "/**/*.rb"]
131
+ files = Dir[@@environment + "/" + @@config['output']['migrations']['path'] + "/**/*.rb"]
129
132
  files.each do |file|
130
133
  chunks = file.split("/").pop.split(".")[0].split("_")
131
134
  return file if chunks[1..chunks.size-2].join("_") == file_name
@@ -8,6 +8,7 @@ module Databasion
8
8
 
9
9
  def self.config?
10
10
  raise CronSystemError, 'CronSystem cannot load without a config.' unless defined?(@@config)
11
+ raise CronSystemError, 'CronSystem now requires an environments config section.' unless defined?(@@config['environments'])
11
12
  true
12
13
  end
13
14
 
@@ -20,15 +21,17 @@ module Databasion
20
21
  @@config
21
22
  end
22
23
 
23
- def self.run
24
+ def self.run(opts)
24
25
  Databasion::GoogleLoader.config = @@config
25
- version = Databasion::GoogleLoader.run_version
26
+ version = Databasion::GoogleLoader.run_version(opts)
26
27
 
27
- if File.exist?(@@config['cron']['version']['file'])
28
- old_version = File.open(@@config['cron']['version']['file']).readline.strip
28
+ version_file = "%s/config/version_%s" % [@@config['project_base'], opts[:env]]
29
+
30
+ if File.exist?(version_file)
31
+ old_version = File.open(version_file).readline.strip
29
32
  if version > old_version
30
33
  Databasion::LOGGER.info "Version changed, running databasion."
31
- system "cd %s && databasion %s" % [@@config['cron']['project_base'], @@config['cron']['options']]
34
+ system "cd %s && %s %s" % [@@config['project_base'], File.dirname(__FILE__) + '/../../bin/databasion', @@config['environments'][opts[:env]]['cron_options']]
32
35
  write_version(version)
33
36
  elsif version < old_version
34
37
  Databasion::LOGGER.info "Version rollback is currently not implemented."
@@ -37,13 +40,13 @@ module Databasion
37
40
  end
38
41
  else
39
42
  Databasion::LOGGER.info "CronSystem running for the first time."
40
- system "cd %s && databasion %s" % [@@config['cron']['project_base'], @@config['cron']['options']]
41
- write_version(version)
43
+ system "cd %s && %s %s" % [@@config['project_base'], File.dirname(__FILE__) + '/../../bin/databasion', @@config['environments'][opts[:env]]['cron_options']]
44
+ write_version(version, version_file)
42
45
  end
43
46
  end
44
47
 
45
- def self.write_version(version)
46
- File.open(@@config['cron']['version']['file'], 'w') do |file|
48
+ def self.write_version(version, version_file)
49
+ File.open(version_file, 'w') do |file|
47
50
  file.write version
48
51
  end
49
52
  end
@@ -5,15 +5,16 @@ module Databasion
5
5
  class GoogleLoaderError < StandardError; end
6
6
 
7
7
  class GoogleLoader
8
+
9
+ @@session = nil
8
10
 
9
- @@master_sheet = 'Database'
10
- @@version_sheet = 'Version'
11
+ @@environment_sheet = 'Environments'
11
12
 
12
- @@table_def = 'table'
13
- @@field_def = 'field'
14
- @@type_def = 'type'
15
- @@index_def = 'index'
16
- @@ignore_def = 'ignore'
13
+ @@table_def = 'table'
14
+ @@field_def = 'field'
15
+ @@type_def = 'type'
16
+ @@index_def = 'index'
17
+ @@ignore_def = 'ignore'
17
18
 
18
19
  def self.config?
19
20
  raise GoogleLoaderError, 'GoogleLoader cannot load without a config.' unless defined?(@@config)
@@ -33,20 +34,12 @@ module Databasion
33
34
  @@session
34
35
  end
35
36
 
36
- def self.master_sheet=(master)
37
- @@master_sheet = master
38
- end
39
-
40
- def self.master_sheet
41
- @@master_sheet
42
- end
43
-
44
- def self.version_sheet=(version)
45
- @@version_sheet = version
37
+ def self.environment_sheet=(environment)
38
+ @@environment_sheet = environment
46
39
  end
47
40
 
48
- def self.version_sheet
49
- @@version_sheet
41
+ def self.environment_sheet
42
+ @@environment_sheet
50
43
  end
51
44
 
52
45
  def self.login
@@ -57,18 +50,19 @@ module Databasion
57
50
  end
58
51
  end
59
52
 
60
- def self.run
53
+ def self.run(opts=nil)
54
+ @@environment = opts[:env]
61
55
  config?
62
56
  login
63
57
  process.each do |data_hash|
64
- Databasion::YamlBuilder.run(data_hash, @@config['output']['yaml_path'])
58
+ Databasion::YamlBuilder.run(data_hash, @@config, opts)
65
59
  end
66
60
  end
67
61
 
68
- def self.run_version
62
+ def self.run_version(opts=nil)
69
63
  config?
70
- login
71
- fetch_version
64
+ login if session.nil?
65
+ fetch_environment_version(opts)
72
66
  end
73
67
 
74
68
  private
@@ -93,7 +87,7 @@ module Databasion
93
87
  master_list = []
94
88
  header_info = nil
95
89
  spreadsheet.worksheets.each do |worksheet|
96
- if worksheet.title == master_sheet
90
+ if worksheet.title == @@environment
97
91
  worksheet.rows.each_with_index do |row, index|
98
92
  if index == 0
99
93
  header_info = row
@@ -113,12 +107,14 @@ module Databasion
113
107
  end
114
108
 
115
109
  def self.parse(worksheet)
116
- name = ''
117
- plural = true
118
- fields = []
119
- types = []
120
- indexes = []
121
- data = []
110
+ name = ''
111
+ plural = true
112
+ auto = false
113
+ fields = []
114
+ primaries = []
115
+ types = []
116
+ indexes = {}
117
+ data = []
122
118
 
123
119
  ignore_cols = []
124
120
 
@@ -137,15 +133,34 @@ module Databasion
137
133
  end
138
134
  when @@field_def
139
135
  row.each do |field|
140
- fields.push field unless field.empty?
136
+ begin
137
+ unless field.empty?
138
+ d = field.split(",")
139
+ case d[1].strip
140
+ when 'primary'
141
+ primaries.push d[0]
142
+ when 'auto'
143
+ if d[0] == 'id'
144
+ auto = true
145
+ else
146
+ primaries.push d[0]
147
+ end
148
+ end
149
+ fields.push d[0]
150
+ end
151
+ rescue
152
+ fields.push field unless field.empty?
153
+ end
141
154
  end
142
155
  when @@type_def
143
156
  row.each do |type|
144
157
  types.push type unless type.empty?
145
158
  end
146
159
  when @@index_def
147
- row.each_with_index do |index, i|
148
- indexes.push i-1 unless index.empty? or i == 0
160
+ row.each_with_index do |field, i|
161
+ unless field.empty? or i == 0
162
+ indexes.include?(field) ? (indexes[field].push i-1) : (indexes[field] ||= [i-1])
163
+ end
149
164
  end
150
165
  when @@ignore_def
151
166
  row.each_with_index do |ignore, i|
@@ -162,6 +177,8 @@ module Databasion
162
177
  'name' => name,
163
178
  'plural' => plural,
164
179
  'fields' => fields[1..fields.size],
180
+ 'auto' => auto,
181
+ 'primaries' => primaries,
165
182
  'types' => types[1..types.size],
166
183
  'indexes' => indexes,
167
184
  'data' => data,
@@ -169,19 +186,20 @@ module Databasion
169
186
  }
170
187
  end
171
188
 
172
- def self.fetch_version
189
+ def self.fetch_environment_version(opts)
173
190
  version = nil
174
191
  @@config['sheets'].each do |token|
175
192
  spreadsheet = @@session.spreadsheet_by_key(token['key'])
176
193
  spreadsheet.worksheets.each do |worksheet|
177
- if worksheet.title == version_sheet
178
- worksheet.rows.each do |row|
179
- version = row[0]
194
+ if worksheet.title == environment_sheet
195
+ worksheet.rows.each_with_index do |row, index|
196
+ next if index == 0
197
+ version = row[1] if row[0] == opts[:env]
180
198
  end
181
199
  end
182
200
  end
183
201
  end
184
- raise GoogleLoaderError, "A Version spreadsheet was not found in any of the Google Spreadsheets supplied in google.yml" if version.nil?
202
+ raise GoogleLoaderError, "An Environments spreadsheet was not found in any of the Google Spreadsheets supplied in google.yml" if version.nil?
185
203
  version
186
204
  end
187
205
 
@@ -16,7 +16,7 @@ module Databasion
16
16
  Databasion.set_ar_logger
17
17
  Databasion::LOGGER.info "Updating from YAML..."
18
18
 
19
- models = Dir[@@config['output']['migrations']['models'] + "/*.rb"].each { |file| load file }
19
+ models = Dir[opts[:env] + "/" + @@config['output']['migrations']['models'] + "/*.rb"].each { |file| load file }
20
20
 
21
21
  models.each do |model|
22
22
  f = model.split('/')
@@ -27,9 +27,9 @@ module Databasion
27
27
  Databasion::LOGGER.info "Loading %s into database..." % camel_name
28
28
 
29
29
  begin
30
- yaml_file = YAML.load_file('%s/%s.yml' % [@@config['output']['yaml_path'], plural_name])
30
+ yaml_file = YAML.load_file('%s/%s.yml' % [opts[:env] + "/" + @@config['output']['yaml_path'], plural_name])
31
31
  rescue
32
- yaml_file = YAML.load_file('%s/%s.yml' % [@@config['output']['yaml_path'], normal_name])
32
+ yaml_file = YAML.load_file('%s/%s.yml' % [opts[:env] + "/" + @@config['output']['yaml_path'], normal_name])
33
33
  end
34
34
 
35
35
  for row in yaml_file['data']
@@ -8,18 +8,18 @@ module Databasion
8
8
  @@config = config
9
9
  end
10
10
 
11
- def self.run
11
+ def self.run(opts)
12
12
  require 'migration_helpers/init'
13
13
 
14
- files = Dir["%s/*.yml" % @@config['output']['yaml_path']]
15
- Databasion::BuildMigration.run(files, @@config)
14
+ files = Dir["%s/%s/*.yml" % [opts[:env], @@config['output']['yaml_path']]]
15
+ Databasion::BuildMigration.run(files, @@config, opts)
16
16
 
17
17
  Databasion.set_ar_logger
18
18
  Databasion::LOGGER.info "Migrating..."
19
19
 
20
- YAML.load_file('config/database.yml').each do |config|
20
+ YAML.load_file('config/database.%s.yml' % opts[:env]).each do |config|
21
21
  ActiveRecord::Base.establish_connection(config[1])
22
- path = @@config['output']['migrations']['path'] + "/" + config[0]
22
+ path = opts[:env] + "/" + @@config['output']['migrations']['path'] + "/" + config[0]
23
23
  ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
24
24
  ActiveRecord::Migrator.migrate(path, ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
25
25
  end
@@ -1,17 +1,15 @@
1
1
  class <%= class_name %>Migration < ActiveRecord::Migration
2
2
  def self.up
3
- create_table :<%= table_name %><%= ', :id => false' if fields.collect {|f| f['field']}.include?('id') %> do |t|
3
+ create_table :<%= table_name %><%= ', :id => false' if !auto %><%= ', :options => "%s"' % options if options %> do |t|
4
4
  <% for field in fields %>
5
- t.<%= field['type'] %> :<%= field['field'] %><%= ', :limit => %s' % field['size'] if field['size'] %><%= "\n" %>
5
+ t.<%= field['type'] %> :<%= field['field'] %><%= ', :limit => %s' % field['size'] if field['size'] %><%= ', :default => "%s"' % field['default'] if field['default'] %><%= "\n" %>
6
6
  <% end %>
7
7
  end
8
- <% if fields.collect {|f| f['field']}.include?('id') %>
9
- execute "ALTER TABLE <%= table_name %> ADD PRIMARY KEY (id)"
8
+ <% if primaries.size > 0 and !auto %>
9
+ execute "ALTER TABLE <%= table_name %> ADD PRIMARY KEY (<%= primaries.collect {|p| "%s, " % p }.to_s.strip.chop %>)"
10
10
  <% end %>
11
- <% if indexes.size > 1 %>
12
- add_index :<%= table_name %>, [<%= indexes.collect {|i| ":%s" % i }.join(",") %>]
13
- <% elsif indexes.size == 1 %>
14
- add_index :<%= table_name %>, :<%= indexes[0] %><%= "\n" %>
11
+ <% indexes.each do |index| %>
12
+ add_index :<%= table_name %>, [<%= index['index'].collect {|i| ":%s" % i }.join(", ") %>]
15
13
  <% end %>
16
14
  end
17
15
  def self.down
@@ -6,9 +6,10 @@ module Databasion
6
6
 
7
7
  class YamlBuilder
8
8
 
9
- def self.run(data_hash, output_path=nil)
10
- raise YamalizeError, 'Databasion::YamlBuilder requires an output path.' if output_path.nil?
11
- @@output_path = output_path
9
+ def self.run(data_hash, config=nil, opts=nil)
10
+ raise YamalizeError, 'Databasion::YamlBuilder requires an output path.' if config['output']['yaml_path'].nil?
11
+ @@config = config
12
+ @@environment = opts[:env]
12
13
 
13
14
  Databasion::LOGGER.info "Yamlbating %s..." % data_hash['name']
14
15
 
@@ -31,14 +32,19 @@ module Databasion
31
32
  yaml_output += " size: %s\n" % type_data[1] if type_data[1]
32
33
  yaml_output += " default: %s\n" % type_data[2] if type_data[2]
33
34
  end
34
- indexes = ''
35
- data_hash['indexes'].each_with_index do |index, i|
36
- indexes += data_hash['fields'][i] + ","
35
+ yaml_output += " auto: %s\n" % data_hash['auto']
36
+ yaml_output += " indexes:\n"
37
+ data_hash['indexes'].each do |key, indexes|
38
+ index_list = []
39
+ indexes.each do |index|
40
+ index_list.push data_hash['fields'][index] unless data_hash['ignore_cols'].include?(index)
41
+ end
42
+ yaml_output += " - index: [%s]\n" % index_list.join(", ")
37
43
  end
38
- yaml_output += " indexes: [%s]\n" % indexes.chop
44
+ yaml_output += " primaries: [%s]\n" % data_hash['primaries'].join(", ") unless data_hash['primaries'].empty?
39
45
  yaml_output += " connection:\n"
40
46
  data_hash['connection'].each do |key, value|
41
- yaml_output += " %s: %s\n" % [key, value] unless ['spreadsheet', 'options'].include?(key)
47
+ yaml_output += " %s: %s\n" % [key, value] unless ['spreadsheet'].include?(key)
42
48
  end
43
49
  yaml_output += "\n"
44
50
  end
@@ -61,15 +67,16 @@ module Databasion
61
67
  end
62
68
 
63
69
  def self.write(file_name, yaml_output)
64
- check_output_path
65
- f = File.new("%s/%s.yml" % [@@output_path, file_name], 'w')
70
+ path = @@environment + "/" + @@config['output']['yaml_path']
71
+ check_output_path(path)
72
+ f = File.new("%s/%s.yml" % [path, file_name], 'w')
66
73
  f.write(yaml_output)
67
74
  f.close
68
75
  end
69
76
 
70
- def self.check_output_path
71
- unless File.exist?(@@output_path)
72
- FileUtils.mkdir_p(@@output_path)
77
+ def self.check_output_path(path)
78
+ unless File.exist?(path)
79
+ FileUtils.mkdir_p(path)
73
80
  end
74
81
  end
75
82
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: databasion
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brian Jones
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-11-04 00:00:00 +09:00
19
+ date: 2010-12-03 00:00:00 +09:00
20
20
  default_executable: databasion
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -59,12 +59,12 @@ dependencies:
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- hash: 25
62
+ hash: 31
63
63
  segments:
64
64
  - 0
65
65
  - 1
66
- - 1
67
- version: 0.1.1
66
+ - 2
67
+ version: 0.1.2
68
68
  type: :runtime
69
69
  version_requirements: *id003
70
70
  - !ruby/object:Gem::Dependency
@@ -94,6 +94,7 @@ extra_rdoc_files:
94
94
  - LICENSE
95
95
  - README.md
96
96
  files:
97
+ - CHANGELOG.md
97
98
  - LICENSE
98
99
  - README.md
99
100
  - Rakefile