tag_bearer 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c274ae2e03ea354c1015d765e2595f7098ce14d3
4
+ data.tar.gz: 9eacd3799ec06ad8624c38bac9b5bad2d160c1d5
5
+ SHA512:
6
+ metadata.gz: ccb008993f158392321469c90bb0fe1e74cb2003bf45968980eb2a50e3088db9237c5e643eb27cc8060dc4edf4c13a473081de9ac573977c5115f5e2e012988f
7
+ data.tar.gz: d702feb3545219c8aa7caa9655a66005ba186be7b8e4f15e4d516473ad3efc78b96d56e6adf2306490b718db2e5b03ceb27abb30603c150755ae3e24ad5ba14e
@@ -0,0 +1,20 @@
1
+ Copyright 2015 David Jellesma
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,20 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'TagBearer'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ Bundler::GemHelper.install_tasks
18
+ require 'rspec/core/rake_task'
19
+ task :default => :spec
20
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,41 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module TagBearer
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ desc 'Create migrations for TagBearer'
10
+
11
+ def copy_migrations
12
+ copy_migration 'create_tags'
13
+ end
14
+
15
+ def self.next_migration_number(path)
16
+ unless @prev_migration_nr
17
+ @prev_migration_nr = Time.now.utc.strftime('%Y%m%d%H%M%S').to_i
18
+ else
19
+ @prev_migration_nr += 1
20
+ end
21
+ @prev_migration_nr.to_s
22
+ end
23
+
24
+ def self.source_root
25
+ @source_root ||= File.join(File.dirname(__FILE__), 'templates')
26
+ end
27
+
28
+ protected
29
+
30
+ def copy_migration(filename)
31
+ if self.class.migration_exists?('db/migrate', "#{filename}")
32
+ say_status('skipped', "Migration #{filename}.rb already exists")
33
+ else
34
+ migration_template "#{filename}.rb", "db/migrate/#{filename}.rb"
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,10 @@
1
+ class CreateTags < ActiveRecord::Migration
2
+ def change
3
+ create_table(:tags) do |t|
4
+ t.string :key, required: true
5
+ t.string :value
6
+ t.references :taggable, polymorphic: true, index: true
7
+ t.timestamps null: false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module TagBearer
2
+ require 'tag_bearer/acts_as_tag_bearer'
3
+ require 'tag_bearer/engine' if defined?(Rails)
4
+ require 'tag_bearer/tag'
5
+ end
@@ -0,0 +1,64 @@
1
+ require 'tag_bearer/acts_as_tag_bearer'
2
+ require 'tag_bearer/tag'
3
+
4
+ module TagBearer
5
+
6
+ def self.tag_table=(name)
7
+ TagBearer::Tag.table_name = name
8
+ end
9
+
10
+ module ActsAsTagBearer
11
+ extend ActiveSupport::Concern
12
+
13
+ included do; end
14
+
15
+ module ClassMethods
16
+ def acts_as_tag_bearer(options = {})
17
+ has_many :tags, as: :taggable, class_name: Tag
18
+ include TagBearer::ActsAsTagBearer::LocalInstanceMethods
19
+ end
20
+
21
+ def find_matches(conditions)
22
+ TagBearer::Tag.select(:taggable_id)
23
+ .where(taggable_type: name)
24
+ .where("(#{TagBearer::Tag.table_name}.key, #{TagBearer::Tag.table_name}.value) IN (#{conditions.join(',')})")
25
+ .group(:taggable_id, :taggable_type)
26
+ .having("COUNT(distinct #{TagBearer::Tag.table_name}.key)=#{conditions.size}").pluck(:taggable_id)
27
+ end
28
+
29
+ def with_tags(params)
30
+ conditions = params.map{ |condition| "('#{condition[0].to_s}', '#{condition[1].to_s}')" }
31
+ matches = find_matches conditions
32
+ matches.any? ? (name.constantize.find matches) : []
33
+ end
34
+ end
35
+
36
+ module LocalInstanceMethods
37
+ def assign_tag(params)
38
+ params.keys.each_with_index do |key, index|
39
+ tags.where(key: key).first_or_create.update(value: params.values[index])
40
+ end
41
+ end
42
+
43
+ def full_sync!(params)
44
+ params.each{ |t| tag(t) }
45
+ tags.where.not(key: params.map{|t| t[:key]}).destroy_all
46
+ end
47
+
48
+ def get_tag(tag)
49
+ tags.find_by(key: tag).try :value
50
+ end
51
+
52
+ def tag_list
53
+ tags.pluck(:key)
54
+ end
55
+
56
+ def tag(params)
57
+ tags.where(key: params[:key]).first_or_create.update(value: params[:value])
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+
64
+ ActiveRecord::Base.send :include, TagBearer::ActsAsTagBearer
@@ -0,0 +1,5 @@
1
+ require 'rails'
2
+ module TagBearer
3
+ class Engine < Rails::Engine
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+
3
+ module TagBearer
4
+ class Tag < ActiveRecord::Base
5
+ belongs_to :taggable, polymorphic: true
6
+ validates_presence_of :key
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module TagBearer
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ describe TagBearer::ActsAsTagBearer do
4
+
5
+ describe '#tag' do
6
+ let(:model) { TaggableModel.create }
7
+
8
+ it 'creates a single tag' do
9
+ model.tag key: 'Owner', value: 'someone'
10
+ expect(model.tags).to have(1).match
11
+ end
12
+
13
+ it 'requires a key' do
14
+ model.tag value: 'someone'
15
+ expect(model.tags).to have(0).matches
16
+ end
17
+
18
+ it 'does not require a value' do
19
+ model.tag key: 'someone'
20
+ expect(model.tags).to have(1).match
21
+ end
22
+
23
+ it 'overwrites a tag with an existing key' do
24
+ model.tag key: 'Owner', value: 'someone'
25
+ expect(model.get_tag('Owner')).to eq 'someone'
26
+
27
+ model.tag key: 'Owner', value: 'someone else'
28
+ expect(model.get_tag('Owner')).to eq 'someone else'
29
+
30
+ expect(model.tags).to have(1).match
31
+ end
32
+ end
33
+
34
+ describe 'get_tag' do
35
+ let(:model) { TaggableModel.create }
36
+
37
+ before(:each) do
38
+ model.tag key: 'env', value: 'test'
39
+ end
40
+
41
+ it 'returns a tag by name' do
42
+ expect(model.get_tag('env')).to eq 'test'
43
+ end
44
+
45
+ it 'returns nil if the tag does not exist' do
46
+ expect(model.get_tag('fake tag')).to be_nil
47
+ end
48
+ end
49
+
50
+ describe 'full_sync!' do
51
+ let(:model) { TaggableModel.create }
52
+
53
+ before(:each) do
54
+ model.tag key: 'first', value: 'first value'
55
+ model.tag key: 'second', value: 'original value'
56
+ model.tag key: 'third', value: 'on death row'
57
+
58
+ model.full_sync!([
59
+ {key: 'first', value: 'first value'},
60
+ {key: 'second', value: 'updated value'},
61
+ ])
62
+ end
63
+
64
+ it 'removes derelict keys' do
65
+ expect(model.tags.count).to eq 2
66
+ end
67
+
68
+ it 'updates tag values' do
69
+ expect(model.get_tag('second')).to eq 'updated value'
70
+ end
71
+
72
+ it 'ignores tags with no changes' do
73
+ expect(model.get_tag('first')).to eq 'first value'
74
+ end
75
+ end
76
+
77
+ describe 'tag_list' do
78
+ let(:model) { TaggableModel.create }
79
+
80
+ before(:each) do
81
+ model.tag key: 'env', value: 'production'
82
+ model.tag key: 'name', value: 'smithers'
83
+ end
84
+
85
+ it 'returns all tag keys' do
86
+ expect(model.tag_list).to include 'env', 'name'
87
+ end
88
+ end
89
+
90
+ describe '#with_tags' do
91
+ context 'using mysql' do
92
+ it_behaves_like 'a valid adapter'
93
+
94
+ before(:all) do
95
+ ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'tag_bearer')
96
+ end
97
+ end
98
+
99
+ context 'using postgres' do
100
+ it_behaves_like 'a valid adapter'
101
+
102
+ before(:all) do
103
+ ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: 'tag_bearer')
104
+ end
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe TagBearer do
4
+ describe '#tag_table' do
5
+ let(:new_table_name) { 'taggable' }
6
+
7
+ it 'changes the table name' do
8
+ TagBearer.tag_table = new_table_name
9
+ expect(TagBearer::Tag.table_name).to eq new_table_name
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ require 'codeclimate-test-reporter'
2
+ require 'rspec/collection_matchers'
3
+
4
+ CodeClimate::TestReporter.start
5
+
6
+ $LOAD_PATH << '.' unless $LOAD_PATH.include?('.')
7
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
8
+ require File.expand_path('../../lib/tag_bearer', __FILE__)
9
+
10
+ require 'database_cleaner'
11
+
12
+ postgres = {adapter: 'postgresql', database: 'tag_bearer', username: 'postgres'}
13
+ mysql = {adapter: 'mysql2', database: 'tag_bearer'}
14
+
15
+ [postgres, mysql].each do |connection_data|
16
+ ActiveRecord::Base.establish_connection(connection_data)
17
+ ActiveRecord::Schema.define do
18
+ self.verbose = false
19
+
20
+ create_table(:tags) do |t|
21
+ t.string :key
22
+ t.string :value
23
+ t.references :taggable, polymorphic: true, index: true
24
+ t.timestamps null: false
25
+ end unless table_exists?(:tags)
26
+
27
+ create_table :taggable_model, :force => true do |t|
28
+ t.timestamps null: false
29
+ end unless table_exists?(:taggable_model)
30
+ end
31
+ end
32
+
33
+ Dir['./spec/support/*.rb'].sort.each { |f| require f }
34
+
35
+ RSpec.configure do |config|
36
+ config.raise_errors_for_deprecations!
37
+ config.before(:suite) do
38
+ DatabaseCleaner.strategy = :transaction
39
+ DatabaseCleaner.clean_with(:truncation)
40
+ end
41
+
42
+ config.around(:each) do |example|
43
+ DatabaseCleaner.cleaning do
44
+ example.run
45
+ end
46
+ end
47
+ end
48
+
49
+
50
+
@@ -0,0 +1,4 @@
1
+ class TaggableModel < ActiveRecord::Base
2
+ self.table_name = 'taggable_model'
3
+ acts_as_tag_bearer
4
+ end
@@ -0,0 +1,33 @@
1
+ RSpec.shared_examples 'a valid adapter' do
2
+ let(:collection) { described_class.new([7, 2, 4]) }
3
+
4
+ describe '#with_tags' do
5
+ let(:bran) { TaggableModel.create }
6
+ let(:jaime) { TaggableModel.create }
7
+ let(:sansa) { TaggableModel.create }
8
+ let(:theon) { TaggableModel.create }
9
+
10
+ before(:each) do
11
+ bran.assign_tag home: 'winterfell', family: 'stark', gender: 'male'
12
+ jaime.assign_tag home: 'casterly rock', family: 'lannister', gender: 'male'
13
+ sansa.assign_tag home: 'winterfell', family: 'stark', gender: 'female'
14
+ theon.assign_tag home: 'iron islands', family: 'greyjoy', gender: nil
15
+ end
16
+
17
+ it 'returns matches on a single parameter' do
18
+ expect(TaggableModel.with_tags(home: 'winterfell')).to have(2).matches
19
+ end
20
+
21
+ it 'returns matches on multiple parameters' do
22
+ expect(TaggableModel.with_tags(home: 'winterfell', family: 'stark')).to have(2).matches
23
+ end
24
+
25
+ it 'only returns matches on all parameters' do
26
+ expect(TaggableModel.with_tags(gender: 'male', family: 'stark')).to have(1).match
27
+ end
28
+
29
+ it 'does not consider nil a match' do
30
+ expect(TaggableModel.with_tags(gender: 'male', family: 'greyjoy')).to have(0).matches
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tag_bearer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - David Jellesma
8
+ - Dan Jellesma
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-05-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '4.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '4.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rails
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.2'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '4.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '10.4'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.4'
56
+ - !ruby/object:Gem::Dependency
57
+ name: database_cleaner
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '1.4'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.4'
70
+ - !ruby/object:Gem::Dependency
71
+ name: mysql2
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.3'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.3'
84
+ - !ruby/object:Gem::Dependency
85
+ name: pg
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '0.18'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '0.18'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '3.2'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '3.2'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rspec-collection_matchers
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '1.1'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '1.1'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec-rails
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '3.2'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '3.2'
140
+ description: Key value tagging gem for ActiveRecord models
141
+ email:
142
+ - davidjellesmas@gmail.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - MIT-LICENSE
148
+ - Rakefile
149
+ - lib/generators/tag_bearer/install_generator.rb
150
+ - lib/generators/tag_bearer/templates/create_tags.rb
151
+ - lib/tag_bearer.rb
152
+ - lib/tag_bearer/acts_as_tag_bearer.rb
153
+ - lib/tag_bearer/engine.rb
154
+ - lib/tag_bearer/tag.rb
155
+ - lib/tag_bearer/version.rb
156
+ - spec/acts_as_tag_bearer/acts_as_tag_bearer_spec.rb
157
+ - spec/acts_as_tag_bearer/tag_bearer_spec.rb
158
+ - spec/spec_helper.rb
159
+ - spec/support/taggable_model.rb
160
+ - spec/support/with_tags_shared.rb
161
+ homepage: https://github.com/dsjellz/tag_bearer
162
+ licenses:
163
+ - MIT
164
+ metadata: {}
165
+ post_install_message:
166
+ rdoc_options: []
167
+ require_paths:
168
+ - lib
169
+ required_ruby_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ requirements: []
180
+ rubyforge_project:
181
+ rubygems_version: 2.4.6
182
+ signing_key:
183
+ specification_version: 4
184
+ summary: This gem implements key/value tagging for ActiveRecord models using either
185
+ mysql2 or postgres adapter
186
+ test_files:
187
+ - spec/acts_as_tag_bearer/acts_as_tag_bearer_spec.rb
188
+ - spec/acts_as_tag_bearer/tag_bearer_spec.rb
189
+ - spec/spec_helper.rb
190
+ - spec/support/taggable_model.rb
191
+ - spec/support/with_tags_shared.rb