active_record-annotate 0.1.1 → 0.2

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