gutentag 2.5.0 → 2.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fe6d98b81a4c392bb7591418821697ceb622e80e
4
- data.tar.gz: 5299b1efed9975a5bb902d704617c41dc0a1dd38
2
+ SHA256:
3
+ metadata.gz: b49f3b3dd099ef714f2b23fa1d0e1b08059c59c15447cdece6ee07ac1a0a1272
4
+ data.tar.gz: c0ef3dd657f3c0b6fe216b80807f74bed2abbdefcbd2c6c467176a8ca16b38a2
5
5
  SHA512:
6
- metadata.gz: 1ae92f80e7bd6b60bd7623a976b910ba4a9a43e97aac6b2dab3b0f2d72ee76bce9d1eaddc6a70c2bbbe4500c36cfa9ea2f055635f4e6a0b0182dd6842b43af83
7
- data.tar.gz: 2a07b209873183a7ed92d47bd2c6cecf153af767d14c2fe6e664272c6ee22032134ce4a0b194d5a0837206716cd5f99388b1c5a325c60d7c5c4bdd79aa9d0396
6
+ metadata.gz: ffeef43eb46712c0a261fc569db0571cc2ad66b68254cac89afbba58df15b8f41142b9401f170997608d692d81c353b744b1d71756520f7a80e4cb9c84caeb37
7
+ data.tar.gz: 80996c6d6b7f8addfb86092b80c8ffed4c28f60629db98e126fab4303865b215c4efbf76de30e9d5c8ab74e7a76859fbdfb9fff5d1c16905153f9a91d1aa6e7c
@@ -0,0 +1,90 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-18.04
8
+
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ ruby: [ '2.4', '2.5', '2.6', '2.7', '3.0' ]
13
+ bundler: [ '1.17.3', '2.1.4' ]
14
+ database: [ 'mysql', 'postgresql', 'sqlite' ]
15
+ exclude:
16
+ - ruby: '2.4'
17
+ bundler: '2.1.4'
18
+ - ruby: '2.5'
19
+ bundler: '2.1.4'
20
+ - ruby: '2.6'
21
+ bundler: '2.1.4'
22
+ - ruby: '2.7'
23
+ bundler: '1.17.3'
24
+ - ruby: '3.0'
25
+ bundler: '1.17.3'
26
+
27
+ services:
28
+ postgres:
29
+ image: postgres:10
30
+ env:
31
+ POSTGRES_USER: root
32
+ POSTGRES_PASSWORD: gutentag
33
+ POSTGRES_DB: test
34
+ ports: ['5432:5432']
35
+ # needed because the postgres container does not provide a healthcheck
36
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
37
+
38
+ mysql:
39
+ image: mysql:5.7
40
+ env:
41
+ MYSQL_ROOT_PASSWORD: gutentag
42
+ MYSQL_DATABASE: test
43
+ ports: ['3306:3306']
44
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
45
+
46
+ steps:
47
+ - name: Check out code
48
+ uses: actions/checkout@v2
49
+ - name: Set up ruby
50
+ uses: ruby/setup-ruby@v1
51
+ with:
52
+ ruby-version: ${{ matrix.ruby }}
53
+ bundler: ${{ matrix.bundler }}
54
+ bundler-cache: true
55
+
56
+ - name: Set up Bundler
57
+ run: |
58
+ export BUNDLER_VERSION=${{ matrix.bundler }}
59
+ export BUNDLE_PATH=$PWD/vendor/bundle
60
+
61
+ gem update --system
62
+
63
+ bundle _${{ matrix.bundler }}_ config set path $PWD/$BUNDLE_PATH
64
+ bundle _${{ matrix.bundler }}_ install --jobs=4 --retry=3
65
+ bundle _${{ matrix.bundler }}_ update
66
+ - name: Set up Appraisal
67
+ run: bundle exec appraisal update
68
+ - name: Test
69
+ env:
70
+ CI: "true"
71
+ DATABASE: ${{ matrix.database }}
72
+ DB_USERNAME: root
73
+ DB_PASSWORD: gutentag
74
+ DB_HOST: 127.0.0.1
75
+ run: bundle exec appraisal rspec
76
+
77
+ rubocop:
78
+ runs-on: ubuntu-18.04
79
+
80
+ steps:
81
+ - name: Check out code
82
+ uses: actions/checkout@v2
83
+ - name: Set up ruby
84
+ uses: ruby/setup-ruby@v1
85
+ with:
86
+ ruby-version: 2.7
87
+ bundler: 2.1.4
88
+ bundler-cache: true
89
+ - name: Rubocop
90
+ run: bundle exec rubocop
data/.gitignore CHANGED
@@ -2,4 +2,5 @@
2
2
  gemfiles
3
3
  pkg/*
4
4
  spec/internal/db/*.sqlite
5
+ spec/internal/db/migrate/
5
6
  .rubocop-*-yml
data/.rubocop.yml CHANGED
@@ -1,10 +1,16 @@
1
1
  inherit_from:
2
2
  - https://gist.githubusercontent.com/pat/ba3b8ffb1901bfe5439b460943b6b019/raw/.rubocop.yml
3
3
 
4
+ require: rubocop-performance
5
+
4
6
  AllCops:
5
7
  TargetRubyVersion: 2.3
6
8
  Exclude:
7
9
  - gemfiles/*.gemfile
10
+ - vendor/**/*
11
+
12
+ Bundler/DuplicatedGem:
13
+ Enabled: false
8
14
 
9
15
  Metrics/MethodLength:
10
16
  Exclude:
@@ -18,3 +24,19 @@ Style/MultilineIfModifier:
18
24
  Style/MultilineTernaryOperator:
19
25
  Exclude:
20
26
  - db/migrate/*.rb
27
+
28
+ # 0.80
29
+
30
+ Style/HashEachMethods:
31
+ Enabled: true
32
+ Style/HashTransformKeys:
33
+ Enabled: true
34
+ Style/HashTransformValues:
35
+ Enabled: true
36
+
37
+ # 0.81
38
+
39
+ Lint/RaiseException:
40
+ Enabled: true
41
+ Lint/StructNewOverride:
42
+ Enabled: true
data/Appraisals CHANGED
@@ -17,26 +17,34 @@ appraise "rails_4_2" do
17
17
  gem "activerecord-jdbcmysql-adapter", "~> 1.3.23", :platform => :jruby
18
18
  gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.23", :platform => :jruby
19
19
  gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.23", :platform => :jruby
20
- end
20
+ end if RUBY_VERSION.to_f < 2.7
21
21
 
22
22
  appraise "rails_5_0" do
23
23
  gem "rails", "~> 5.0.3"
24
24
  gem "mysql2", "~> 0.4.0", :platform => :ruby
25
- end if RUBY_VERSION.to_f >= 2.2
25
+ end if RUBY_VERSION.to_f >= 2.2 && RUBY_VERSION.to_f <= 2.7
26
26
 
27
27
  appraise "rails_5_1" do
28
28
  gem "rails", "~> 5.1.1"
29
29
  gem "mysql2", "~> 0.4.0", :platform => :ruby
30
- end if RUBY_VERSION.to_f >= 2.2
30
+ end if RUBY_VERSION.to_f >= 2.2 && RUBY_VERSION.to_f <= 2.7
31
31
 
32
32
  appraise "rails_5_2" do
33
33
  gem "rails", "~> 5.2.0"
34
34
  gem "pg", "~> 1.0", :platform => :ruby
35
35
  gem "mysql2", "~> 0.5.0", :platform => :ruby
36
- end if RUBY_VERSION.to_f >= 2.2
36
+ end if RUBY_VERSION.to_f >= 2.2 && RUBY_VERSION.to_f <= 2.7
37
37
 
38
38
  appraise "rails_6_0" do
39
- gem "rails", "~> 6.0.0.beta1"
40
- gem "pg", "~> 1.0", :platform => :ruby
41
- gem "mysql2", "~> 0.5.0", :platform => :ruby
39
+ gem "rails", "~> 6.0.0"
40
+ gem "pg", "~> 1.0", :platform => :ruby
41
+ gem "mysql2", "~> 0.5.0", :platform => :ruby
42
+ gem "sqlite3", "~> 1.4", :platform => :ruby
43
+ end if RUBY_VERSION.to_f >= 2.5 && RUBY_PLATFORM != "java"
44
+
45
+ appraise "rails_6_1" do
46
+ gem "rails", "~> 6.1.0"
47
+ gem "pg", "~> 1.0", :platform => :ruby
48
+ gem "mysql2", "~> 0.5.0", :platform => :ruby
49
+ gem "sqlite3", "~> 1.4", :platform => :ruby
42
50
  end if RUBY_VERSION.to_f >= 2.5 && RUBY_PLATFORM != "java"
data/CHANGELOG.md CHANGED
@@ -2,9 +2,45 @@
2
2
 
3
3
  All notable changes to this project (at least, from v0.5.0 onwards) will be documented in this file.
4
4
 
5
+ ## 2.6.0 - 2021-07-10
6
+
7
+ ### Added
8
+
9
+ * Queries can now be made for objects that have _none_ of the specified tags using `:match => :none` ([Rares S](https://github.com/laleshii) in [#79](https://github.com/pat/gutentag/pull/79)).
10
+ * Added a generator `gutentag:migration_versions` to update generated migrations so they use the current version of Rails/ActiveRecord's Migration superclass. See discussion in [#80](https://github.com/pat/gutentag/issues/80).
11
+
12
+ ### Changed
13
+
14
+ * When adding Gutentag to a new app, the migrations require the `gutentag:migration_versions` generator to be run to ensure the latest ActiveRecord migration superclass is used. This change has no impact to existing apps. See discussion in [#80](https://github.com/pat/gutentag/issues/80).
15
+
16
+ ## 2.5.4 - 2021-02-21
17
+
18
+ ### Fixed
19
+
20
+ * Don't apply the tag length validation when `ActiveRecord::ConnectionNotEstablished` exceptions are raised. ([John Duff](https://github.com/jduff) in [#77](https://github.com/pat/gutentag/pull/77)).
21
+
22
+ ## 2.5.3 - 2020-06-28
23
+
24
+ ### Fixed
25
+
26
+ * Use `saved_change_to_tag_names?` instead of `tag_names_previously_changed?` for Rails 5.1+ ([Morten Trolle](https://github.com/mtrolle) in [#70](https://github.com/pat/gutentag/pull/70)).
27
+ * `Gutentag::Tag.names_for_scope` now handles empty scopes ([Mike Gunderloy](https://github.com/ffmike) in [#73](https://github.com/pat/gutentag/pull/73)).
28
+
29
+ ## 2.5.2 - 2019-07-08
30
+
31
+ ### Fixed
32
+
33
+ * `tag_names` will no longer be referenced as a database column when tagged models are requested in joins in Rails 4.2 (as reported in [issue #67](https://github.com/pat/gutentag/issues/67)).
34
+
35
+ ## 2.5.1 - 2019-05-10
36
+
37
+ ### Fixed
38
+
39
+ * Ensuring consistent behaviour for tag_names array - names are not duplicated, and are normalised prior to saving (as discussed in [issue #66](https://github.com/pat/gutentag/issues/66)).
40
+
5
41
  ## 2.5.0 - 2019-03-15
6
42
 
7
- **Pleease note this release ends official support of Rails 3.2 and Ruby (MRI) 2.2.** The code currently still works on Ruby 2.2, and all features except for the new `Gutentag::Tag.names_for_scope` method work in Rails 3.2, but they're no longer tested against, and cannot be guaranteed to work in future releases.
43
+ **Please note this release ends official support of Rails 3.2 and Ruby (MRI) 2.2.** The code currently still works on Ruby 2.2, and all features except for the new `Gutentag::Tag.names_for_scope` method work in Rails 3.2, but they're no longer tested against, and cannot be guaranteed to work in future releases.
8
44
 
9
45
  ### Added
10
46
 
data/Gemfile CHANGED
@@ -4,11 +4,20 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
+ gem "appraisal",
8
+ :git => "https://github.com/marcotc/appraisal.git",
9
+ :branch => "explicit-require-set"
10
+
7
11
  gem "test-unit", :platform => :ruby_22
8
12
 
9
13
  gem "mysql2", "~> 0.3", :platform => :ruby
10
14
  gem "pg", "~> 0.18", :platform => :ruby
11
- gem "sqlite3", "~> 1.3.13", :platform => :ruby
15
+
16
+ if RUBY_VERSION.to_f < 3.0
17
+ gem "sqlite3", "~> 1.3.13"
18
+ else
19
+ gem "sqlite3", "~> 1.4"
20
+ end
12
21
 
13
22
  gem "activerecord-jdbcmysql-adapter", ">= 1.3.23", :platform => :jruby
14
23
  gem "activerecord-jdbcpostgresql-adapter", ">= 1.3.23", :platform => :jruby
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Gutentag
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/gutentag.png)](http://badge.fury.io/rb/gutentag)
4
- [![Build Status](https://travis-ci.org/pat/gutentag.png?branch=master)](https://travis-ci.org/pat/gutentag)
5
- [![Code Climate](https://codeclimate.com/github/pat/gutentag.png)](https://codeclimate.com/github/pat/gutentag)
3
+ [![Gem Version](https://badge.fury.io/rb/gutentag.svg)](http://badge.fury.io/rb/gutentag)
4
+ [![Build Status](https://travis-ci.org/pat/gutentag.svg?branch=master)](https://travis-ci.org/pat/gutentag)
5
+ [![Code Climate](https://codeclimate.com/github/pat/gutentag.svg)](https://codeclimate.com/github/pat/gutentag)
6
6
 
7
7
  A good, simple, solid tagging extension for ActiveRecord.
8
8
 
9
9
  This was initially built partly as a proof-of-concept, partly to see how a tagging gem could work when it's not all stuffed within models, and partly just because I wanted a simpler tagging library. It's now a solid little tagging Rails engine.
10
10
 
11
- If you want to know more, read [this blog post](http://freelancing-gods.com/posts/gutentag_simple_rails_tagging).
11
+ If you want to know more, read [this blog post](http://freelancing-gods.com/posts/gutentag_simple_rails_tagging), or have a look at [the Examples page](https://github.com/pat/gutentag/wiki/Examples) in the wiki (which includes a starting point for accepting tag values in a form).
12
12
 
13
13
  ## Contents
14
14
 
@@ -16,6 +16,7 @@ If you want to know more, read [this blog post](http://freelancing-gods.com/post
16
16
  * [Installation](#installation)
17
17
  * [Upgrading](#upgrading)
18
18
  * [Configuration](#configuration)
19
+ * [Extending](#extending)
19
20
  * [Contribution](#contribution)
20
21
  * [Licence](#licence)
21
22
 
@@ -66,6 +67,13 @@ To return records that have _all_ specified tags, use `:match => :all`:
66
67
  Article.tagged_with(:ids => [tag_a.id, tag_b.id], :match => :all)
67
68
  ```
68
69
 
70
+ To return records that have _none_ of the specified tags, use `:match => :none`:
71
+
72
+ ```ruby
73
+ # Returns all articles that have *neither* tag_a nor tag_b.
74
+ Article.tagged_with(:ids => [tag_a.id, tag_b.id], :match => :none)
75
+ ```
76
+
69
77
  <h2 id="installation">Installation</h2>
70
78
 
71
79
  ### Dependencies
@@ -85,11 +93,12 @@ Get it into your Gemfile - and don't forget the version constraint!
85
93
  gem 'gutentag', '~> 2.5'
86
94
  ```
87
95
 
88
- Next: your tags get persisted to your database, so let's import and run the migrations to get the tables set up:
96
+ Next: your tags get persisted to your database, so let's import the migrations, update them to your current version of Rails, and then migrate:
89
97
 
90
98
  ```Bash
91
- rake gutentag:install:migrations
92
- rake db:migrate
99
+ bundle exec rake gutentag:install:migrations
100
+ bundle exec rails generate gutentag:migration_versions
101
+ bundle exec rake db:migrate
93
102
  ```
94
103
 
95
104
  If you're using UUID primary keys, make sure you alter the migration files before running `db:migrate` to use UUIDs for the `taggable_id` foreign key column (as noted in [issue 57](https://github.com/pat/gutentag/issues/57).)
@@ -151,6 +160,19 @@ Gutentag.normaliser = lambda { |value| value.to_s.upcase }
151
160
 
152
161
  Gutentag ignores case by default, but can be customised to be case-sensitive by supplying your own validations and normaliser, as outlined by [Robin Mehner](https://github.com/rmehner) in [issue 42](https://github.com/pat/gutentag/issues/42). Further changes may be required for your schema though, depending on your database.
153
162
 
163
+ <h2 id="extending">Extending</h2>
164
+
165
+ If you need to extend Gutentag's models, you will need to wrap the `include` inside a `to_prepare` hook to ensure it's loaded consistently in all Rails environments:
166
+
167
+ ```ruby
168
+ # config/initializers/gutentag.rb or equivalent
169
+ Rails.application.config.to_prepare do
170
+ Gutentag::Tag.include TagExtensions
171
+ end
172
+ ```
173
+
174
+ Further discussion and examples of this can be found in [issue 65](https://github.com/pat/gutentag/issues/65).
175
+
154
176
  <h2 id="contribution">Contribution</h2>
155
177
 
156
178
  Please note that this project now has a [Contributor Code of Conduct](http://contributor-covenant.org/version/1/0/0/). By participating in this project you agree to abide by its terms.
@@ -21,7 +21,9 @@ class Gutentag::Tag < ActiveRecord::Base
21
21
 
22
22
  def self.names_for_scope(scope)
23
23
  join_conditions = {:taggable_type => scope.name}
24
- if scope.current_scope.present?
24
+ if scope.is_a?(ActiveRecord::Relation)
25
+ return Gutentag::Tag.none unless scope.current_scope.present?
26
+
25
27
  join_conditions[:taggable_id] = scope.select(:id)
26
28
  end
27
29
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- superclass = ActiveRecord::VERSION::MAJOR < 5 ?
4
- ActiveRecord::Migration : ActiveRecord::Migration[4.2]
5
- class GutentagTables < superclass
3
+ class GutentagTables < ActiveRecord::Migration
6
4
  def up
7
5
  create_table :gutentag_taggings do |t|
8
6
  t.integer :tag_id, :null => false
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- superclass = ActiveRecord::VERSION::MAJOR < 5 ?
4
- ActiveRecord::Migration : ActiveRecord::Migration[4.2]
5
- class GutentagCacheCounter < superclass
3
+ class GutentagCacheCounter < ActiveRecord::Migration
6
4
  def up
7
5
  add_column :gutentag_tags, :taggings_count, :integer, :default => 0
8
6
  add_index :gutentag_tags, :taggings_count
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- superclass = ActiveRecord::VERSION::MAJOR < 5 ?
4
- ActiveRecord::Migration : ActiveRecord::Migration[4.2]
5
- class NoNullCounters < superclass
3
+ class NoNullCounters < ActiveRecord::Migration
6
4
  def up
7
5
  change_column :gutentag_tags, :taggings_count, :integer,
8
6
  :default => 0,
data/gutentag.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "gutentag"
5
- s.version = "2.5.0"
5
+ s.version = "2.6.0"
6
6
  s.authors = ["Pat Allan"]
7
7
  s.email = ["pat@freelancing-gods.com"]
8
8
  s.homepage = "https://github.com/pat/gutentag"
@@ -16,11 +16,12 @@ Gem::Specification.new do |s|
16
16
 
17
17
  s.add_runtime_dependency "activerecord", ">= 3.2.0"
18
18
 
19
- s.add_development_dependency "appraisal", "~> 2.1.0"
20
- s.add_development_dependency "bundler", "~> 1.17"
21
- s.add_development_dependency "combustion", "~> 1.1"
22
- s.add_development_dependency "database_cleaner", "~> 1.6"
19
+ # s.add_development_dependency "appraisal", "~> 2.3"
20
+ s.add_development_dependency "bundler", ">= 1.17"
21
+ s.add_development_dependency "combustion", "~> 1.1"
22
+ s.add_development_dependency "database_cleaner", "~> 1.6"
23
23
  s.add_development_dependency "rails"
24
- s.add_development_dependency "rspec-rails", "~> 3.1"
25
- s.add_development_dependency "rubocop", "~> 0.64.0"
24
+ s.add_development_dependency "rspec-rails", "~> 3.1"
25
+ s.add_development_dependency "rubocop", "~> 0.81.0"
26
+ s.add_development_dependency "rubocop-performance", "~> 1"
26
27
  end
@@ -31,7 +31,7 @@ class Gutentag::ActiveRecord
31
31
  end
32
32
 
33
33
  def add_attribute
34
- if legacy?
34
+ if ActiveRecord::VERSION::STRING.to_f <= 4.2
35
35
  model.define_attribute_method "tag_names"
36
36
  else
37
37
  model.attribute "tag_names", ActiveRecord::Type::Value.new,
@@ -2,6 +2,8 @@
2
2
 
3
3
  # For Rails 5.0+
4
4
  module Gutentag::ActiveRecord::InstanceMethods
5
+ AR_VERSION = ActiveRecord::VERSION::STRING.to_f
6
+
5
7
  # If the tag_names attribute was one of the modified values, then let's just
6
8
  # use the modifications, rather than overwriting the stored value.
7
9
  #
@@ -9,7 +11,12 @@ module Gutentag::ActiveRecord::InstanceMethods
9
11
  # the instance directly (e.g. article.tags << tag), which invokes the save
10
12
  # callbacks, but the old tag_names value is stored but not updated.
11
13
  def reset_tag_names
12
- return if tag_names_previously_changed?
14
+ # Rails 5.1 introduces major changes to how ActiveModel::Dirty works:
15
+ # https://github.com/pat/gutentag/pull/70#issuecomment-524605448
16
+ # For Rails <5.1 we'll use *_previously_changed?
17
+ # and for 5.1+ we'll use saved_change_to_*?
18
+ return if AR_VERSION < 5.1 && tag_names_previously_changed?
19
+ return if AR_VERSION >= 5.1 && saved_change_to_tag_names?
13
20
 
14
21
  # Update the underlying value rather than going through the setter, to
15
22
  # ensure this update doesn't get marked as a 'change'.
@@ -8,22 +8,25 @@ module Gutentag::ActiveRecord::InstanceMethods
8
8
  def reset_tag_names
9
9
  # Update the underlying value rather than going through the setter, to
10
10
  # ensure this update doesn't get marked as a 'change'.
11
- self.tag_names = nil
11
+ @tag_names = nil
12
12
  end
13
13
 
14
14
  def tag_names
15
- # If the underlying value is nil, we've not requested this from the
16
- # database yet.
17
- if read_attribute("tag_names") { nil }.nil?
18
- self.tag_names = tags.pluck(:name)
15
+ @tag_names ||= begin
16
+ raw = tags.pluck(:name)
17
+ raw_write_attribute "tag_names", raw
18
+ raw
19
19
  end
20
-
21
- # Use ActiveRecord's underlying implementation with change tracking.
22
- super
23
20
  end
24
21
 
25
22
  def tag_names=(names)
26
- super Gutentag::TagNames.call(names)
23
+ new_names = Gutentag::TagNames.call names
24
+ return if new_names.sort == tag_names.sort
25
+
26
+ tag_names_will_change!
27
+
28
+ write_attribute "tag_names", new_names
29
+ @tag_names = new_names
27
30
  end
28
31
 
29
32
  private
@@ -2,4 +2,8 @@
2
2
 
3
3
  class Gutentag::Engine < Rails::Engine
4
4
  engine_name :gutentag
5
+
6
+ generators do
7
+ require "gutentag/generators/migration_versions_generator"
8
+ end
5
9
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Gutentag
6
+ module Generators
7
+ class MigrationVersionsGenerator < Rails::Generators::Base
8
+ desc "Update the ActiveRecord version in Gutentag migrations"
9
+
10
+ def update_migration_versions
11
+ if ::ActiveRecord::VERSION::MAJOR < 5
12
+ puts "No changes required"
13
+ else
14
+ migration_files.each do |file|
15
+ gsub_file file,
16
+ /< ActiveRecord::Migration$/,
17
+ "< ActiveRecord::Migration[#{rails_version}]"
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def migration_files
25
+ Dir[Rails.root.join("db/migrate/*.rb")].select do |path|
26
+ known_migration_names.any? do |known|
27
+ File.basename(path)[/\A\d+_#{known}\.gutentag.rb\z/]
28
+ end
29
+ end
30
+ end
31
+
32
+ def known_migration_names
33
+ @known_migration_names ||= begin
34
+ Dir[File.join(__dir__, "../../../db/migrate/*.rb")].collect do |path|
35
+ File.basename(path).gsub(/\A\d+_/, "").gsub(/\.rb\z/, "")
36
+ end
37
+ end
38
+ end
39
+
40
+ def rails_version
41
+ @rails_version ||= [
42
+ ::ActiveRecord::VERSION::MAJOR,
43
+ ::ActiveRecord::VERSION::MINOR
44
+ ].join(".")
45
+ end
46
+ end
47
+ end
48
+ end
@@ -4,6 +4,8 @@ class Gutentag::TagNames
4
4
  def self.call(names)
5
5
  return nil if names.nil?
6
6
 
7
- names.reject(&:blank?)
7
+ names.reject(&:blank?).uniq.collect do |name|
8
+ Gutentag.normaliser.call(name)
9
+ end
8
10
  end
9
11
  end
@@ -13,6 +13,7 @@ class Gutentag::TagValidations
13
13
  if ActiveRecord::VERSION::STRING.to_f > 4.0
14
14
  classes << ActiveRecord::NoDatabaseError
15
15
  end
16
+ classes << ActiveRecord::ConnectionNotEstablished
16
17
  classes << Mysql2::Error if defined?(::Mysql2)
17
18
  classes << PG::ConnectionBad if defined?(::PG)
18
19
  classes
@@ -36,7 +37,7 @@ class Gutentag::TagValidations
36
37
 
37
38
  def add_length_validation?
38
39
  klass.table_exists? && limit.present?
39
- rescue *DATABASE_ERROR_CLASSES
40
+ rescue *DATABASE_ERROR_CLASSES => _error
40
41
  warn <<-MESSAGE
41
42
  The database is not currently available, and so Gutentag was not able to set
42
43
  up tag validations completely (in particular: adding a length limit to match
@@ -8,7 +8,7 @@ class Gutentag::TaggedWith::Query
8
8
  end
9
9
 
10
10
  def call
11
- model.where "#{model_id} IN (#{query.to_sql})"
11
+ model.where "#{model_id} #{operator} (#{query.to_sql})"
12
12
  end
13
13
 
14
14
  private
@@ -20,8 +20,16 @@ class Gutentag::TaggedWith::Query
20
20
  end
21
21
 
22
22
  def query
23
- return taggable_ids_query if match == :any || values.length == 1
23
+ return taggable_ids_query if match_any_or_none? || values.length == 1
24
24
 
25
25
  taggable_ids_query.having("COUNT(*) = #{values.length}").group(:taggable_id)
26
26
  end
27
+
28
+ def operator
29
+ match == :none ? "NOT IN" : "IN"
30
+ end
31
+
32
+ def match_any_or_none?
33
+ %i[any none].include?(match)
34
+ end
27
35
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe "Removing unused" do
5
+ RSpec.describe "Removing unused" do
6
6
  let(:article) { Article.create }
7
7
 
8
8
  it "deletes only unused tags" do
@@ -27,4 +27,12 @@ RSpec.describe "Tag names for scopes" do
27
27
  expect(Gutentag::Tag.names_for_scope(Article)).
28
28
  to match_array(%w[ koala wombat cassowary ])
29
29
  end
30
+
31
+ it "returns an empty array for an empty scope" do
32
+ Article.create :title => "mammals", :tag_names => %w[ koala wombat ]
33
+ Article.create :title => "birds", :tag_names => %w[ cassowary ]
34
+
35
+ expect(Gutentag::Tag.names_for_scope(Article.where(:title => "reptiles"))).
36
+ to match_array([])
37
+ end
30
38
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe "Managing tags via names" do
5
+ RSpec.describe "Managing tags via names" do
6
6
  let(:article) { Article.create }
7
7
 
8
8
  it "returns tag names" do
@@ -70,6 +70,13 @@ describe "Managing tags via names" do
70
70
  expect(article.tags.collect(&:name)).to eq(%w[ portland oregon ruby ])
71
71
  end
72
72
 
73
+ it "does not repeat appended names that exist in the array of strings" do
74
+ article.tag_names = %w[ portland oregon ]
75
+ article.tag_names += %w[ oregon ruby ]
76
+
77
+ expect(article.tag_names).to match(%w[ portland oregon ruby ])
78
+ end
79
+
73
80
  it "removes a single tag name" do
74
81
  article.tag_names = %w[ portland oregon ]
75
82
  article.tag_names.delete "oregon"
@@ -99,6 +106,11 @@ describe "Managing tags via names" do
99
106
  expect(article.tags.collect(&:name)).to eq(%w[ portland ])
100
107
  end
101
108
 
109
+ it "normalises tag names" do
110
+ article.tag_names = %w[ Perth ]
111
+ expect(article.tag_names).to eq(%w[ perth ])
112
+ end
113
+
102
114
  it "allows setting of tag names on unpersisted objects" do
103
115
  article = Article.new :tag_names => %w[ melbourne pancakes ]
104
116
  article.save!
@@ -123,4 +135,8 @@ describe "Managing tags via names" do
123
135
 
124
136
  expect(article.tag_names).to eq(%w[ melbourne ])
125
137
  end
138
+
139
+ it "allows eager-loading of the model via an association" do
140
+ expect { Comment.eager_load(:article).to_a }.to_not raise_error
141
+ end
126
142
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe "Adding and removing tags" do
5
+ RSpec.describe "Adding and removing tags" do
6
6
  let(:article) { Article.create }
7
7
  let(:pancakes) { Gutentag::Tag.create :name => "pancakes" }
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Gutentag::ActiveRecord do
5
+ RSpec.describe Gutentag::ActiveRecord do
6
6
  describe ".tagged_with" do
7
7
  let!(:melbourne_article) do
8
8
  article = Article.create :title => "Overview"
@@ -160,6 +160,26 @@ describe Gutentag::ActiveRecord do
160
160
  it { is_expected.not_to include oregon_article }
161
161
  end
162
162
 
163
+ context "matching excluding one tag" do
164
+ subject do
165
+ Article.tagged_with(:names => %w[ melbourne ], :match => :none)
166
+ end
167
+
168
+ it { expect(subject.count).to eq 1 }
169
+ it { is_expected.to include oregon_article }
170
+ it do
171
+ is_expected.not_to include melbourne_oregon_article, melbourne_article
172
+ end
173
+ end
174
+
175
+ context "matching excluding all tags" do
176
+ subject do
177
+ Article.tagged_with(:names => %w[ melbourne oregon ], :match => :none)
178
+ end
179
+
180
+ it { expect(subject.count).to eq 0 }
181
+ end
182
+
163
183
  it "should work on STI subclasses" do
164
184
  thinkpiece = Thinkpiece.create! :tag_names => ["pancakes"]
165
185
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Gutentag do
5
+ RSpec.describe Gutentag do
6
6
  describe ".normalizer" do
7
7
  it "downcases the provided name" do
8
8
  expect(Gutentag.normaliser.call("Tasty Pancakes")).to eq("tasty pancakes")
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comment < ActiveRecord::Base
4
+ belongs_to :article
5
+ end
@@ -2,11 +2,26 @@
2
2
  test:
3
3
  adapter: mysql2
4
4
  database: gutentag
5
- username: root
5
+ username: <%= ENV.fetch("DB_USERNAME", "root") %>
6
+ <% if ENV["DB_HOST"] %>
7
+ host: <%= ENV["DB_HOST"] %>
8
+ <% end %>
9
+ <% if ENV["DB_PASSWORD"] %>
10
+ password: <%= ENV["DB_PASSWORD"] %>
11
+ <% end %>
6
12
  <% elsif ENV["DATABASE"] == "postgres" %>
7
13
  test:
8
14
  adapter: postgresql
9
15
  database: gutentag
16
+ <% if ENV["DB_HOST"] %>
17
+ host: <%= ENV["DB_HOST"] %>
18
+ <% end %>
19
+ <% if ENV["DB_USERNAME"] %>
20
+ username: <%= ENV["DB_USERNAME"] %>
21
+ <% end %>
22
+ <% if ENV["DB_PASSWORD"] %>
23
+ password: <%= ENV["DB_PASSWORD"] %>
24
+ <% end %>
10
25
  <% else %>
11
26
  test:
12
27
  adapter: sqlite3
@@ -6,4 +6,10 @@ ActiveRecord::Schema.define do
6
6
  t.string :type
7
7
  t.timestamps :null => false
8
8
  end
9
+
10
+ create_table :comments, :force => true do |t|
11
+ t.references :article
12
+ t.string :name
13
+ t.text :text
14
+ end
9
15
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Gutentag::Tag, :type => :model do
5
+ RSpec.describe Gutentag::Tag, :type => :model do
6
6
  describe ".find_by_name" do
7
7
  it "returns a tag with the same name" do
8
8
  existing = Gutentag::Tag.create! :name => "pancakes"
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Gutentag::Tagging, :type => :model do
5
+ RSpec.describe Gutentag::Tagging, :type => :model do
6
6
  describe "#valid?" do
7
7
  let(:tag) { Gutentag::Tag.create! :name => "pancakes" }
8
8
  let(:taggable) { Article.create! }
data/spec/spec_helper.rb CHANGED
@@ -4,14 +4,17 @@ require "bundler/setup"
4
4
 
5
5
  Bundler.require :default, :development
6
6
 
7
- Dir["#{__dir__}/support/**/*.rb"].each { |file| require file }
7
+ Dir["#{__dir__}/support/**/*.rb"].sort.each { |file| require file }
8
8
 
9
- Combustion.initialize! :active_record
9
+ Combustion.initialize! :active_record, :database_migrate => false
10
+ AdjustMigrations.call(Combustion::Application)
10
11
  ActiveSupport.run_load_hooks :gutentag unless defined?(Gutentag::Engine)
11
12
 
12
13
  require "rspec/rails"
13
14
 
14
15
  RSpec.configure do |config|
16
+ config.disable_monkey_patching!
17
+
15
18
  if config.respond_to?(:use_transactional_tests)
16
19
  config.use_transactional_tests = false
17
20
  else
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "gutentag/generators/migration_versions_generator"
5
+
6
+ class AdjustMigrations
7
+ def self.call(application)
8
+ new(application).call
9
+ end
10
+
11
+ def initialize(application)
12
+ @application = application
13
+ end
14
+
15
+ def call
16
+ copy_migrations_to_app
17
+ run_generator
18
+ run_migrations
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :application
24
+
25
+ def copy_migrations_to_app
26
+ FileUtils.mkdir_p application.root.join("db/migrate")
27
+
28
+ Dir["#{__dir__}/../../db/migrate/*.rb"].each do |file|
29
+ name = File.basename(file.gsub(/rb\z/, "gutentag.rb"))
30
+ destination = application.root.join("db/migrate/#{name}")
31
+
32
+ FileUtils.cp file, destination.to_s
33
+ end
34
+ end
35
+
36
+ def migration_context
37
+ if ActiveRecord::MigrationContext.instance_method(:initialize).arity <= 1
38
+ ActiveRecord::MigrationContext.new migration_paths
39
+ else
40
+ ActiveRecord::MigrationContext.new(
41
+ migration_paths, ActiveRecord::Base.connection.schema_migration
42
+ )
43
+ end
44
+ end
45
+
46
+ def migration_paths
47
+ application.root.join("db/migrate")
48
+ end
49
+
50
+ def run_generator
51
+ Gutentag::Generators::MigrationVersionsGenerator.start(
52
+ ["--quiet"], :destination_root => application.root
53
+ )
54
+ end
55
+
56
+ def run_migrations
57
+ if ActiveRecord::VERSION::STRING.to_f >= 5.2
58
+ migration_context.migrate
59
+ else
60
+ ActiveRecord::Migrator.migrate migration_paths, nil
61
+ end
62
+ end
63
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gutentag
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-15 00:00:00.000000000 Z
11
+ date: 2021-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -24,32 +24,18 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.2.0
27
- - !ruby/object:Gem::Dependency
28
- name: appraisal
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 2.1.0
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 2.1.0
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: bundler
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
- - - "~>"
31
+ - - ">="
46
32
  - !ruby/object:Gem::Version
47
33
  version: '1.17'
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
- - - "~>"
38
+ - - ">="
53
39
  - !ruby/object:Gem::Version
54
40
  version: '1.17'
55
41
  - !ruby/object:Gem::Dependency
@@ -114,14 +100,28 @@ dependencies:
114
100
  requirements:
115
101
  - - "~>"
116
102
  - !ruby/object:Gem::Version
117
- version: 0.64.0
103
+ version: 0.81.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.81.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-performance
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.64.0
124
+ version: '1'
125
125
  description: A good, simple, solid tagging extension for ActiveRecord
126
126
  email:
127
127
  - pat@freelancing-gods.com
@@ -129,9 +129,9 @@ executables: []
129
129
  extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
+ - ".github/workflows/ci.yml"
132
133
  - ".gitignore"
133
134
  - ".rubocop.yml"
134
- - ".travis.yml"
135
135
  - Appraisals
136
136
  - CHANGELOG.md
137
137
  - Gemfile
@@ -153,6 +153,7 @@ files:
153
153
  - lib/gutentag/change_state.rb
154
154
  - lib/gutentag/dirty.rb
155
155
  - lib/gutentag/engine.rb
156
+ - lib/gutentag/generators/migration_versions_generator.rb
156
157
  - lib/gutentag/persistence.rb
157
158
  - lib/gutentag/remove_unused.rb
158
159
  - lib/gutentag/tag_names.rb
@@ -169,6 +170,7 @@ files:
169
170
  - spec/gutentag/active_record_spec.rb
170
171
  - spec/gutentag_spec.rb
171
172
  - spec/internal/app/models/article.rb
173
+ - spec/internal/app/models/comment.rb
172
174
  - spec/internal/app/models/thinkpiece.rb
173
175
  - spec/internal/config/database.yml
174
176
  - spec/internal/db/schema.rb
@@ -176,6 +178,7 @@ files:
176
178
  - spec/models/gutentag/tag_spec.rb
177
179
  - spec/models/gutentag/tagging_spec.rb
178
180
  - spec/spec_helper.rb
181
+ - spec/support/adjust_migrations.rb
179
182
  - spec/support/mysql.rb
180
183
  homepage: https://github.com/pat/gutentag
181
184
  licenses:
@@ -196,8 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
199
  - !ruby/object:Gem::Version
197
200
  version: '0'
198
201
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.4.5.5
202
+ rubygems_version: 3.1.2
201
203
  signing_key:
202
204
  specification_version: 4
203
205
  summary: Good Tags
data/.travis.yml DELETED
@@ -1,22 +0,0 @@
1
- language: ruby
2
- dist: xenial
3
- script: bundle exec appraisal rake
4
- rvm:
5
- - 2.3.8
6
- - 2.4.5
7
- - 2.5.3
8
- - 2.6.1
9
- - jruby-9.2.5.0
10
- before_install:
11
- - gem install bundler --version 1.17.3
12
- install:
13
- - bundle _1.17.3_ install --jobs=3 --retry=3
14
- before_script:
15
- - bundle exec appraisal install
16
- env:
17
- - DATABASE=postgres
18
- - DATABASE=mysql
19
- - DATABASE=sqlite
20
- services:
21
- - mysql
22
- - postgresql