active_record-annotate 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
@@ -0,0 +1,7 @@
1
+ guard :rspec, all_on_start: true, all_after_pass: true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+
6
+ notification :terminal_notifier, activate: 'com.googlecode.iTerm2'
7
+ end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # ActiveRecord::Annotate
1
+ # ActiveRecord::Annotate [![Build Status](https://travis-ci.org/7even/active_record-annotate.png)](https://travis-ci.org/7even/active_record-annotate) [![Code Climate](https://codeclimate.com/github/7even/active_record-annotate.png)](https://codeclimate.com/github/7even/active_record-annotate)
2
2
 
3
- `ActiveRecord::Annotate` is a simple `ActiveRecord` plugin for annotating your rails models. It is based on `ActiveRecord::SchemaDumper` so the annotation format is very close to what you see in `db/schema.rb`.
3
+ ActiveRecord::Annotate is a simple ActiveRecord plugin for annotating your rails models. It is based on `ActiveRecord::SchemaDumper` (a core ActiveRecord class responsible for creating `db/schema.rb`) so the annotation format is very close to what you write in your migrations.
4
4
 
5
5
  ## Installation
6
6
 
@@ -9,7 +9,8 @@ Trivial.
9
9
  ``` ruby
10
10
  # Gemfile
11
11
  group :development do
12
- gem 'active_record-annotate'
12
+ # you don't want to annotate your models in production, do you?
13
+ gem 'active_record-annotate', '~> 0.2'
13
14
  end
14
15
  ```
15
16
 
@@ -19,9 +20,15 @@ $ bundle
19
20
 
20
21
  ## Usage
21
22
 
22
- Gem adds a simple `db:annotate` rake task - it just writes the annotation to the top of each model file in a comment block. Magic encoding comment is preserved.
23
+ Gem adds a simple `db:annotate` rake task - it just writes the annotation to the top of each model file in a comment block (magic encoding comment is preserved).
23
24
 
24
- This is what it looks like:
25
+ Once you install the gem into your application it hooks `db:annotate` to run after each `db:migrate` / `db:rollback` to keep the annotations in sync with the DB schema, but if you added a new model without migrating (or just accidentally messed up with something) you can always run the annotation process by hand:
26
+
27
+ ``` sh
28
+ $ rake db:annotate
29
+ ```
30
+
31
+ This is what a common annotation looks like:
25
32
 
26
33
  ``` ruby
27
34
  # create_table :documents, force: true do |t|
@@ -38,12 +45,17 @@ class Document < ActiveRecord::Base
38
45
  # ...
39
46
  ```
40
47
 
48
+ ## Changelog
49
+
50
+ * 0.1 Initial version with core functionality
51
+ * 0.1.1 Support for several models per table
52
+ * 0.2 Auto-annotation after `db:migrate` & `db:rollback`, basic output
53
+
41
54
  ## Roadmap
42
55
 
43
56
  * Cover everything with tests
44
57
  * Write YARD docs
45
58
  * Add some means to configure the annotation process (annotation format, a place to put it)
46
- * Try to add auto-annotation after `db:migrate`
47
59
 
48
60
  ## Contributing
49
61
 
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -22,4 +22,9 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.3'
24
24
  spec.add_development_dependency 'rake'
25
+
26
+ spec.add_development_dependency 'guard-rspec'
27
+ spec.add_development_dependency 'terminal-notifier-guard'
28
+ spec.add_development_dependency 'pry'
29
+ spec.add_development_dependency 'awesome_print'
25
30
  end
@@ -1,5 +1,5 @@
1
1
  require 'active_record/annotate/dumper'
2
- require 'active_record/annotate/writer'
2
+ require 'active_record/annotate/file'
3
3
  require 'active_record/annotate/version'
4
4
  require 'active_record/annotate/railtie'
5
5
 
@@ -7,17 +7,31 @@ module ActiveRecord
7
7
  module Annotate
8
8
  class << self
9
9
  def annotate
10
- models.each do |table_name, file_paths|
10
+ processed_models = []
11
+
12
+ models.each do |table_name, file_paths_and_classes|
11
13
  annotation = Dumper.dump(table_name)
12
14
 
13
- file_paths.each do |path|
14
- Writer.write(annotation, path)
15
+ file_paths_and_classes.each do |path, klass|
16
+ file = File.new(path)
17
+ file.annotate_with(annotation)
18
+
19
+ if file.changed?
20
+ file.write
21
+ processed_models << "#{klass} (#{file.relative_path})"
22
+ end
23
+ end
24
+ end
25
+
26
+ unless processed_models.empty?
27
+ puts 'Annotated models:'
28
+ processed_models.each do |model|
29
+ puts " * #{model}"
15
30
  end
16
31
  end
17
32
  end
18
33
 
19
34
  def models
20
- models_dir = Rails.root.join('app/models')
21
35
  files_mask = models_dir.join('**', '*.rb')
22
36
 
23
37
  hash_with_arrays = Hash.new do |hash, key|
@@ -25,17 +39,30 @@ module ActiveRecord
25
39
  end
26
40
 
27
41
  Dir.glob(files_mask).each_with_object(hash_with_arrays) do |path, models|
28
- # .../app/models/car/hatchback.rb -> car/hatchback
29
- short_path = path.sub(models_dir.to_s + '/', '').sub(/\.rb$/, '')
30
- # skip any app/models/concerns files
31
- next if short_path.starts_with?('concerns')
42
+ short_path = short_path_for(path)
43
+ next if short_path.starts_with?('concerns') # skip any app/models/concerns files
44
+
45
+ klass = class_name_for(short_path)
46
+ next unless klass < ActiveRecord::Base # collect only AR::Base descendants
32
47
 
33
- # car/hatchback -> Car::Hatchback
34
- klass = short_path.camelize.constantize
35
- # collect only AR::Base descendants
36
- models[klass.table_name] << path if klass < ActiveRecord::Base
48
+ models[klass.table_name] << [path, klass]
37
49
  end
38
50
  end
51
+
52
+ # .../app/models/car/hatchback.rb -> car/hatchback
53
+ def short_path_for(full_path)
54
+ full_path.sub(models_dir.to_s + '/', '').sub(/\.rb$/, '')
55
+ end
56
+
57
+ # car/hatchback -> Car::Hatchback
58
+ def class_name_for(short_path)
59
+ short_path.camelize.constantize
60
+ end
61
+
62
+ private
63
+ def models_dir
64
+ Rails.root.join('app/models')
65
+ end
39
66
  end
40
67
  end
41
68
  end
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ module Annotate
3
+ class File
4
+ attr_reader :path, :lines
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ @content = ::File.read(path)
9
+ @lines = @content.split(?\n)
10
+ end
11
+
12
+ def annotate_with(annotation)
13
+ if @lines.first =~ /^\s*#.*coding/
14
+ # encoding: utf-8 encountered on the first line
15
+ encoding_line = @lines.shift
16
+ end
17
+
18
+ while @lines.first.start_with?('#') || @lines.first.blank?
19
+ # throw out comments and empty lines
20
+ # in the beginning of the file (old annotation)
21
+ @lines.shift
22
+ end
23
+
24
+ @lines.unshift(*annotation, nil)
25
+ @lines.unshift(encoding_line) unless encoding_line.nil?
26
+ @lines.push(nil) # newline at the end of file
27
+ end
28
+
29
+ def write
30
+ new_file_content = @lines.join(?\n)
31
+ temp_path = "#{@path}.annotated"
32
+
33
+ ::File.open(temp_path, 'w') do |temp_file|
34
+ temp_file.write(new_file_content)
35
+ end
36
+
37
+ ::File.delete(@path)
38
+ ::File.rename(temp_path, @path)
39
+ end
40
+
41
+ def changed?
42
+ @lines.join(?\n) != @content
43
+ end
44
+
45
+ def relative_path
46
+ path.sub(/^#{Rails.root}\//, '')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Annotate
3
- VERSION = '0.1.1'
3
+ VERSION = '0.2'
4
4
  end
5
5
  end
@@ -4,3 +4,9 @@ namespace :db do
4
4
  ActiveRecord::Annotate.annotate
5
5
  end
6
6
  end
7
+
8
+ %w(db:migrate db:rollback).each do |task_name|
9
+ Rake::Task[task_name].enhance do
10
+ Rake::Task['db:annotate'].invoke
11
+ end
12
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Annotate::File do
4
+ let(:file_path) do
5
+ file = Tempfile.new('test_annotation')
6
+ File.open(file.path, 'w') do |tempfile|
7
+ tempfile.write(<<-FILE)
8
+ # encoding: utf-8
9
+ # create_table :users do |t|
10
+ # t.string :name
11
+ # t.integer :age
12
+ # end
13
+
14
+ class User < ActiveRecord::Base
15
+ has_many :posts
16
+ end
17
+ FILE
18
+ end
19
+
20
+ file.path
21
+ end
22
+
23
+ let(:old_annotation) do
24
+ [
25
+ '# create_table :users do |t|',
26
+ '# t.string :name',
27
+ '# t.integer :age',
28
+ '# end'
29
+ ]
30
+ end
31
+
32
+ let(:new_annotation) do
33
+ [
34
+ '# create_table :users do |t|',
35
+ '# t.string :name',
36
+ '# t.integer :age, null: false, default: 0',
37
+ '# end'
38
+ ]
39
+ end
40
+
41
+ let(:expected_result) do
42
+ <<-FILE
43
+ # encoding: utf-8
44
+ # create_table :users do |t|
45
+ # t.string :name
46
+ # t.integer :age, null: false, default: 0
47
+ # end
48
+
49
+ class User < ActiveRecord::Base
50
+ has_many :posts
51
+ end
52
+ FILE
53
+ end
54
+
55
+ let(:file) { ActiveRecord::Annotate::File.new(file_path) }
56
+
57
+ describe "#annotate_with" do
58
+ it "changes the lines adding the new annotation" do
59
+ file.annotate_with(new_annotation)
60
+
61
+ processed_file_content = file.lines.join(?\n)
62
+ expect(processed_file_content).to eq(expected_result)
63
+ end
64
+ end
65
+
66
+ describe "#changed?" do
67
+ context "with an old annotation" do
68
+ before(:each) do
69
+ file.annotate_with(old_annotation)
70
+ end
71
+
72
+ it "returns false" do
73
+ expect(file).not_to be_changed
74
+ end
75
+ end
76
+
77
+ context "with a new annotation" do
78
+ before(:each) do
79
+ file.annotate_with(new_annotation)
80
+ end
81
+
82
+ it "returns true" do
83
+ expect(file).to be_changed
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "#write" do
89
+ before(:each) do
90
+ file.annotate_with(new_annotation)
91
+ end
92
+
93
+ it "writes the new contents to file" do
94
+ file.write
95
+
96
+ new_file_contents = ::File.read(file.path)
97
+ expect(new_file_contents).to eq(expected_result)
98
+ end
99
+ end
100
+
101
+ describe "#relative_path" do
102
+ before(:each) do
103
+ Rails.stub(:root).and_return('root')
104
+ file.stub(:path).and_return('root/namespace/path.rb')
105
+ end
106
+
107
+ it "returns a relative path" do
108
+ expect(file.relative_path).to eq('namespace/path.rb')
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Annotate do
4
+ describe ".short_path_for" do
5
+ before(:each) do
6
+ subject.stub(:models_dir).and_return('dir')
7
+ end
8
+
9
+ it "removes the root/app/models prefix and .rb suffix" do
10
+ short_path = subject.short_path_for('dir/namespace/model_name.rb')
11
+ expect(short_path).to eq('namespace/model_name')
12
+ end
13
+ end
14
+
15
+ describe ".class_name_for" do
16
+ it "finds the class by the short path" do
17
+ class_name = subject.class_name_for('active_record/annotate/file')
18
+ expect(class_name).to eq(ActiveRecord::Annotate::File)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ require 'active_record'
2
+ require 'active_record/annotate'
3
+ require 'pry'
4
+ require 'awesome_print'
metadata CHANGED
@@ -1,32 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record-annotate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: '0.2'
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Vsevolod Romashov
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-11-15 00:00:00.000000000 Z
12
+ date: 2013-11-20 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rails
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - '>='
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
21
  version: '3.2'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - '>='
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '3.2'
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: bundler
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ~>
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ~>
39
44
  - !ruby/object:Gem::Version
@@ -41,15 +46,81 @@ dependencies:
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: rake
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
- - - '>='
51
+ - - ! '>='
46
52
  - !ruby/object:Gem::Version
47
53
  version: '0'
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
- - - '>='
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: guard-rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: terminal-notifier-guard
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: pry
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: awesome_print
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
53
124
  - !ruby/object:Gem::Version
54
125
  version: '0'
55
126
  description: Adds a rake task which prepends each model file with an excerpt about
@@ -61,39 +132,55 @@ extensions: []
61
132
  extra_rdoc_files: []
62
133
  files:
63
134
  - .gitignore
135
+ - .rspec
136
+ - .travis.yml
64
137
  - Gemfile
138
+ - Guardfile
65
139
  - LICENSE.txt
66
140
  - README.md
67
141
  - Rakefile
68
142
  - active_record-annotate.gemspec
69
143
  - lib/active_record/annotate.rb
70
144
  - lib/active_record/annotate/dumper.rb
145
+ - lib/active_record/annotate/file.rb
71
146
  - lib/active_record/annotate/railtie.rb
72
147
  - lib/active_record/annotate/version.rb
73
- - lib/active_record/annotate/writer.rb
74
148
  - lib/tasks/annotate.rake
149
+ - spec/active_record/annotate/file_spec.rb
150
+ - spec/active_record/annotate_spec.rb
151
+ - spec/spec_helper.rb
75
152
  homepage: https://github.com/7even/active_record-annotate
76
153
  licenses:
77
154
  - MIT
78
- metadata: {}
79
155
  post_install_message:
80
156
  rdoc_options: []
81
157
  require_paths:
82
158
  - lib
83
159
  required_ruby_version: !ruby/object:Gem::Requirement
160
+ none: false
84
161
  requirements:
85
- - - '>='
162
+ - - ! '>='
86
163
  - !ruby/object:Gem::Version
87
164
  version: '0'
165
+ segments:
166
+ - 0
167
+ hash: 3616105738017822554
88
168
  required_rubygems_version: !ruby/object:Gem::Requirement
169
+ none: false
89
170
  requirements:
90
- - - '>='
171
+ - - ! '>='
91
172
  - !ruby/object:Gem::Version
92
173
  version: '0'
174
+ segments:
175
+ - 0
176
+ hash: 3616105738017822554
93
177
  requirements: []
94
178
  rubyforge_project:
95
- rubygems_version: 2.0.3
179
+ rubygems_version: 1.8.23
96
180
  signing_key:
97
- specification_version: 4
181
+ specification_version: 3
98
182
  summary: ActiveRecord models annotator based on rails' schema dumper
99
- test_files: []
183
+ test_files:
184
+ - spec/active_record/annotate/file_spec.rb
185
+ - spec/active_record/annotate_spec.rb
186
+ - spec/spec_helper.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 0d4d19dd9aa4aa53ab77fbb3e956de2a2ed0f400
4
- data.tar.gz: 8ad179c14a5499aa39ae090b5d1a8e5ccaa6924e
5
- SHA512:
6
- metadata.gz: b584d252701c53b7153e3977825e95722902edd654ef0eb1a5fd02a093ecdc00c2276b182b338252cba9f37727384ac7f0c8b0df128c3abacfaef9155862adaf
7
- data.tar.gz: 1efd1c3364ae8dfe5f7758e6d7aba2d39b71f11e7a9fdb9544ab397260c35bbcaac90655d9dea94817bf848a9ce0d0f2835f885819a660cce503103280f12584
@@ -1,38 +0,0 @@
1
- module ActiveRecord
2
- module Annotate
3
- module Writer
4
- class << self
5
- def write(annotation, path)
6
- new_file_content = assemble(annotation, path)
7
-
8
- temp_path = "#{path}.annotated"
9
- File.open(temp_path, 'w') do |temp_file|
10
- temp_file.puts(new_file_content)
11
- end
12
-
13
- File.delete(path)
14
- File.rename(temp_path, path)
15
- end
16
-
17
- def assemble(annotation, path)
18
- lines = File.read(path).split("\n")
19
-
20
- if lines.first =~ /^\s*#.*coding/
21
- # encoding: utf-8 encountered on the first line
22
- encoding_line = lines.shift
23
- end
24
-
25
- while lines.first.starts_with?('#') || lines.first.blank?
26
- # throw out comments and empty lines in the beginning of the file (old annotation)
27
- lines.shift
28
- end
29
-
30
- lines.unshift(*annotation, nil)
31
- lines.unshift(encoding_line) unless encoding_line.nil?
32
-
33
- lines.join("\n")
34
- end
35
- end
36
- end
37
- end
38
- end