activerecord-column_metadata 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ spec/*.log
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord-json_metadata.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-onwards Tutuf Ltd.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # ActiveRecord::ColumnMetadata
2
+
3
+ Store metadata about columns in the database, as comments on columns (advanced PostgreSQL feature)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'activerecord-column_metadata'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install activerecord-column_metadata
18
+
19
+ ## Usage
20
+
21
+ In a migration:
22
+
23
+ create_table(:cars) do |t|
24
+ t.string :name
25
+ t.string :manufacturer
26
+ t.string :model
27
+
28
+ t.boolean :4wd
29
+ t.boolean :abs
30
+ t.boolean :airbag
31
+ t.boolean :gps
32
+ t.boolean :music_player
33
+
34
+ [:4wd, :abs, :airbag, :gps, :music_player].each do |extra|
35
+ t.column_metadata(extra, {extra: true})
36
+ end
37
+ end
38
+
39
+ or
40
+
41
+ add_column :cars, :aircondition, :boolean, metadata: {extra: true}
42
+
43
+ And then you can say in the model:
44
+
45
+ class Car < ActiveRecord::Base
46
+ def self.extras
47
+ ActiveRecord::JsonMetadata.to_a("cars").select do |arr|
48
+ column_name, metadata = arr
49
+ column_name if metadata["extra"] == true
50
+ end
51
+ end
52
+ end
53
+
54
+ ## Rationale
55
+
56
+ In the above example you could hardcode extras in the model:
57
+
58
+ class Car < ActiveRecord::Base
59
+ def self.extras
60
+ [:4wd, :abs, :airbag, :gps, :music_player]
61
+ end
62
+ end
63
+
64
+ But what will happen when you have to add or remove an extra? You have to edit two files: migration and the corresponding ActiveRecord model. Unnecessary repetition
65
+ causes brain injury.
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core'
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :default => :spec
6
+
7
+ desc "Run all specs in spec directory"
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ namespace :test do
11
+ desc 'Build the test database'
12
+ task :create_database do
13
+ %x( createdb -U postgres activerecord-column_metadata_test )
14
+ end
15
+
16
+ desc 'Drop the test database'
17
+ task :drop_database do
18
+ %x( dropdb -U postgres activerecord-column_metadata_test )
19
+ end
20
+
21
+ desc 'Rebuild the test database'
22
+ task :rebuild_database => [:drop_database, :create_database]
23
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_record/column_metadata/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "activerecord-column_metadata"
8
+ spec.version = ActiveRecord::ColumnMetadata::VERSION
9
+ spec.authors = ["Sava Chankov", "Tsanka Encheva"]
10
+ spec.email = ["sava@tutuf.com", "tsanka@tutuf.com"]
11
+ spec.description = %q{ActiveRecord extension to store column metadata in database as comments on columns}
12
+ spec.summary = %q{ActiveRecord extension to store column metadata in database}
13
+ spec.homepage = "http://github.com/tutuf/activerecord-column_metadata"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", "~>3.0.3"
22
+ spec.add_dependency "pg"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "database_cleaner"
28
+ end
@@ -0,0 +1,4 @@
1
+ require "active_record"
2
+ require "active_record/column_metadata/version"
3
+ require "active_record/column_metadata/read"
4
+ require "active_record/column_metadata/write"
@@ -0,0 +1,20 @@
1
+ require 'json'
2
+
3
+ module ActiveRecord
4
+ class ColumnMetadata
5
+ # Retrieves metadata stored in the database for +table_name+ as
6
+ # [["col_name", {"key" => "val"}], ...]
7
+ def self.to_a(table_name)
8
+ res = ActiveRecord::Base.connection.query(<<-END_SQL, 'Get comments')
9
+ SELECT a.attname,
10
+ pg_catalog.col_description(a.attrelid, a.attnum)
11
+ FROM pg_catalog.pg_attribute a
12
+ WHERE a.attrelid = '#{table_name}'::regclass
13
+ AND a.attnum > 0
14
+ AND NOT a.attisdropped
15
+ ORDER BY a.attname
16
+ END_SQL
17
+ res.map{ |tupple| [tupple.first, JSON.parse(tupple.last)] unless tupple.last.nil? }.compact
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ class ColumnMetadata
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_record/connection_adapters/abstract/schema_definitions'
2
+ require 'active_record/connection_adapters/abstract/schema_statements'
3
+ require 'active_record/connection_adapters/postgresql_adapter'
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ class TableDefinition
8
+ module ColumnMetadata
9
+ def column_metadata(column, hash)
10
+ add_comment(column, hash)
11
+ end
12
+
13
+ def add_comment(column, comment)
14
+ @comments ||= []
15
+ @comments << [column, comment]
16
+ end
17
+
18
+ def comments
19
+ @comments
20
+ end
21
+ end
22
+
23
+ include ColumnMetadata
24
+ end
25
+
26
+ module SchemaStatements
27
+ # works only with databases that support comments on columns
28
+ def create_table(table_name,options={},&block)
29
+ table_definition = TableDefinition.new(self)
30
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
31
+
32
+ yield table_definition if block_given?
33
+
34
+ if options[:force] && table_exists?(table_name)
35
+ drop_table(table_name, options)
36
+ end
37
+
38
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
39
+ create_sql << "#{quote_table_name(table_name)} ("
40
+ create_sql << table_definition.to_sql
41
+ create_sql << ") #{options[:options]}"
42
+ execute create_sql
43
+ table_definition.comments.each{ |c| write_json_comment(table_name, c.to_a.first, c.to_a.last) } if table_definition.comments
44
+ end
45
+ end
46
+
47
+ class PostgreSQLAdapter < AbstractAdapter
48
+ def add_column_with_metadata(*args)
49
+ add_column_without_metadata(*args)
50
+ options = args.extract_options!
51
+ write_json_comment(args[0], args[1], options[:metadata])
52
+ end
53
+ alias_method_chain :add_column, :metadata
54
+
55
+ def write_json_comment(table_name, column_name, comment)
56
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment.to_json)}"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::ColumnMetadata do
4
+ it 'should have a version number' do
5
+ ActiveRecord::ColumnMetadata::VERSION.should_not be_nil
6
+ end
7
+
8
+ def get_comments(table_name)
9
+ ActiveRecord::Base.connection.query(<<-END_SQL, 'Get comments')
10
+ SELECT a.attname,
11
+ pg_catalog.col_description(a.attrelid, a.attnum)
12
+ FROM pg_catalog.pg_attribute a
13
+ WHERE a.attrelid = '#{table_name}'::regclass
14
+ AND a.attnum > 0
15
+ AND NOT a.attisdropped
16
+ ORDER BY a.attname
17
+ END_SQL
18
+ end
19
+
20
+ class CreateTableMigration < ActiveRecord::Migration
21
+ def self.up
22
+ create_table(:people) do |t|
23
+ t.string :name
24
+ t.column_metadata(:name, {:something => true})
25
+ end
26
+ end
27
+ end
28
+
29
+ class AddColumnMigration < ActiveRecord::Migration
30
+ def self.up
31
+ add_column :people, :surname, :string, :metadata => {full_text_search: "double_metaphone"}
32
+ end
33
+ end
34
+
35
+ describe 'write should add JSON in comment' do
36
+ it 'with create_table' do
37
+ CreateTableMigration.up
38
+ res = get_comments("people")
39
+ expect(res).to include( ["name",'{"something":true}'])
40
+ end
41
+
42
+ it 'with add_collumn' do
43
+ CreateTableMigration.up
44
+ AddColumnMigration.up
45
+ res = get_comments("people")
46
+ expect(res).to include( ['surname', '{"full_text_search":"double_metaphone"}'])
47
+ end
48
+ end
49
+
50
+ describe 'read' do
51
+ it '#to_a should return a column comments as an array of arrays' do
52
+ CreateTableMigration.up
53
+ res = ActiveRecord::ColumnMetadata.to_a(:people)
54
+ res.should == [["name", {"something" => true}]]
55
+ end
56
+ end
57
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ postgresql:
2
+ adapter: postgresql
3
+ username: postgres
4
+ password: postgres
5
+ database: activerecord-column_metadata_test
6
+ min_messages: ERROR
@@ -0,0 +1,31 @@
1
+ require 'logger'
2
+ require 'rspec'
3
+ require 'database_cleaner'
4
+ require 'active_record'
5
+
6
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
7
+ require 'active_record/column_metadata'
8
+
9
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
10
+ ActiveRecord::Base.configurations = YAML::load_file(File.dirname(__FILE__) + '/database.yml')
11
+ ActiveRecord::Base.establish_connection(ENV['DB'] || 'postgresql')
12
+
13
+ ActiveRecord::Base.silence do
14
+ ActiveRecord::Migration.verbose = false
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+
19
+ config.before(:suite) do
20
+ DatabaseCleaner.strategy = :transaction
21
+ end
22
+
23
+ config.before(:each) do
24
+ DatabaseCleaner.start
25
+ end
26
+
27
+ config.after(:each) do
28
+ DatabaseCleaner.clean
29
+ end
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-column_metadata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sava Chankov
9
+ - Tsanka Encheva
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-07-12 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 3.0.3
31
+ - !ruby/object:Gem::Dependency
32
+ name: pg
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '1.3'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rake
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: rspec
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: database_cleaner
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: ActiveRecord extension to store column metadata in database as comments
112
+ on columns
113
+ email:
114
+ - sava@tutuf.com
115
+ - tsanka@tutuf.com
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - .gitignore
121
+ - .rspec
122
+ - .travis.yml
123
+ - Gemfile
124
+ - LICENSE.txt
125
+ - README.md
126
+ - Rakefile
127
+ - activerecord-column_metadata.gemspec
128
+ - lib/active_record/column_metadata.rb
129
+ - lib/active_record/column_metadata/read.rb
130
+ - lib/active_record/column_metadata/version.rb
131
+ - lib/active_record/column_metadata/write.rb
132
+ - spec/activerecord_column_metadata_spec.rb
133
+ - spec/database.yml
134
+ - spec/spec_helper.rb
135
+ homepage: http://github.com/tutuf/activerecord-column_metadata
136
+ licenses:
137
+ - MIT
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ! '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 1.8.25
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: ActiveRecord extension to store column metadata in database
160
+ test_files:
161
+ - spec/activerecord_column_metadata_spec.rb
162
+ - spec/database.yml
163
+ - spec/spec_helper.rb
164
+ has_rdoc: