gutentag 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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