validates_duplicity_of 0.1.4 → 0.2.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
2
  SHA1:
3
- metadata.gz: 1e3bf057fa7474081194521f3e227118b783c880
4
- data.tar.gz: 5609fa7c0fb610544b5dc9dd687d2aca6c7c11c5
3
+ metadata.gz: b8059f9febbe7dfc82d2ca9ac828303efaf1ed74
4
+ data.tar.gz: 2dd82ed7bd23ebc3c28c9bcbb22d15bd07099c8b
5
5
  SHA512:
6
- metadata.gz: fb8fe3834a5bf9a188691ee85632ca1717ffc12f236142ec6c8ade705b05d59e0d54d118a21e86511e6fffd6fa9784e19bf59716b6fb1dd89e5b267b9e900012
7
- data.tar.gz: 4a05b2b33694e80c136ae9a2ffcc8b927785c9a5e8ea95c9df9ab91fea9ad0e763bdf24b60d88374c93f116b91aef4a426a8dbcec85824936de6195d1c48fdc0
6
+ metadata.gz: 25591738d1ddb68caf0fa024d42a55a41c7b77e1256bf8f3931ad6372eefdb5e6dbb57bc62ca3d064a4773160d354ac6b81022ca57259dceb4ebb6edfc5a84e8
7
+ data.tar.gz: a2b55ebd390817312ee264abf094f1f10190b8931172c2ce5cb21e0bc81fbce1a4172a03977ad9ca2323f46d1738be98f09e841e60980d91c6f40dd73715ca7b
data/.travis.yml CHANGED
@@ -1,3 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
3
+ - 2.2.4
4
+ - 2.3.0
data/README.md CHANGED
@@ -1,20 +1,22 @@
1
1
  # validates_duplicity_of
2
2
 
3
- [![Build Status](https://travis-ci.org/madwork/validates_duplicity_of.png)](https://travis-ci.org/madwork/validates_duplicity_of)
4
- [![Gem Version](https://badge.fury.io/rb/validates_duplicity_of.png)](http://badge.fury.io/rb/validates_duplicity_of)
3
+ [![Build Status](https://img.shields.io/travis/madwork/validates_duplicity_of.svg)](https://travis-ci.org/madwork/validates_duplicity_of)
4
+ [![Gem](https://img.shields.io/gem/v/validates_duplicity_of.svg)](https://rubygems.org/gems/validates_duplicity_of)
5
5
 
6
6
  ActiveRecord (>= 3.2) callback which handle the name duplication in your model with ease.
7
7
  Ruby 2 only!
8
8
 
9
9
  **Example:**
10
10
 
11
- Untitled File > Untitled File (1) > Untitled File (2)
11
+ Untitled File > Untitled File (1) > Untitled File (2) ...
12
12
 
13
13
  ## Installation
14
14
 
15
15
  Add this line to your application's Gemfile:
16
16
 
17
- gem 'validates_duplicity_of'
17
+ ```ruby
18
+ gem 'validates_duplicity_of'
19
+ ```
18
20
 
19
21
  And then execute:
20
22
 
@@ -24,37 +26,47 @@ Or install it yourself as:
24
26
 
25
27
  $ gem install validates_duplicity_of
26
28
 
27
- Or with security:
29
+ ## Usage
28
30
 
29
- $ wget http://git.io/hHL6zQ -O madwork-public_cert.pem
30
- $ gem cert --add madwork-public_cert.pem
31
- $ gem install validates_duplicity_of --trust-policy=HighSecurity
31
+ Use the method `validates_duplicity_of` with an attribute in your Rails model. The scope is optional.
32
32
 
33
- ## Usage
33
+ ```ruby
34
+ class Post < ActiveRecord::Base
35
+ validates_duplicity_of :name
36
+ end
37
+
38
+ post = Post.create name: "Foo"
39
+ post.name
40
+ => "Foo"
41
+
42
+ post = Post.create name: "Foo"
43
+ post.name
44
+ => "Foo (1)"
34
45
 
35
- Use method `validates_duplicity_of` with attribute in your Rails model. Scope is optional. Default callback is `before_save`.
46
+ class Note < ActiveRecord::Base
47
+ validates_duplicity_of :title, scope: :user_id
48
+ end
36
49
 
37
- class Post < ActiveRecord::Base
38
- validates_duplicity_of :name, scope: :user_id
39
- end
50
+ note = Note.create title: "Bar", user_id: 1
51
+ note.title
52
+ => "Bar"
40
53
 
41
- p = Post.create name: "Foo", user_id: 1
42
- p.name
43
- => "Foo"
54
+ note = Note.create title: "Bar", user_id: 1
55
+ note.title
56
+ => "Bar (1)"
57
+ ```
44
58
 
45
- p = Post.create name: "Foo", user_id: 1
46
- p.name
47
- => "Foo (1)"
59
+ ## Development
48
60
 
49
- class Note < ActiveRecord::Base
50
- validates_uniqueness_of :name
51
- validates_duplicity_of :name, callback: :before_validation
52
- end
61
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
62
+
63
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
53
64
 
54
65
  ## Contributing
55
66
 
56
- 1. Fork it
57
- 2. Create your feature branch (`git checkout -b my-new-feature`)
58
- 3. Commit your changes (`git commit -am 'Add some feature'`)
59
- 4. Push to the branch (`git push origin my-new-feature`)
60
- 5. Create new Pull Request
67
+ Bug reports and pull requests are welcome on GitHub at https://github.com/madwork/validates_duplicity_of.
68
+
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "validates_duplicity_of"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ module ValidatesDuplicityOf
2
+ class Callback
3
+ attr_reader :attr_name, :scope
4
+
5
+ def initialize(attr_name, scope)
6
+ @attr_name = attr_name
7
+ @scope = scope
8
+ end
9
+
10
+ def before_save(record)
11
+ duplicator = Duplicator.new self, record
12
+ duplicator.validate!
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module ValidatesDuplicityOf
2
+ class Duplicator
3
+ def initialize(callback, record)
4
+ @callback = callback
5
+ @record = Record.new record, callback.attr_name, callback.scope
6
+ end
7
+
8
+ # Updates the attribute of the record
9
+ def validate!
10
+ return unless @record.update_required?
11
+
12
+ if changed_attribute_match?
13
+ @record.attr_value = @record.attr_changed_value
14
+ else
15
+ @record.attr_value.concat(next_index)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # Computes the next index
22
+ def next_index
23
+ match_names = record_names.flat_map{ |name| name.match(/\((\d+)\)$/) }.compact
24
+ match_ids = match_names.flat_map(&:captures).map(&:to_i)
25
+ index = match_ids.sort.last.to_i + 1
26
+ " (#{index})"
27
+ end
28
+
29
+ # Finds the record names with this match
30
+ def record_names
31
+ @record.where_match("#{@record.attr_value} (%)").pluck(@record.attr_name)
32
+ end
33
+
34
+ def changed_attribute_match?
35
+ /#{Regexp.escape(@record.attr_value)} \(\d+\)$/.match @record.attr_changed_value
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ module ValidatesDuplicityOf
2
+ class Record < SimpleDelegator
3
+ attr_reader :attr_name, :scope
4
+
5
+ def initialize(record, attr_name, scope)
6
+ super(record)
7
+ @attr_name = attr_name.to_s
8
+ @scope = scope
9
+ end
10
+
11
+ # Callback attribute getter
12
+ def attr_value
13
+ __getobj__[attr_name]
14
+ end
15
+
16
+ # Callback attribute setter
17
+ def attr_value=(value)
18
+ __getobj__[attr_name] = value
19
+ end
20
+
21
+ def update_required?
22
+ value_changed? && value_present? && record_exists?
23
+ end
24
+
25
+ def attr_changed_value
26
+ changed_attributes[attr_name]
27
+ end
28
+
29
+ def where_match(pattern)
30
+ scoped_relation.where __getobj__.class.arel_table[attr_name].matches(pattern)
31
+ end
32
+
33
+ private
34
+
35
+ def value_changed?
36
+ changed.include? attr_name
37
+ end
38
+
39
+ def value_present?
40
+ attr_value.present?
41
+ end
42
+
43
+ def record_exists?
44
+ scoped_relation.where(attr_name => attr_value).exists?
45
+ end
46
+
47
+ def scoped_relation
48
+ scope ? __getobj__.class.where(scope => __getobj__[scope]) : __getobj__.class
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module ValidatesDuplicityOf
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -2,23 +2,15 @@ require 'validates_duplicity_of/version'
2
2
  require 'active_record'
3
3
 
4
4
  module ValidatesDuplicityOf
5
- def validates_duplicity_of(attr_name, scope: nil, callback: :before_save)
6
- validates = -> do
7
- return unless changed.include? attr_name.to_s
8
- return unless self[attr_name].present?
9
- if self.class.exists?(Hash[*[attr_name, self[attr_name], scope, self[scope]].compact])
10
- if /#{Regexp.escape(self[attr_name])} \(\d+\)$/.match changed_attributes[attr_name.to_s]
11
- self[attr_name] = changed_attributes[attr_name.to_s]
12
- else
13
- relation = scope ? self.class.where(Hash[*[scope, self[scope]]]) : self.class
14
- names = relation.where(self.class.arel_table[attr_name].matches("#{self[attr_name]} (%)")).pluck(attr_name)
15
- name_ids = names.flat_map{ |name| name.match(/\((\d+)\)$/).captures }.map(&:to_i)
16
- index = name_ids.sort.last.to_i + 1
17
- self[attr_name].concat " (#{index})"
18
- end
19
- end
20
- end
21
- set_callback *callback.to_s.split('_').reverse.map(&:to_sym), &validates # default: set_callback :save, :before, &validates
5
+ autoload :Callback, 'validates_duplicity_of/callback'
6
+ autoload :Duplicator, 'validates_duplicity_of/duplicator'
7
+ autoload :Record, 'validates_duplicity_of/record'
8
+
9
+ # Creates a before save callback for duplicate value
10
+ # @param attr_name [Symbol] the ActiveRecord attribute
11
+ # @param scope [Symbol] a scope for the query
12
+ def validates_duplicity_of(attr_name, scope: nil)
13
+ set_callback :save, :before, Callback.new(attr_name, scope)
22
14
  end
23
15
  end
24
16
 
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe "validates_duplicity_of" do
4
4
  context "without scope" do
5
- before(:each) { Post.create name: "New name" }
6
- after(:each) { Post.delete_all }
5
+ before { Post.create name: "New name" }
6
+ after { Post.delete_all }
7
7
 
8
8
  it "should update name if post already exists" do
9
9
  10.times do |index|
@@ -46,14 +46,25 @@ describe "validates_duplicity_of" do
46
46
  post = Post.create name: ""
47
47
  expect(post.name).to eq ""
48
48
  end
49
+
50
+ it "should handle invalid previous matches" do
51
+ Post.create [{ name: "Name" }, { name: "Name (first)" }]
52
+ post = Post.create name: "Name"
53
+ expect(post.name).to eq "Name (1)"
54
+ end
55
+
56
+ it "should handle non digit matches" do
57
+ Post.create name: "Name (first)"
58
+ post = Post.create name: "Name"
59
+ expect(post.name).to eq "Name"
60
+ post.update_attributes name: "Name (first)"
61
+ expect(post.name).to eq "Name (first) (1)"
62
+ end
49
63
  end
50
64
 
51
65
  context "with scope" do
52
- before(:each) do
53
- Note.create title: "New title", user_id: 1
54
- Note.create title: "New title", user_id: 2
55
- end
56
- after(:each) { Note.delete_all }
66
+ before { Note.create [{ title: "New title", user_id: 1 }, { title: "New title", user_id: 2 }] }
67
+ after { Note.delete_all }
57
68
 
58
69
  it "should update title if note already exists" do
59
70
  10.times do |index|
@@ -73,14 +84,4 @@ describe "validates_duplicity_of" do
73
84
  expect(note.title).to eq "New title (11)"
74
85
  end
75
86
  end
76
-
77
- context "with before_validation callback" do
78
- before(:each) { Comment.create name: "New name" }
79
- after(:each) { Comment.delete_all }
80
-
81
- it "should update name before validation" do
82
- comment = Comment.create name: "New name"
83
- expect(comment.name).to eq "New name (1)"
84
- end
85
- end
86
- end
87
+ end
@@ -5,8 +5,3 @@ end
5
5
  class Note < ActiveRecord::Base
6
6
  validates_duplicity_of :title, scope: :user_id
7
7
  end
8
-
9
- class Comment < ActiveRecord::Base
10
- validates_uniqueness_of :name
11
- validates_duplicity_of :name, callback: :before_validation
12
- end
@@ -9,8 +9,4 @@ ActiveRecord::Schema.define do
9
9
  t.string :title
10
10
  t.integer :user_id
11
11
  end
12
-
13
- create_table :comments, force: true do |t|
14
- t.string :name
15
- end
16
- end
12
+ end
metadata CHANGED
@@ -1,121 +1,105 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validates_duplicity_of
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Durand
8
8
  autorequire:
9
9
  bindir: bin
10
- cert_chain:
11
- - |
12
- -----BEGIN CERTIFICATE-----
13
- MIIDkjCCAnqgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMRcwFQYDVQQDDA52aW5j
14
- ZW50LmR1cmFuZDEXMBUGCgmSJomT8ixkARkWB21hZHdvcmsxEzARBgoJkiaJk/Is
15
- ZAEZFgNvcmcwHhcNMTMwOTE4MTY0MDEwWhcNMTQwOTE4MTY0MDEwWjBHMRcwFQYD
16
- VQQDDA52aW5jZW50LmR1cmFuZDEXMBUGCgmSJomT8ixkARkWB21hZHdvcmsxEzAR
17
- BgoJkiaJk/IsZAEZFgNvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
18
- AQDzlsghBvfbH4yPzi3dXyfkmxjy/uksOkYwzqwbZ6z/TvJdIjY9Xqi/gLe2AxGF
19
- kuVxDuxih84WDG8qxCnAfUxt2nDGAVEspmTXHBAsyZqLLDRiP5LvmH/eAKsJY+rU
20
- ERSJOaLAotMqHHqXiWnpRgFnzE6RM3Cnqq8LH0jIGF9F8gZVLZeRD+VXK4IX1Vyd
21
- HgZKgTPL15k92u7bzqBw7niPCBa9LfYxID6E1bnSHw/kb+7hWarVd/sxpgeH0jgb
22
- f8ieDs8S9+bL/TfzEDAOALAqMDgPXPX1mDKESDZ/wBGW/tP3NSbVKsSZFK8b0e00
23
- Au/fVbAe+P7+T8po/a5UhjMbAgMBAAGjgYgwgYUwCQYDVR0TBAIwADALBgNVHQ8E
24
- BAMCBLAwHQYDVR0OBBYEFKne49vGKR3nRMYhyEbJDI2BVQikMCUGA1UdEQQeMByB
25
- GnZpbmNlbnQuZHVyYW5kQG1hZHdvcmsub3JnMCUGA1UdEgQeMByBGnZpbmNlbnQu
26
- ZHVyYW5kQG1hZHdvcmsub3JnMA0GCSqGSIb3DQEBBQUAA4IBAQB0EDvvwy8uol26
27
- Xm4O1iYcfgkTTX11UwgI6XIbS+1AzUwhl4BdWWFFxcy7v2/UMcx4QaJm+tL2B05V
28
- CcVFS8tOMaGk6s7fPdhFswRp+HE9Ex5aiCMAhoIdUbrR5LCJTWyiEBLJhforT+3R
29
- RARQ4FabsVVHevPHxzEAUGGGdWzbG4BcvKpWxbsOz/6yVgy3dpscmanMPH6pBpNH
30
- XD6+HDD0HP0RBeQtHV2LIhLpiJLmNWRmJ9/Cfry72diEnM7173RAelpJNj0kRYjJ
31
- xDr7hfCTJQsNosd7V5q7Uz+SYPK9ALyONt5t3KKYEWS/mBCStEuQXwjIsWG/bqwr
32
- vqfQstIS
33
- -----END CERTIFICATE-----
34
- date: 2013-12-17 00:00:00.000000000 Z
10
+ cert_chain: []
11
+ date: 2016-02-22 00:00:00.000000000 Z
35
12
  dependencies:
36
13
  - !ruby/object:Gem::Dependency
37
14
  name: bundler
38
15
  requirement: !ruby/object:Gem::Requirement
39
16
  requirements:
40
- - - ~>
17
+ - - "~>"
41
18
  - !ruby/object:Gem::Version
42
19
  version: '1.3'
43
20
  type: :development
44
21
  prerelease: false
45
22
  version_requirements: !ruby/object:Gem::Requirement
46
23
  requirements:
47
- - - ~>
24
+ - - "~>"
48
25
  - !ruby/object:Gem::Version
49
26
  version: '1.3'
50
27
  - !ruby/object:Gem::Dependency
51
28
  name: rake
52
29
  requirement: !ruby/object:Gem::Requirement
53
30
  requirements:
54
- - - '>='
31
+ - - ">="
55
32
  - !ruby/object:Gem::Version
56
33
  version: '0'
57
34
  type: :development
58
35
  prerelease: false
59
36
  version_requirements: !ruby/object:Gem::Requirement
60
37
  requirements:
61
- - - '>='
38
+ - - ">="
62
39
  - !ruby/object:Gem::Version
63
40
  version: '0'
64
41
  - !ruby/object:Gem::Dependency
65
42
  name: rspec
66
43
  requirement: !ruby/object:Gem::Requirement
67
44
  requirements:
68
- - - '>='
45
+ - - ">="
69
46
  - !ruby/object:Gem::Version
70
47
  version: '2.3'
71
48
  type: :development
72
49
  prerelease: false
73
50
  version_requirements: !ruby/object:Gem::Requirement
74
51
  requirements:
75
- - - '>='
52
+ - - ">="
76
53
  - !ruby/object:Gem::Version
77
54
  version: '2.3'
78
55
  - !ruby/object:Gem::Dependency
79
56
  name: sqlite3-ruby
80
57
  requirement: !ruby/object:Gem::Requirement
81
58
  requirements:
82
- - - '>='
59
+ - - ">="
83
60
  - !ruby/object:Gem::Version
84
61
  version: '0'
85
62
  type: :development
86
63
  prerelease: false
87
64
  version_requirements: !ruby/object:Gem::Requirement
88
65
  requirements:
89
- - - '>='
66
+ - - ">="
90
67
  - !ruby/object:Gem::Version
91
68
  version: '0'
92
69
  - !ruby/object:Gem::Dependency
93
70
  name: activerecord
94
71
  requirement: !ruby/object:Gem::Requirement
95
72
  requirements:
96
- - - '>='
73
+ - - ">="
97
74
  - !ruby/object:Gem::Version
98
75
  version: '3.2'
99
76
  type: :runtime
100
77
  prerelease: false
101
78
  version_requirements: !ruby/object:Gem::Requirement
102
79
  requirements:
103
- - - '>='
80
+ - - ">="
104
81
  - !ruby/object:Gem::Version
105
82
  version: '3.2'
106
83
  description: ActiveRecord callback which handle the name duplication in your model.
107
84
  email: vincent.durand@madwork.org
108
- executables: []
85
+ executables:
86
+ - console
87
+ - setup
109
88
  extensions: []
110
89
  extra_rdoc_files: []
111
90
  files:
112
- - .gitignore
113
- - .travis.yml
91
+ - ".gitignore"
92
+ - ".travis.yml"
114
93
  - Gemfile
115
94
  - LICENSE.txt
116
95
  - README.md
117
96
  - Rakefile
97
+ - bin/console
98
+ - bin/setup
118
99
  - lib/validates_duplicity_of.rb
100
+ - lib/validates_duplicity_of/callback.rb
101
+ - lib/validates_duplicity_of/duplicator.rb
102
+ - lib/validates_duplicity_of/record.rb
119
103
  - lib/validates_duplicity_of/version.rb
120
104
  - spec/lib/validates_duplicity_of_spec.rb
121
105
  - spec/spec_helper.rb
@@ -132,17 +116,17 @@ require_paths:
132
116
  - lib
133
117
  required_ruby_version: !ruby/object:Gem::Requirement
134
118
  requirements:
135
- - - '>='
119
+ - - ">="
136
120
  - !ruby/object:Gem::Version
137
121
  version: '0'
138
122
  required_rubygems_version: !ruby/object:Gem::Requirement
139
123
  requirements:
140
- - - '>='
124
+ - - ">="
141
125
  - !ruby/object:Gem::Version
142
126
  version: '0'
143
127
  requirements: []
144
128
  rubyforge_project:
145
- rubygems_version: 2.1.11
129
+ rubygems_version: 2.5.0
146
130
  signing_key:
147
131
  specification_version: 4
148
132
  summary: ActiveRecord callback which handle the name duplication in your model with
checksums.yaml.gz.sig DELETED
@@ -1,3 +0,0 @@
1
- Ͱ��
2
- <��Y�w��F|>]3yC<M��O}SAh���z_ 볾�\;����q����VFt�b�l@C�$�2�� [�eX�Z���#:@Z���pV�E�zbLwTB�����
3
- ��b\-)5/l�Hs3�y^�We= [DU�
data.tar.gz.sig DELETED
Binary file
metadata.gz.sig DELETED
Binary file