nested_attributes_uniqueness 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d3ec8e669dac67be4f9161042d74a311a8e7b883
4
+ data.tar.gz: 4c21d4d03351bf18bdde71f987e5d76e1596035b
5
+ SHA512:
6
+ metadata.gz: 0511876edbe34f270313b475024b624a94e6e9454cd2905b11788b500f582149ad4c44d3ad032c7ed1ba38bcf4b7c695073e13a1aab5424cd6947776b3fd2496
7
+ data.tar.gz: 317f3e6b4053c79f478b6d9e00d5cb3b3c508023e01cc3fb9bbf75ff46bd865916917937be8d332ae0a8aa778924afcce3423d81e3652512ba0db1d2b2bd6112
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nested_attributes_uniqueness.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ NestedAttributesUniqueness
2
+ -------
3
+
4
+ This gem provides two class methods `validates_uniqueness_in_memory` and `validates_uniqueness_in_memory_for_tree_polymorphism` which allows us to validate the uniqueness of nested attributes in memory.
5
+
6
+ Installation
7
+ -------
8
+
9
+ Add this line to your application`s Gemfile:
10
+
11
+ ```shell
12
+ gem 'nested_attributes_uniqueness', '~> 0.1.0'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```shell
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```shell
24
+ $ gem install nested_attributes_uniqueness
25
+ ```
26
+
27
+ Usage
28
+ -------
29
+
30
+ For _ActiveRecord:_
31
+
32
+ ```ruby
33
+ class User < ActiveRecord::Base
34
+ include NestedAttributesUniqueness
35
+
36
+ has_many :posts
37
+
38
+ validates_uniqueness_in_memory :posts, :name, { scope: user_id, case_sensitive: false }
39
+ end
40
+ ```
41
+
42
+
43
+ Testing
44
+ -------
45
+
46
+ ```shell
47
+ $ bundle
48
+ $ bundle exec rspec spec
49
+ ```
50
+
51
+ Contributing
52
+ -------
53
+
54
+ ```
55
+ 1. Fork it ( https://github.com/vinsol/nested_attributes_uniqueness/fork ).
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
57
+ 3. Add test cases and verify all tests are green.
58
+ 4. Commit your changes (`git commit -am 'Add some feature'`).
59
+ 5. Push to the branch (`git push origin my-new-feature`).
60
+ 6. Create new Pull Request.
61
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nested_attributes_uniqueness"
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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/gem_development ADDED
Binary file
@@ -0,0 +1,10 @@
1
+ require 'active_support'
2
+ require "nested_attributes_uniqueness/version"
3
+ require "nested_attributes_uniqueness/validator"
4
+
5
+
6
+ module NestedAttributesUniqueness
7
+ if defined?(ActiveRecord)
8
+ ActiveRecord::Base.include NestedAttributesUniqueness::Validator
9
+ end
10
+ end
@@ -0,0 +1,212 @@
1
+ # This module provides uniqueness validation for nested_attributes.
2
+ #
3
+ # +USAGE+
4
+ #
5
+ # class User < ActiveRecord::Base
6
+ # include NestedAttributesUniqueness
7
+ #
8
+ # has_many :posts
9
+ #
10
+ # validates_uniqueness_in_memory :posts, :name
11
+ # end
12
+ #
13
+ # class Post < ActiveRecord::Base
14
+ # belongs_to :user
15
+ # end
16
+
17
+ # This module also provides uniqueness validation for nested_attributes which are associated with a
18
+ # tree polymorphic association.
19
+ # It assumes that the tree polymorphic table uses container and component as the name of the
20
+ # polymorphic attributes
21
+ #
22
+ # +USAGE+
23
+ #
24
+ # class Form < ActiveRecord::Base
25
+ # has_many :form_contents, as: :container
26
+ #
27
+ # # Ensures replies have unique content across post
28
+ # validates_uniqueness_in_memory_for_polymorphism :form_contents, :sub_form_group, :sub_forms, :name
29
+ # end
30
+ #
31
+ # class FormContent < ActiveRecord::Base
32
+ # belongs_to :container, polymorphic: true
33
+ # belongs_to :component, polymorphic: true
34
+ # end
35
+ #
36
+ # class SubFormGroup < ActiveRecord::Base
37
+ # has_many :form_contents, as: :component
38
+ # has_many :sub_forms
39
+ # end
40
+ #
41
+ # class SubForm < ActiveRecord::Base
42
+ # belongs_to :sub_form_group
43
+ # has_many form_components, as: :container
44
+ # end
45
+ module NestedAttributesUniqueness
46
+ module Validator
47
+ extend ::ActiveSupport::Concern
48
+
49
+ private
50
+ # Note - can be updated w.r.t. scoped attributes combination
51
+ def validate_unique_nested_attributes(parent, collection, attribute, options = {})
52
+ return true if collection.empty?
53
+
54
+ build_default_options(options)
55
+ validate_unique_attribute_in_collection(parent, attribute, collection, options)
56
+ end
57
+
58
+ # Note - can be updated w.r.t. scoped attributes combination
59
+ def validate_unique_nested_attributes_for_tree_polymorphism(parent, polymorphic_association_name, type, collection_name, attribute, options = {})
60
+ types_with_collection = find_types_and_all_collections(parent, polymorphic_association_name, type, collection_name)
61
+ return true if types_with_collection.blank?
62
+
63
+ build_default_options(options)
64
+
65
+ # Maintains unique values for complete collection
66
+ hash = {}
67
+
68
+ types_with_collection.each do |component, collection|
69
+ validate_unique_attribute_in_collection(parent, attribute, collection, options, hash)
70
+ end
71
+ end
72
+
73
+ def build_default_options(options = {})
74
+ options[:message] ||= "has already been taken"
75
+ end
76
+
77
+ def validate_unique_attribute_in_collection(parent, attribute, collection, options, hash = {})
78
+ collection_name = collection.first.class.name.pluralize
79
+ collection.each do |record|
80
+ if (!record.marked_for_destruction? && record.errors.get(attribute).blank?)
81
+ attribute_value = record.send(attribute)
82
+ attribute_value = attribute_value.downcase if options[:case_sensitive] == false
83
+ key = [attribute_value]
84
+ key += Array.wrap(options[:scope]).map { |attribute| record.public_send(attribute) }
85
+ if hash[key] || existing_record_with_attribute?(record, attribute, collection, options)
86
+ record.errors.add(attribute, options[:message])
87
+ add_error_to_base(parent, collection_name)
88
+ else
89
+ (hash[key] = record)
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def existing_record_with_attribute?(record, attribute, collection, options)
96
+ record_id = record.id
97
+ collection_ids = collection.map(&:id).compact
98
+ record_attribute_value = record.public_send(attribute)
99
+
100
+ if record_attribute_value.is_a? Numeric
101
+ query = "#{ attribute.to_s } = #{ record_attribute_value }"
102
+ else
103
+ query = "#{ attribute.to_s } = '#{ record_attribute_value }'"
104
+ end
105
+ if record_id.present? && collection_ids.present?
106
+ ids_query_string = "(#{ collection_ids.join(',') })"
107
+ query += " AND id NOT IN #{ ids_query_string }"
108
+ end
109
+ existing_records = record.class.where(query)
110
+ records_exists = existing_records.present?
111
+ if options[:scope]
112
+ scope_value = record.public_send(options[:scope])
113
+ existing_records = existing_records.where(:"#{ options[:scope] }" => scope_value)
114
+ records_exists = existing_records.present?
115
+ end
116
+ records_exists
117
+ end
118
+
119
+ def add_error_to_base(parent, collection_name)
120
+ message = "#{ collection_name } not valid"
121
+ existing_errors = parent.errors
122
+ existing_errors_on_base = (existing_errors.present? ? existing_errors.messages[:base] : nil)
123
+ return if existing_errors_on_base.present? && existing_errors_on_base.include?(message)
124
+
125
+ parent.errors.add(:base, message)
126
+ end
127
+
128
+ # Returns a hash with component as key and its collection as value.
129
+ # component can be at any level but in hash, it is at root level
130
+ def find_types_and_all_collections(parent, polymorphic_association_name, type, collection_name)
131
+ components = find_desired_components(parent, polymorphic_association_name, type.to_s.camelize.constantize)
132
+
133
+ components_with_collection = {}
134
+
135
+ components.each do |component|
136
+ components_with_collection[component] = component.send(collection_name)
137
+
138
+ components_with_collection[component].each do |collection_element|
139
+ components_with_collection.merge!(find_types_and_all_collections(collection_element, polymorphic_association_name, type, collection_name))
140
+ end
141
+ end
142
+ components_with_collection
143
+ end
144
+
145
+ def find_desired_components(parent, polymorphic_association_name, type)
146
+ components = parent.send(:try, polymorphic_association_name).to_a.map(&:component)
147
+ components.map do |component|
148
+ if component.is_a?(type)
149
+ component
150
+ else
151
+ find_desired_components(component, polymorphic_association_name, type)
152
+ end
153
+ end.flatten.compact
154
+ end
155
+
156
+ module ClassMethods
157
+ # This method adds an +after_validation+ callback.
158
+ #
159
+ # ==== Parameters
160
+ #
161
+ # * +collection_name+ - The association name that should be used for fetching
162
+ # collection.
163
+ # * +attribute+ - The attribute name on the association that should be validated.
164
+ # * +options+ - It accepts all options that `UniqunessValidator` accepts.
165
+ # Default to no options.
166
+ #
167
+ # ==== Example
168
+ #
169
+ # # Without options
170
+ # class User < ActiveRecord::Base
171
+ # include NestedAttributesUniqueness
172
+ #
173
+ # has_many :posts
174
+ #
175
+ # validates_uniqueness_in_memory :posts, :name
176
+ # end
177
+ #
178
+ # # With options
179
+ # class User < ActiveRecord::Base
180
+ # include NestedAttributesUniqueness
181
+ #
182
+ # has_many :posts
183
+ #
184
+ # validates_uniqueness_in_memory :posts, :name, { case_sensitive: false }
185
+ # end
186
+ def validates_uniqueness_in_memory(collection_name, attribute, options = {})
187
+ after_validation do
188
+ validate_unique_nested_attributes self, public_send(collection_name), attribute, options
189
+ end
190
+ end
191
+
192
+ # This method adds an +after_validation+ callback.
193
+ #
194
+ # ==== Parameters
195
+ #
196
+ # * +polymorphic_association_name+ - Name of the tree polumorphic association
197
+ # with container and component as the name of polymorphic attributes
198
+ # * +type+ - The component type in which to look for collection
199
+ # * +collection_name+ - The association name that should be used for fetching
200
+ # collection.
201
+ # * +attribute+ - The attribute name on the association that should be validated.
202
+ # * +options+ - It accepts all options that `UniqunessValidator` accepts.
203
+ # Defaults to no options.
204
+ # Supports scope and case_sensitive options
205
+ def validates_uniqueness_in_memory_for_tree_polymorphism(polymorphic_association_name, type, collection_name, attribute, options = {})
206
+ after_validation do
207
+ validate_unique_nested_attributes_for_tree_polymorphism self, polymorphic_association_name, type, collection_name, attribute, options
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,3 @@
1
+ module NestedAttributesUniqueness
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nested_attributes_uniqueness/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nested_attributes_uniqueness"
8
+ spec.version = NestedAttributesUniqueness::VERSION
9
+ spec.authors = ["Akshay", "Ankit", "Udit"]
10
+ spec.email = ["akshay.vishnoi@vinsol.com", "ankit.bansal@vinsol.com", "udit@vinsol.com"]
11
+
12
+ spec.summary = %q{Checks for uniqueness vaidation in nested attributes for objects in memory .}
13
+ spec.description = %q{Checks for uniqueness vaidation in nested attributes for objects in memory .}
14
+ spec.homepage = "https://github.com/vinsol/nested_attributes_uniqueness"
15
+ spec.license = 'MIT'
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", ">= 1.5"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.3.0"
33
+ spec.add_development_dependency "coveralls", "~> 0.8.2"
34
+ spec.add_development_dependency "activerecord", ">=3.2.0"
35
+ spec.add_development_dependency "activesupport", ">=3.2.0"
36
+ spec.add_development_dependency "sqlite3"
37
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nested_attributes_uniqueness
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Akshay
8
+ - Ankit
9
+ - Udit
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2015-09-16 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '1.5'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '10.0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '10.0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 3.3.0
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 3.3.0
57
+ - !ruby/object:Gem::Dependency
58
+ name: coveralls
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 0.8.2
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 0.8.2
71
+ - !ruby/object:Gem::Dependency
72
+ name: activerecord
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 3.2.0
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 3.2.0
85
+ - !ruby/object:Gem::Dependency
86
+ name: activesupport
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.2.0
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 3.2.0
99
+ - !ruby/object:Gem::Dependency
100
+ name: sqlite3
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ description: Checks for uniqueness vaidation in nested attributes for objects in memory
114
+ .
115
+ email:
116
+ - akshay.vishnoi@vinsol.com
117
+ - ankit.bansal@vinsol.com
118
+ - udit@vinsol.com
119
+ executables: []
120
+ extensions: []
121
+ extra_rdoc_files: []
122
+ files:
123
+ - ".DS_Store"
124
+ - ".gitignore"
125
+ - ".rspec"
126
+ - ".travis.yml"
127
+ - Gemfile
128
+ - LICENSE
129
+ - README.md
130
+ - Rakefile
131
+ - bin/console
132
+ - bin/setup
133
+ - gem_development
134
+ - lib/nested_attributes_uniqueness.rb
135
+ - lib/nested_attributes_uniqueness/validator.rb
136
+ - lib/nested_attributes_uniqueness/version.rb
137
+ - nested_attributes_uniqueness.gemspec
138
+ homepage: https://github.com/vinsol/nested_attributes_uniqueness
139
+ licenses:
140
+ - MIT
141
+ metadata:
142
+ allowed_push_host: https://rubygems.org
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.4.5
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: Checks for uniqueness vaidation in nested attributes for objects in memory
163
+ .
164
+ test_files: []