repository 0.0.1

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.
@@ -0,0 +1,59 @@
1
+ # a Stash is a glorified hashtable, used for keeping objects in memory
2
+ # and indexing them by key. Any objects put into the stash must have an
3
+ # key, as identified by the Keymaster class. A stash is used by Repository
4
+ # to keep the objects it recieves from its Storage.
5
+ module Repository
6
+ class Stash
7
+
8
+ attr_reader :data # for debugging only
9
+
10
+ def initialize
11
+ @data = {}
12
+ end
13
+
14
+ def size
15
+ @data.size
16
+ end
17
+
18
+ def clear
19
+ @data = {}
20
+ end
21
+
22
+ # Put an object, or an array of objects, into the stash.
23
+ # All such objects must be identifiable by the Keymaster class;
24
+ # if not, this will raise a Repository::Storage::Unidentified exception
25
+ # (possibly leaving some remaining items unstashed).
26
+ def put(object)
27
+ if object.is_a? Array
28
+ object.each do |o|
29
+ put(o)
30
+ end
31
+ else
32
+ key = object.id
33
+ raise Storage::Unidentified, "you can't stash an object without id" unless key
34
+ @data[key] = object
35
+ end
36
+ end
37
+
38
+ # get an object by key
39
+ def get(key)
40
+ @data[key]
41
+ end
42
+
43
+ alias_method :[], :get
44
+
45
+ # Finds all stashed objects that match the argument. Argument is either
46
+ # a criterion, an key, or an array of either keys or criteria.
47
+ # Returns an array of objects.
48
+ def find(arg)
49
+ if arg.is_a? Criterion
50
+ @data.values.select{|object| arg.match? object}
51
+ elsif arg.is_a? Array
52
+ arg.map{|key| get(key)}
53
+ else
54
+ [get(arg)]
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,34 @@
1
+ module Repository
2
+ class StashStorage < Storage
3
+
4
+ attr_reader :stash
5
+
6
+ def initialize(klass, stash = Stash.new)
7
+ raise "nope" if klass.is_a? Stash
8
+ raise "nuh-uh" unless stash.is_a? Stash
9
+ super(klass)
10
+ @stash = stash
11
+ end
12
+
13
+ def size
14
+ @stash.size
15
+ end
16
+
17
+ def clear
18
+ @stash.clear
19
+ end
20
+
21
+ def store(objects)
22
+ @stash.put(objects)
23
+ end
24
+
25
+ def find_by_criterion(criterion)
26
+ @stash.find(criterion)
27
+ end
28
+
29
+ def find_by_keys(keys)
30
+ @stash.find(keys)
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,78 @@
1
+ module Repository
2
+
3
+ # Storage is an abstract base class for the backing storage area of
4
+ # a Repository. Concrete implementations include Stash (for in-memory
5
+ # storage).
6
+ class Storage
7
+
8
+ class Unidentified < RuntimeError
9
+ end
10
+
11
+ class Unimplemented < RuntimeError
12
+ end
13
+
14
+ def self.for_class(klass)
15
+ StashStorage.new(klass)
16
+ end
17
+
18
+ def initialize(klass = nil)
19
+ @klass = klass
20
+ end
21
+
22
+ def size
23
+ raise Unimplemented
24
+ end
25
+
26
+ def clear
27
+ raise Unimplemented
28
+ end
29
+
30
+ # Put an object, or an array of objects, into the storage.
31
+ def store(objects)
32
+ unless objects.is_a? Array
33
+ objects = [objects]
34
+ end
35
+ objects.each do |object|
36
+ store(object)
37
+ end
38
+ end
39
+
40
+ # get an object by key
41
+ def get(key)
42
+ find_by_keys([key]).first
43
+ end
44
+
45
+ # alias for get
46
+ def [](key)
47
+ get(key)
48
+ end
49
+
50
+ # Finds all stashed objects that match the argument. Argument is either
51
+ # a criterion, an key, or an array of either keys or criteria.
52
+ # Returns an array of objects.
53
+ def find(arg)
54
+ if arg.is_a? Criterion
55
+ find_by_criterion(arg)
56
+ elsif arg.is_a? Array
57
+ find_by_keys(arg)
58
+ else
59
+ find_by_keys([arg])
60
+ end
61
+ end
62
+
63
+ protected
64
+
65
+ def store(objects)
66
+ raise Unimplemented
67
+ end
68
+
69
+ def find_by_criterion(criterion)
70
+ raise Unimplemented
71
+ end
72
+
73
+ def find_by_keys(keys)
74
+ raise Unimplemented
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.add_dependency 'active_support', [">= 0"]
5
+ gem.add_development_dependency 'pry'
6
+ gem.add_development_dependency 'rake'
7
+ gem.add_development_dependency 'rspec'
8
+
9
+ gem.authors = ["Alex Chaffee", "Nikita Fedyashev"]
10
+ gem.description = %q{A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.}
11
+ gem.email = 'loci.master@gmail.com'
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.homepage = 'http://github.com/nfedyashev/repository'
14
+ gem.name = 'repository'
15
+ gem.require_paths = ['lib']
16
+ gem.extra_rdoc_files = ['README.md']
17
+ gem.required_rubygems_version = Gem::Requirement.new(">= 1.3.6") if gem.respond_to? :required_rubygems_version=
18
+ gem.summary = %q{A Ruby implementation of the Repository Pattern}
19
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
20
+ gem.version = '0.0.1'
21
+ end
22
+
@@ -0,0 +1,21 @@
1
+ require 'ostruct'
2
+
3
+ module Repository
4
+
5
+ class Project < OpenStruct
6
+ end
7
+
8
+ class User < OpenStruct
9
+ def initialize(hash = {})
10
+ super({:name => nil}.merge(hash))
11
+ end
12
+ end
13
+
14
+ class Address < OpenStruct
15
+ end
16
+
17
+ class Country < OpenStruct
18
+ end
19
+
20
+
21
+ end
@@ -0,0 +1,51 @@
1
+ ENV["T_ENV"] = "test"
2
+ require 'repository'
3
+ require 'rspec'
4
+ require 'pry'
5
+
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+
8
+ require 'domain'
9
+
10
+ module CustomMatchers
11
+ # borrowed from http://github.com/aiwilliams/spec_goodies
12
+
13
+ class IncludeOnly # :nodoc:all
14
+ def initialize(*expected)
15
+ @expected = expected.flatten
16
+ end
17
+
18
+ def matches?(actual)
19
+ @missing = @expected.reject {|e| actual.include?(e)}
20
+ @extra = actual.reject {|e| @expected.include?(e)}
21
+ @extra.empty? && @missing.empty?
22
+ end
23
+
24
+ def failure_message
25
+ message = "expected to include only #{@expected.inspect}"
26
+ message << "\nextra: #{@extra.inspect}" unless @extra.empty?
27
+ message << "\nmissing: #{@missing.inspect}" unless @missing.empty?
28
+ message
29
+ end
30
+
31
+ def negative_failure_message
32
+ "expected to include more than #{@expected.inspect}"
33
+ end
34
+
35
+ def to_s
36
+ "include only #{@expected.inspect}"
37
+ end
38
+ end
39
+
40
+ # Unlike checking that two Enumerables are equal, where the
41
+ # objects in corresponding positions must be equal, this will
42
+ # allow you to ensure that an Enumerable has all the objects
43
+ # you expect, in any order; no more, no less.
44
+ def include_only(*expected)
45
+ IncludeOnly.new(*expected)
46
+ end
47
+ end
48
+
49
+ RSpec.configure do |config|
50
+ include CustomMatchers
51
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ module Repository
5
+ describe Criterion do
6
+ before do
7
+ @alice = User.new(:name => "Alice", :id => 1)
8
+ @bob = User.new(:name => "Bob", :id => 2)
9
+ @charlie = User.new(:name => "Charlie", :id => 3)
10
+ end
11
+
12
+ describe Criterion::And do
13
+ before do
14
+ @c = Criterion::And.new(
15
+ (@c1 = Criterion::Contains.new(:subject => "name", :value => "a")),
16
+ (@c2 = Criterion::Contains.new(:subject => "name", :value => "r"))
17
+ )
18
+ end
19
+
20
+ describe '#match' do
21
+
22
+ it "fails to match if no criteria match" do
23
+ @c.should_not be_match(@bob)
24
+ end
25
+
26
+ it "fails to match if only one criterion matches" do
27
+ @c.should_not be_match(@alice)
28
+ end
29
+
30
+ it "matches if all criteria match" do
31
+ @c.should be_match(@charlie)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe Criterion::Or do
37
+ before do
38
+ @c = Criterion::Or.new(
39
+ (@c1 = Criterion::Contains.new(:subject => "name", :value => "o")), # 'o' is only in 'bob'
40
+ (@c2 = Criterion::Contains.new(:subject => "name", :value => "r")) # 'r' is only in 'charlie'
41
+ )
42
+ end
43
+
44
+ describe '#match' do
45
+ it "fails to match if no criteria match" do
46
+ @c.should_not be_match(@alice)
47
+ end
48
+
49
+ it "matches if only one criterion matches" do
50
+ @c.should be_match(@bob)
51
+ @c.should be_match(@charlie)
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ module Repository
5
+ describe Criterion do
6
+ before do
7
+ @alice = User.new(:name => "Alice", :id => 1)
8
+ @bob = User.new(:name => "Bob", :id => 2)
9
+ @charlie = User.new(:name => "Charlie", :id => 3)
10
+ end
11
+
12
+ describe Criterion::Contains do
13
+ before do
14
+ @c = Criterion::Contains.new(:subject => "name", :value => "ABC")
15
+ end
16
+
17
+ it "has a swell descriptor" do
18
+ @c.descriptor.should == "name contains"
19
+ end
20
+
21
+ it "matches identical text" do
22
+ @c.should be_match(User.new(:name => "ABC"))
23
+ end
24
+ it "matches mixed-case identical text" do
25
+ @c.should be_match(User.new(:name => "AbC"))
26
+ end
27
+ it "matches text in the middle" do
28
+ @c.should be_match(User.new(:name => "drabCouch"))
29
+ end
30
+ it "doesn't match different text" do
31
+ @c.should_not be_match(User.new(:name => "abx"))
32
+ end
33
+ describe "multiple values" do
34
+ before do
35
+ @c = Criterion::Contains.new(:subject => "name", :descriptor => "is kinda named", :value => ["ABC", "123"])
36
+ end
37
+ it "matches any" do
38
+ @c.should be_match(User.new(:name => "abc"))
39
+ @c.should be_match(User.new(:name => "123"))
40
+ end
41
+ it "fails to match" do
42
+ @c.should_not be_match(User.new(:name => "12ab3c"))
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,79 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ module Repository
5
+ describe Criterion do
6
+ before do
7
+ @alice = User.new(:name => "Alice", :id => 1)
8
+ @bob = User.new(:name => "Bob", :id => 2)
9
+ @charlie = User.new(:name => "Charlie", :id => 3)
10
+ end
11
+
12
+ describe Criterion::Equals do
13
+ before do
14
+ @c = Criterion::Equals.new(:subject => "name", :value => "alex")
15
+ end
16
+
17
+ it "has a swell descriptor" do
18
+ Criterion::Equals.new({}).descriptor.should == "id equals"
19
+ end
20
+
21
+ describe '#match' do
22
+ describe "a string" do
23
+ before do
24
+ @c = Criterion::Equals.new(:subject => "name", :value => "Alice")
25
+ end
26
+
27
+ it "matches a string" do
28
+ @c.should be_match @alice
29
+ end
30
+
31
+ it "fails to match a string" do
32
+ @c.should_not be_match @bob
33
+ end
34
+
35
+ it "fails to match a string with the wrong case" do
36
+ @c.should_not be_match User.new(:name => "alice")
37
+ end
38
+ end
39
+
40
+ describe "an int" do
41
+ before do
42
+ @c = Criterion::Equals.new(:subject => "id", :value => 1)
43
+ end
44
+
45
+ it "matches an int" do
46
+ @c.match?(@alice).should be_true
47
+ end
48
+
49
+ it "fails to match an int" do
50
+ @c.match?(@bob).should be_false
51
+ end
52
+ end
53
+
54
+ describe "a set of ints" do
55
+ before do
56
+ @c = Criterion::Equals.new(:subject => "id", :value => [1,2])
57
+ end
58
+
59
+ it "matches" do
60
+ @c.should be_match @alice
61
+ @c.should be_match @bob
62
+ end
63
+
64
+ it "fails to match an int" do
65
+ @c.should_not be_match @charlie
66
+ end
67
+ end
68
+
69
+ describe "a string value in the criterion" do
70
+ it "should match an int value in the object" do
71
+ @c = Criterion::Equals.new(:subject => "id", :value => "1")
72
+ @c.should be_match @alice
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end