ohm-composite 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +49 -0
- data/UNLICENSE +24 -0
- data/lib/ohm/composite.rb +66 -0
- data/ohm-composite.gemspec +16 -0
- data/rakefile +7 -0
- data/test/collisions_test.rb +29 -0
- data/test/composite_test.rb +99 -0
- data/test/prelude.rb +11 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZGQwNWM5MWVhM2ZlZTA1MzQ2ZjFhZjQ3YzdhOWJjZDliNjc0MzZmNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NTI0YzA2ZjBmZDRiYjU2NGNjYzAxYTA5NGViNDI4MWM3MGNhYjNlNg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDdjNGI1NTdjYjk1NWQ4NGRlOThmZDI3OTQxZThlY2ExMGNkZjlmZTI1NmJk
|
10
|
+
ZDg4ZjI0MWFjNDZhNjk0NTgwZGYyM2RkOGRjODNiMmM1OTNlM2FkMDZiOGM5
|
11
|
+
ZDFiMzU1OGY3ZWU3ZDFmNDkxNzZiNmNkMDEwNDRlNTFmZjg2ZGI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDc1NGQ1YTlkZjg4ZGZhNTZiOTcxYTYzMzAyZTM0Nzc5ZTJlNmY2ZjU0MDYw
|
14
|
+
NjcyMjE0N2IxNWE3NjExY2Y2MjZhODRhZjY3MWI3NTc4YmE3N2E1ZDRhNmY1
|
15
|
+
ODAzYzExNjE4ODQwZDY4NjAzZWY4Yzg4M2FkNGZmN2YwOTI1MjM=
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
Ohm::Composite
|
2
|
+
==============
|
3
|
+
|
4
|
+
Provides composite regular and unique indices for [Ohm][1].
|
5
|
+
|
6
|
+
Usage
|
7
|
+
-----
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require "ohm/composite"
|
11
|
+
|
12
|
+
class Post
|
13
|
+
include Ohm::Composite
|
14
|
+
|
15
|
+
attribute :user_id
|
16
|
+
attribute :date
|
17
|
+
attribute :slug
|
18
|
+
|
19
|
+
composite_index [:user_id, :date]
|
20
|
+
composite_unique [:user_id, :slug]
|
21
|
+
end
|
22
|
+
|
23
|
+
Post.composite_find(user_id: 1, date: Date.today)
|
24
|
+
Post.composite_with(user_id: 1, slug: "lorem-ipsum")
|
25
|
+
```
|
26
|
+
|
27
|
+
"Find or create"
|
28
|
+
----------------
|
29
|
+
|
30
|
+
Often times you need to find an instance by its uniqueness, or create
|
31
|
+
it if it's not found.
|
32
|
+
|
33
|
+
For this purpose there's `composite_with_or_create`:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
Post.composite_with_or_create(user_id: 1, slug: "lorem-ipsum")
|
37
|
+
```
|
38
|
+
|
39
|
+
This is guaranteed to be contention-safe: if two competing threads or
|
40
|
+
processes end up in the create branch, only one will win and the other
|
41
|
+
will receive the created instance. None of them will raise exceptions
|
42
|
+
nor receive nils.
|
43
|
+
|
44
|
+
License
|
45
|
+
-------
|
46
|
+
|
47
|
+
See `UNLICENSE`. With love, from [Educabilia](http://educabilia.com).
|
48
|
+
|
49
|
+
[1]: https://github.com/soveran/ohm
|
data/UNLICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
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 NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "ohm"
|
2
|
+
|
3
|
+
module Ohm::Composite
|
4
|
+
VERSION = "0.1.0"
|
5
|
+
|
6
|
+
def self.included(model)
|
7
|
+
model.extend(Macros)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.method_name(attrs)
|
11
|
+
:"_composite_#{join(attrs, "_")}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.values(values)
|
15
|
+
join(values, ":")
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.join(elements, char)
|
19
|
+
replacement = char * 2
|
20
|
+
|
21
|
+
elements.map { |e| e.to_s.gsub(char, replacement) }.join(char)
|
22
|
+
end
|
23
|
+
|
24
|
+
module Macros
|
25
|
+
def composite_unique(attrs)
|
26
|
+
unique(_composite(attrs))
|
27
|
+
end
|
28
|
+
|
29
|
+
def composite_index(attrs)
|
30
|
+
index(_composite(attrs))
|
31
|
+
end
|
32
|
+
|
33
|
+
def composite_with(hash)
|
34
|
+
keys = hash.keys.sort
|
35
|
+
values = keys.map { |k| hash[k] }
|
36
|
+
|
37
|
+
with(Ohm::Composite.method_name(keys), Ohm::Composite.values(values))
|
38
|
+
end
|
39
|
+
|
40
|
+
def composite_find(hash)
|
41
|
+
keys = hash.keys.sort
|
42
|
+
values = keys.map { |k| hash[k] }
|
43
|
+
|
44
|
+
find(Ohm::Composite.method_name(keys) => Ohm::Composite.values(values))
|
45
|
+
end
|
46
|
+
|
47
|
+
def composite_with_or_create(hash)
|
48
|
+
composite_with(hash) ||
|
49
|
+
begin
|
50
|
+
create(hash)
|
51
|
+
rescue Ohm::UniqueIndexViolation
|
52
|
+
composite_with(hash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def _composite(attrs)
|
57
|
+
attrs = attrs.sort
|
58
|
+
|
59
|
+
method_name = Ohm::Composite.method_name(attrs)
|
60
|
+
|
61
|
+
define_method(method_name) { Ohm::Composite.values(attrs.map { |att| send(att) }) }
|
62
|
+
|
63
|
+
method_name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path("lib/ohm/composite", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "ohm-composite"
|
5
|
+
s.version = Ohm::Composite::VERSION
|
6
|
+
s.summary = "Composite (regular and unique) indices for Ohm."
|
7
|
+
s.authors = ["Educabilia", "Damian Janowski"]
|
8
|
+
s.email = ["opensource@educabilia.com", "djanowski@dimaion.com"]
|
9
|
+
s.homepage = "https://github.com/educabilia/ohm-composite"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
13
|
+
|
14
|
+
s.add_development_dependency "ohm"
|
15
|
+
s.add_development_dependency "cutest"
|
16
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "prelude"
|
2
|
+
|
3
|
+
class Post < Ohm::Model
|
4
|
+
include Ohm::Composite
|
5
|
+
|
6
|
+
attribute :foo
|
7
|
+
attribute :bar
|
8
|
+
attribute :baz
|
9
|
+
|
10
|
+
attribute :bar_baz
|
11
|
+
attribute :baz_foo
|
12
|
+
|
13
|
+
composite_unique [:foo, :bar_baz]
|
14
|
+
composite_unique [:bar, :baz_foo]
|
15
|
+
end
|
16
|
+
|
17
|
+
scope do
|
18
|
+
test "does not confuse names" do
|
19
|
+
post1 = Post.create(foo: "foo", bar_baz: "bar_baz")
|
20
|
+
|
21
|
+
assert_equal post1, Post.composite_with(foo: "foo", bar_baz: "bar_baz")
|
22
|
+
assert_equal nil, Post.composite_with(bar: "bar_baz", baz_foo: "foo")
|
23
|
+
|
24
|
+
post2 = Post.create(bar: "bar_baz", baz_foo: "foo")
|
25
|
+
|
26
|
+
assert_equal post1, Post.composite_with(foo: "foo", bar_baz: "bar_baz")
|
27
|
+
assert_equal post2, Post.composite_with(bar: "bar_baz", baz_foo: "foo")
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative "prelude"
|
2
|
+
|
3
|
+
class Post < Ohm::Model
|
4
|
+
include Ohm::Composite
|
5
|
+
|
6
|
+
attribute :date
|
7
|
+
attribute :slug
|
8
|
+
|
9
|
+
composite_index [:date, :slug]
|
10
|
+
end
|
11
|
+
|
12
|
+
class Rating < Ohm::Model
|
13
|
+
include Ohm::Composite
|
14
|
+
|
15
|
+
attribute :user_id
|
16
|
+
attribute :post_id
|
17
|
+
|
18
|
+
composite_unique [:user_id, :post_id]
|
19
|
+
end
|
20
|
+
|
21
|
+
scope do
|
22
|
+
def assert_empty(value)
|
23
|
+
flunk("#{value} is not empty.") unless value.empty?
|
24
|
+
success
|
25
|
+
end
|
26
|
+
|
27
|
+
test "find" do
|
28
|
+
post = Post.create(date: "2013-03-23", slug: "lorem-ipsum")
|
29
|
+
|
30
|
+
assert_equal [post], Post.composite_find(date: "2013-03-23", slug: "lorem-ipsum").to_a
|
31
|
+
assert_empty Post.composite_find(date: "2013-03-23", slug: "dolor-sit").to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
test "find with different hash order" do
|
35
|
+
post = Post.create(date: "2013-03-23", slug: "lorem-ipsum")
|
36
|
+
|
37
|
+
assert_equal [post], Post.composite_find(slug: "lorem-ipsum", date: "2013-03-23").to_a
|
38
|
+
end
|
39
|
+
|
40
|
+
test "fails with unknown indices" do
|
41
|
+
assert_raise { Post.composite_find(slug: "foo") }
|
42
|
+
end
|
43
|
+
|
44
|
+
test "does not confuse values when joining" do
|
45
|
+
post1 = Post.create(date: "2013-03-23:lorem", slug: "ipsum")
|
46
|
+
post2 = Post.create(date: "2013-03-23", slug: "lorem:ipsum")
|
47
|
+
|
48
|
+
assert_equal [post1], Post.composite_find(date: "2013-03-23:lorem", slug: "ipsum").to_a
|
49
|
+
assert_equal [post2], Post.composite_find(date: "2013-03-23", slug: "lorem:ipsum").to_a
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
scope do
|
54
|
+
test "unique" do
|
55
|
+
Rating.create(user_id: 1, post_id: 2)
|
56
|
+
|
57
|
+
assert_raise(Ohm::UniqueIndexViolation) do
|
58
|
+
Rating.create(user_id: 1, post_id: 2)
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_raise(Ohm::UniqueIndexViolation) do
|
62
|
+
Rating.create(post_id: 2, user_id: 1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
test "find unique" do
|
67
|
+
rating = Rating.create(user_id: 1, post_id: 2)
|
68
|
+
|
69
|
+
assert_equal rating, Rating.composite_with(user_id: 1, post_id: 2)
|
70
|
+
assert_equal rating, Rating.composite_with(post_id: 2, user_id: 1)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
scope do
|
75
|
+
test "with or create" do
|
76
|
+
rating = Rating.composite_with_or_create(user_id: 1, post_id: 2)
|
77
|
+
|
78
|
+
assert rating
|
79
|
+
|
80
|
+
assert_equal rating, Rating.composite_with_or_create(user_id: 1, post_id: 2)
|
81
|
+
end
|
82
|
+
|
83
|
+
test "contention" do
|
84
|
+
results = []
|
85
|
+
|
86
|
+
threads = Array.new(10) do
|
87
|
+
Thread.new do
|
88
|
+
10.times do |i|
|
89
|
+
results << Rating.composite_with_or_create(user_id: i, post_id: 2)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
threads.each(&:join)
|
95
|
+
|
96
|
+
assert !results.include?(nil)
|
97
|
+
assert_equal 10, results.uniq.size
|
98
|
+
end
|
99
|
+
end
|
data/test/prelude.rb
ADDED
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ohm-composite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Educabilia
|
8
|
+
- Damian Janowski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ohm
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ! '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ! '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: cutest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description:
|
43
|
+
email:
|
44
|
+
- opensource@educabilia.com
|
45
|
+
- djanowski@dimaion.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- README.md
|
51
|
+
- UNLICENSE
|
52
|
+
- lib/ohm/composite.rb
|
53
|
+
- ohm-composite.gemspec
|
54
|
+
- rakefile
|
55
|
+
- test/collisions_test.rb
|
56
|
+
- test/composite_test.rb
|
57
|
+
- test/prelude.rb
|
58
|
+
homepage: https://github.com/educabilia/ohm-composite
|
59
|
+
licenses: []
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.0.0
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Composite (regular and unique) indices for Ohm.
|
81
|
+
test_files: []
|