is_unique 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,7 +1,7 @@
1
1
  Is Unique
2
2
  =========
3
3
 
4
- This is a simple ActiveRecord extension that makes sure that no duplicate records are added to the database. When you create a new model instance that already exists, it won't create a new record, but return the existing one instead. The extension generates a hash off the model attributes and use it to check for existing records. Of course, it ignores primary key, created_at, updated_at and other columns that you ask it to.
4
+ This is a simple ActiveRecord extension that makes sure that no duplicate records are added to the database. When you create a new model instance that already exists, it won't create a new record, but will return the existing one instead. The extension generates a hash off the model attributes and use it to check for existing records. Of course, it ignores primary key, created_at, updated_at and other columns that you ask it to.
5
5
 
6
6
  Here's an example. Say you have a Location model and you want all your locations to be unique. When you add a new location you'd like to check whether it already exists and if it does, you don't want to create a new record.
7
7
 
@@ -21,14 +21,14 @@ class Location < ActiveRecord::Base
21
21
  is_unique
22
22
  end
23
23
 
24
- Then fire up console and try it:
24
+ Then fire up the console and try it:
25
25
 
26
26
  >> l = Location.create(:name => 'London', :lat => '51.5', :lng => '-0.13', :alias => 'Londinium')
27
- Location Load (0.4ms) SELECT * FROM "locations" WHERE ("locations"."unique_hash" = 189338025) LIMIT 1
27
+ Location Load (0.4ms) SELECT * FROM `locations` WHERE (`locations`.`unique_hash` = '...') LIMIT 1
28
28
  Location Create (0.6ms) INSERT INTO "locations" ...
29
29
  => #<Location id: ...>
30
30
  >> l.clone.save
31
- Location Load (0.7ms) SELECT * FROM "locations" WHERE ("locations"."unique_hash" = 189338025) LIMIT 1
31
+ Location Load (0.7ms) SELECT * FROM "locations" WHERE ("locations"."unique_hash" = '...') LIMIT 1
32
32
  Location Load (0.6ms) SELECT * FROM "locations" WHERE ("locations"."id" = 1)
33
33
  => true
34
34
 
@@ -46,7 +46,7 @@ end
46
46
  >> n.alias = 'Something different'
47
47
  => "Something different"
48
48
  >> n.save
49
- Location Load (0.7ms) SELECT * FROM "locations" WHERE ("locations"."unique_hash" = 189338025) LIMIT 1
49
+ Location Load (0.7ms) SELECT * FROM "locations" WHERE ("locations"."unique_hash" = '...') LIMIT 1
50
50
  Location Load (0.6ms) SELECT * FROM "locations" WHERE ("locations"."id" = 2)
51
51
  => true
52
52
  >> n == l
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.1.0
@@ -1,6 +1,6 @@
1
1
  class <%= file_name.camelcase %> < ActiveRecord::Migration
2
2
  def self.up
3
- add_column :<%= table_name %>, :<%= unique_hash_column %>, :integer
3
+ add_column :<%= table_name %>, :<%= unique_hash_column %>, :string, :limit => 20
4
4
  add_index :<%= table_name %>, :<%= unique_hash_column %>
5
5
  # Generate hashes for existing records
6
6
  <%= class_name %>.find_each { |r| r.save(false) }
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{is_unique}
8
- s.version = "0.0.4"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Eugene Bolshakov"]
12
- s.date = %q{2010-03-26}
12
+ s.date = %q{2010-05-18}
13
13
  s.description = %q{Makes ActiveRecord return existing records instead of creating duplicates}
14
14
  s.email = %q{eugene.bolshakov@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -1,3 +1,5 @@
1
+ require 'digest/sha1'
2
+
1
3
  module IsUnique
2
4
  def self.included(base)
3
5
  base.extend IsUnique::ClassMethods
@@ -49,11 +51,15 @@ module IsUnique
49
51
  end
50
52
 
51
53
  def calculate_unique_hash
52
- self.is_unique_hash = unique_attributes.sort_by{ |pair| pair.hash }.hash
54
+ self.is_unique_hash = Digest::SHA1.digest(
55
+ unique_attribute_names.inject('') do |r, e|
56
+ r + "#{e}#{read_attribute_before_type_cast(e)}"
57
+ end
58
+ )
53
59
  end
54
60
 
55
- def unique_attributes
56
- attributes.except(*self.class.read_inheritable_attribute(:is_unique_ignore))
61
+ def unique_attribute_names
62
+ attribute_names - self.class.read_inheritable_attribute(:is_unique_ignore)
57
63
  end
58
64
  end
59
65
  end
@@ -14,7 +14,7 @@ def setup_db
14
14
  t.string :name
15
15
  t.float :lat, :lng
16
16
  t.string :alias
17
- t.integer :unique_hash
17
+ t.string :unique_hash, :length => 20
18
18
  t.timestamps
19
19
  end
20
20
  end
@@ -16,8 +16,8 @@ class PopulatedPlace < Location
16
16
  end
17
17
 
18
18
  describe "A unique model" do
19
- before(:each) do
20
- @it = Location.create(
19
+ subject do
20
+ Location.create(
21
21
  :name => 'London',
22
22
  :lat => '51.5084152563931',
23
23
  :lng => '-0.125532746315002',
@@ -25,44 +25,44 @@ describe "A unique model" do
25
25
  )
26
26
  end
27
27
 
28
+ it 'should have a 20 characters long unique hash' do
29
+ subject.unique_hash.length.should == 20
30
+ end
31
+
28
32
  it "should not create a new record with the same attributes" do
29
- new_record = @it.clone
30
- lambda { new_record.save! }.
31
- should_not change(Location, :count)
33
+ new_record = subject.clone
34
+ expect { new_record.save! }.to_not change(Location, :count)
32
35
  end
33
36
 
34
37
  it "should not create a new record with a different ignored attribute" do
35
- new_record = @it.clone
38
+ new_record = subject.clone
36
39
  new_record.alias = 'Greater London'
37
- lambda { new_record.save! }.
38
- should_not change(Location, :count)
40
+ expect { new_record.save! }.to_not change(Location, :count)
39
41
  end
40
42
 
41
43
  it "should return the existing record on creation" do
42
- new_record = @it.clone
44
+ new_record = subject.clone
43
45
  new_record.save!
44
- new_record.should == @it
46
+ new_record.should == subject
45
47
  end
46
48
 
47
49
  it "should allow to create a record with different attributes" do
48
- new_record = @it.clone
50
+ new_record = subject.clone
49
51
  new_record.name = 'Greater London'
50
- lambda { new_record.save! }.
51
- should change(Location, :count).by(1)
52
- new_record.should_not == @it
52
+ expect { new_record.save! }.to change(Location, :count).by(1)
53
+ new_record.should_not == subject
53
54
  end
54
55
 
55
56
  it "should allow to create a record with original attributes when an existing record is updated" do
56
- new_record = @it.clone
57
- @it.update_attribute(:name, 'Greater London')
58
- lambda { new_record.save! }.
59
- should change(Location, :count).by(1)
60
- new_record.should_not == @it
57
+ new_record = subject.clone
58
+ subject.update_attribute(:name, 'Greater London')
59
+ expect { new_record.save! }.to change(Location, :count).by(1)
60
+ new_record.should_not == subject
61
61
  end
62
62
 
63
63
  context "that is a subclass of another model" do
64
- before(:each) do
65
- @it = PopulatedPlace.create(
64
+ subject do
65
+ PopulatedPlace.create(
66
66
  :name => 'London',
67
67
  :lat => '51.5084152563931',
68
68
  :lng => '-0.125532746315002',
@@ -71,16 +71,15 @@ describe "A unique model" do
71
71
  end
72
72
 
73
73
  it "should be able to override columns that should be ignored" do
74
- new_record = @it.clone
74
+ new_record = subject.clone
75
75
  new_record.name = 'Greater London'
76
76
  new_record.alias = 'London, UK'
77
- lambda { new_record.save! }.
78
- should_not change(PopulatedPlace, :count)
77
+ expect { new_record.save! }.to_not change(PopulatedPlace, :count)
79
78
  end
80
79
 
81
80
  it "should run callbacks just once" do
82
- @it.should_receive(:calculate_unique_hash).once
83
- @it.save
81
+ subject.should_receive(:calculate_unique_hash).once
82
+ subject.save
84
83
  end
85
84
  end
86
85
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 4
9
- version: 0.0.4
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Eugene Bolshakov
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-26 00:00:00 +03:00
17
+ date: 2010-05-18 00:00:00 +04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency