simple_unique 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/support/aws_init.rb
19
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simple_unique.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jeremy Green
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # SimpleUnique
2
+
3
+ A simple gem to add `validates_uniqueness_of` to `AWS::Record::Model`
4
+ supplied by `aws-sdk`.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'simple_unique'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install simple_unique
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ def Person < AWS::Record::Model
24
+ string_attr :name
25
+ validates_uniqueness_of :name
26
+ end
27
+ ```
28
+
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,45 @@
1
+ require 'aws/record/validator'
2
+
3
+ module AWS
4
+ module Record
5
+ class UniquenessValidator < Validator
6
+
7
+ ACCEPTED_OPTIONS = [:message, :scope, :allow_nil, :allow_blank, :if, :unless]
8
+
9
+ def validate_attribute record, attribute_name, value
10
+
11
+ # @TODO - Model the initial relation setup after the setup
12
+ # found in ActiveRecord::Validations::UniquenessValidator.
13
+ # Not sure it we have to go to such lengths or not...
14
+ relation = record.class
15
+
16
+ scope_array(options[:scope]).each do |scope_item|
17
+ scope_value = record.send(scope_item.to_sym)
18
+ relation = relation.where(scope_item.to_sym => scope_value)
19
+ end
20
+
21
+ existing_record = relation.where(attribute_name.to_sym => value).first
22
+ taken = !existing_record.nil?
23
+
24
+ record.errors.add(attribute_name, message) if taken #blank
25
+
26
+ end
27
+
28
+ def message
29
+ options[:message] || 'has already been taken'
30
+ end
31
+
32
+ protected
33
+ def scope_array(scope)
34
+ if scope.nil?
35
+ []
36
+ elsif scope.is_a? Array
37
+ scope
38
+ else
39
+ [scope]
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,93 @@
1
+ require 'simple_unique/validations/uniqueness'
2
+
3
+ module AWS
4
+ module Record
5
+
6
+ module Validations
7
+
8
+ # Validates whether the value of the specified attributes are unique across
9
+ # the system. Useful for making sure that only one user can be named “foo”.
10
+ #
11
+ # class Person < AWS::Record::Model
12
+ # validates_uniqueness_of :user_name
13
+ # end
14
+ #
15
+ # It can also validate whether the value of the specified attributes are unique based on a scope parameter:
16
+ #
17
+ # class Person < AWS::Record::Model
18
+ # validates_uniqueness_of :user_name, :scope => :account_id
19
+ # end
20
+ #
21
+ # Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once
22
+ # per semester for a particular class.
23
+ #
24
+ # class TeacherSchedule < AWS::Record::Model
25
+ # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
26
+ # end
27
+ #
28
+ # When the record is created, a check is performed to make sure that no record exists in the database
29
+ # with the given value for the specified attribute (that maps to a column). When the record is updated,
30
+ # the same check is made but disregarding the record itself.
31
+ #
32
+ # Configuration options:
33
+ # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
34
+ # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
35
+ # * <tt>:case_sensitive</tt> - Always +true+. SimpleDB does not support case insensitive search.
36
+ # * <tt>:allow_nil</tt> - Always +true+.
37
+ # * <tt>:allow_blank</tt> - Always +true+.
38
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
39
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
40
+ # The method, proc or string should return or evaluate to a true or false value.
41
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
42
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
43
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, proc or string should
44
+ # return or evaluate to a true or false value.
45
+ #
46
+ # === Concurrency and integrity
47
+ #
48
+ # Using this validation method in conjunction with AWS::Record::Model#save
49
+ # does not guarantee the absence of duplicate record insertions, because
50
+ # uniqueness checks on the application level are inherently prone to race
51
+ # conditions. For example, suppose that two users try to post a Comment at
52
+ # the same time, and a Comment's title must be unique. At the database-level,
53
+ # the actions performed by these users could be interleaved in the following manner:
54
+ #
55
+ # User 1 | User 2
56
+ # ------------------------------------+--------------------------------------
57
+ # # User 1 checks whether there's |
58
+ # # already a comment with the title |
59
+ # # 'My Post'. This is not the case. |
60
+ # SELECT * FROM comments |
61
+ # WHERE title = 'My Post' |
62
+ # |
63
+ # | # User 2 does the same thing and also
64
+ # | # infers that his title is unique.
65
+ # | SELECT * FROM comments
66
+ # | WHERE title = 'My Post'
67
+ # |
68
+ # # User 1 inserts his comment. |
69
+ # INSERT INTO comments |
70
+ # (title, content) VALUES |
71
+ # ('My Post', 'hi!') |
72
+ # |
73
+ # | # User 2 does the same thing.
74
+ # | INSERT INTO comments
75
+ # | (title, content) VALUES
76
+ # | ('My Post', 'hello!')
77
+ # |
78
+ # | # ^^^^^^
79
+ # | # Boom! We now have a duplicate
80
+ # | # title!
81
+ #
82
+ # To guard against duplicates you should create a process to periodically
83
+ # scan your data and take appropriate action when duplicates are found. (Ick!)
84
+ # This means that this validation is useful only in systems where the data creation
85
+ # events that trigger it are few and far between (relatively speaking).
86
+ def validates_uniqueness_of *args
87
+ validators << UniquenessValidator.new(self, *args)
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleUnique
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "simple_unique/version"
2
+ require "simple_unique/validations"
3
+
4
+ module SimpleUnique
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'simple_unique/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "simple_unique"
8
+ gem.version = SimpleUnique::VERSION
9
+ gem.authors = ["Jeremy Green"]
10
+ gem.email = ["jeremy@octolabs.com"]
11
+ gem.description = %q{Validations for AWS::Record::Model}
12
+ gem.summary = %q{Validations for AWS::Record::Model}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "aws-sdk", "~> 1.8.0"
21
+ gem.add_development_dependency "rspec", ">= 2.0.0"
22
+
23
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'model validations' do
4
+
5
+ let(:klass) { Class.new(AWS::Record::Base) }
6
+
7
+ before(:each) do
8
+ klass.create_domain
9
+ klass.each{|u| u.destroy }
10
+ sleep(2)
11
+ end
12
+
13
+ describe 'validates_uniqueness_of' do
14
+ it 'should only allow one record with a given attribute value' do
15
+ klass.string_attr :name
16
+ klass.validates_uniqueness_of :name
17
+ widg = klass.new(:name => "Turd Furguson")
18
+ widg.valid?.should be_true
19
+ widg.save
20
+ sleep(2) # Allow a couple of seconds for SimpleDB to catch up
21
+
22
+ widg = klass.new(:name => "Turd Furguson")
23
+ widg.valid?.should be_false
24
+ widg.errors[:name].should == ['has already been taken']
25
+ end
26
+
27
+ it 'should only allow one record with a given attribute value within a given scope' do
28
+ klass.string_attr :name
29
+ klass.string_attr :category
30
+ klass.validates_uniqueness_of :name, :scope => :category
31
+ widg = klass.new(:name => "Turd Furguson", :category => "Funny name")
32
+ widg.valid?.should be_true
33
+ widg.save
34
+ sleep(2) # Allow a couple of seconds for SimpleDB to catch up
35
+
36
+ widg = klass.new(:name => "Turd Furguson", :category => "Funny name")
37
+ widg.valid?.should be_false
38
+ widg.errors[:name].should == ['has already been taken']
39
+
40
+ widg.category = "Silly name"
41
+ widg.valid?.should be_true
42
+ end
43
+
44
+
45
+ it 'should skip the validation if the given attribute is nil when :allow_nil => true' do
46
+ klass.string_attr :name
47
+ klass.string_attr :category # need to avoid the "can't save empty record" error
48
+ klass.validates_uniqueness_of :name#, :allow_nil => true
49
+ widg = klass.new(:name => nil, :category => "A cat")
50
+ widg.valid?.should be_true
51
+ widg.save
52
+ sleep(2) # Allow a couple of seconds for SimpleDB to catch up
53
+
54
+ widg = klass.new(:name => nil, :category => "A cat")
55
+ widg.valid?.should be_true
56
+ end
57
+
58
+ it 'should skip the validation if the given attribute is blank when :allow_blank => true' do
59
+ klass.string_attr :name
60
+ klass.string_attr :category # need to avoid the "can't save empty record" error
61
+ klass.validates_uniqueness_of :name#, :allow_blank => true
62
+ widg = klass.new(:name => "", :category => "A cat")
63
+ widg.valid?.should be_true
64
+ widg.save
65
+ sleep(2) # Allow a couple of seconds for SimpleDB to catch up
66
+
67
+ widg = klass.new(:name => "", :category => "A cat")
68
+ widg.valid?.should be_true
69
+ end
70
+
71
+ it 'should skip the validation if the :if option evaluates to false' do
72
+ klass.string_attr :name
73
+ klass.boolean_attr :should_validate
74
+ klass.validates_uniqueness_of :name, :if => :should_validate
75
+ widg = klass.new(:name => "Turd Furguson")
76
+ widg.valid?.should be_true
77
+ widg.save
78
+ sleep(2) # Allow a couple of seconds for SimpleDB to catch up
79
+
80
+ widg = klass.new(:name => "Turd Furguson",:should_validate => false)
81
+ widg.valid?.should be_true
82
+ end
83
+
84
+ it 'should skip the validation if the :unless option evaluates to true' do
85
+ klass.string_attr :name
86
+ klass.boolean_attr :should_not_validate
87
+ klass.validates_uniqueness_of :name, :unless => :should_not_validate
88
+ widg = klass.new(:name => "Turd Furguson")
89
+ widg.valid?.should be_true
90
+ widg.save
91
+ sleep(2) # Allow a couple of seconds for SimpleDB to catch up
92
+
93
+ widg = klass.new(:name => "Turd Furguson",:should_not_validate => true)
94
+ widg.valid?.should be_true
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+
@@ -0,0 +1,22 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ require 'aws-sdk'
8
+ require 'support/aws_init'
9
+
10
+ require "#{File.dirname(__FILE__)}/../lib/simple_unique"
11
+
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+
17
+ # Run specs in random order to surface order dependencies. If you find an
18
+ # order dependency and want to debug it, you can fix the order by providing
19
+ # the seed, which is printed after each run.
20
+ # --seed 1234
21
+ config.order = 'random'
22
+ end
@@ -0,0 +1,4 @@
1
+ AWS_SECRET_KEY_ID='abc'
2
+ AWS_SECRET_KEY ='123'
3
+ AWS.config({:access_key_id => AWS_SECRET_KEY_ID, :secret_access_key => AWS_SECRET_KEY})
4
+ AWS::Record.domain_prefix = "aws-model-validations-test-"
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_unique
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Green
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: aws-sdk
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.8.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.8.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.0.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.0.0
46
+ description: Validations for AWS::Record::Model
47
+ email:
48
+ - jeremy@octolabs.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .rspec
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/simple_unique.rb
60
+ - lib/simple_unique/validations.rb
61
+ - lib/simple_unique/validations/uniqueness.rb
62
+ - lib/simple_unique/version.rb
63
+ - simple_unique.gemspec
64
+ - spec/simple_unique/validations_spec.rb
65
+ - spec/spec_helper.rb
66
+ - spec/support/aws_init.rb.example
67
+ homepage: ''
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.24
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Validations for AWS::Record::Model
91
+ test_files:
92
+ - spec/simple_unique/validations_spec.rb
93
+ - spec/spec_helper.rb
94
+ - spec/support/aws_init.rb.example