repository 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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