domain_driven 0.0.8.1 → 0.0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/domain_driven.gemspec +2 -2
- data/lib/domain_driven/block_active_record.rb +3 -3
- data/lib/domain_driven/criteria.rb +1 -6
- data/lib/domain_driven/entity.rb +12 -4
- data/lib/domain_driven/model.rb +1 -1
- data/lib/domain_driven/repository.rb +22 -9
- data/lib/domain_driven/service.rb +48 -3
- data/lib/domain_driven/version.rb +2 -1
- data/spec/domain_driven/block_active_record_spec.rb +51 -0
- data/spec/domain_driven/criteria_spec.rb +2 -7
- data/spec/domain_driven/entity_spec.rb +59 -0
- data/spec/domain_driven/model_spec.rb +17 -0
- data/spec/domain_driven/repository_spec.rb +8 -0
- data/spec/domain_driven/service_spec.rb +56 -0
- data/spec/spec_helper.rb +35 -0
- metadata +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 212330e091078c401fb20be56c0afcbd6ab54743
|
4
|
+
data.tar.gz: 5f897a5e7a5a256d69483407f57585ee1551330f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a5fa9bb7c91cf0bfa70b02801a3d1dad1c26b4101a147a0cfd3006b5aca48d00fc1cf1c20bfbb5c47a5b7b9a733db2489d3bf2fe91482cbd82bdb43c08817b2
|
7
|
+
data.tar.gz: c0d1fe134f69849145a3575c38d03116ee9caec6092cb6dd9a8ba74d3ca50f84646f0c4b586340fb5cbcaece05950d6008dd245022c5fee2b0e0afc11e9dd292
|
data/domain_driven.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = DomainDriven::VERSION
|
9
9
|
spec.authors = ["Mark Windholtz", "Rob Biedenharn" ]
|
10
10
|
spec.email = ["mark@agiledna.com", "Rob@AgileConsultingLLC.com"]
|
11
|
-
spec.summary = %q{Domain Driven
|
12
|
-
spec.description = %q{
|
11
|
+
spec.summary = %q{domain_driven allows Domain Driven Design (DDD) within RubyOnRails }
|
12
|
+
spec.description = %q{domain_driven provides Rails abstract classes and generators to support for a Domain Driven Design}
|
13
13
|
spec.homepage = "https://github.com/mwindholtz/domain_driven"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -35,8 +35,8 @@ module DomainDriven
|
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
def no_db_methods
|
39
|
+
fail ActiveRecordNotAvailableError, "No DB methods on Business Objects"
|
40
|
+
end
|
41
41
|
end
|
42
42
|
end
|
@@ -24,17 +24,12 @@ module DomainDriven
|
|
24
24
|
@chain = []
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def merge(criteria)
|
28
28
|
criteria.chain.each do |e|
|
29
29
|
add_message(e)
|
30
30
|
end
|
31
31
|
self
|
32
32
|
end
|
33
|
-
|
34
|
-
def unshift(criteria)
|
35
|
-
criteria.shift(self)
|
36
|
-
criteria
|
37
|
-
end
|
38
33
|
|
39
34
|
# sugar
|
40
35
|
def add(name, *args)
|
data/lib/domain_driven/entity.rb
CHANGED
@@ -27,18 +27,19 @@ module DomainDriven
|
|
27
27
|
def entity?
|
28
28
|
true
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
|
+
alias :_real_class :class
|
31
32
|
def class
|
32
33
|
_data.class
|
33
34
|
end
|
34
|
-
|
35
|
+
|
35
36
|
def self.wrap(entity)
|
36
37
|
entity ? new(entity) : nil
|
37
38
|
end
|
38
39
|
|
39
40
|
def self.wraps(entities)
|
40
41
|
return nil unless entities
|
41
|
-
wrap(entities.extend Model).extend Collection
|
42
|
+
wrap(entities.extend Model).extend Collection
|
42
43
|
end
|
43
44
|
|
44
45
|
end
|
@@ -46,9 +47,16 @@ module DomainDriven
|
|
46
47
|
module Collection
|
47
48
|
def each
|
48
49
|
_data.each do |_item|
|
49
|
-
yield wrap(_item)
|
50
|
+
yield self._real_class.wrap(_item)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_a
|
55
|
+
_data.inject([]) do |array, _item|
|
56
|
+
array << self._real_class.wrap(_item)
|
50
57
|
end
|
51
58
|
end
|
59
|
+
|
52
60
|
def include?(other)
|
53
61
|
if other.respond_to?(:_data)
|
54
62
|
_data.include?(other._data)
|
data/lib/domain_driven/model.rb
CHANGED
@@ -1,11 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module DomainDriven
|
4
|
+
module Repository
|
5
|
+
attr_accessor :logger
|
6
|
+
def to_s
|
7
|
+
"#{@name}"
|
7
8
|
end
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
def initialize(name)
|
15
|
+
@name = name
|
16
|
+
self.logger = Logger.new(STDOUT)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(klass)
|
20
|
+
super
|
21
|
+
klass.extend ClassMethods
|
22
|
+
end
|
23
|
+
end
|
11
24
|
end
|
@@ -12,10 +12,46 @@ module DomainDriven
|
|
12
12
|
raise
|
13
13
|
rescue Pundit::NotAuthorizedError
|
14
14
|
raise
|
15
|
-
# rescue => x
|
16
|
-
# perform_rescue(x)
|
17
15
|
end
|
18
16
|
|
17
|
+
module Repo # this is the Repository Factory
|
18
|
+
module_function
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_writer :logger, :factory
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
@logger ||= Logger.new(STDOUT)
|
26
|
+
end
|
27
|
+
|
28
|
+
def factory
|
29
|
+
return @factory if @factory
|
30
|
+
raise "Repo has no factory initialized. Add an intializer file with something like this: DomainDriven::Service::Repo.factory = { course: CourseRepository, ...}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def for(name)
|
34
|
+
logger.info("Provisioning Repo #{name}")
|
35
|
+
if repo_class_name = factory[name]
|
36
|
+
repo_class = repo_class_name.to_s.classify.constantize
|
37
|
+
repo = repo_class.new(name)
|
38
|
+
repo
|
39
|
+
else
|
40
|
+
raise "repository not found: (#{name})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def method_missing(name,*args)
|
46
|
+
if name.to_s[0...4] == 'for_'
|
47
|
+
self.for(name.to_s[4..-1].to_sym)
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end # module Repo
|
54
|
+
|
19
55
|
protected
|
20
56
|
def current_user
|
21
57
|
context[:current_member]
|
@@ -25,6 +61,10 @@ module DomainDriven
|
|
25
61
|
context[:request]
|
26
62
|
end
|
27
63
|
|
64
|
+
def criteria
|
65
|
+
context[:criteria] || DomainDriven::Criteria.new
|
66
|
+
end
|
67
|
+
|
28
68
|
def perform_main
|
29
69
|
raise "subclass responsibility for #{self.class} "
|
30
70
|
end
|
@@ -34,6 +74,11 @@ module DomainDriven
|
|
34
74
|
context.fail!
|
35
75
|
end
|
36
76
|
|
37
|
-
end
|
77
|
+
end # class Service
|
78
|
+
|
38
79
|
|
80
|
+
class ServiceOrganizer
|
81
|
+
include Interactor::Organizer
|
82
|
+
end #
|
83
|
+
|
39
84
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class EntityUnderTest < DomainDriven::Entity
|
4
|
+
end
|
5
|
+
|
6
|
+
class RaiseEverything
|
7
|
+
def method_missing(name,*args)
|
8
|
+
raise "RaiseEverything should never receieve any message"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'Entity' do
|
13
|
+
|
14
|
+
Given! (:model) { RaiseEverything.new }
|
15
|
+
Given! (:entity) { EntityUnderTest.new(model) }
|
16
|
+
|
17
|
+
context 'attempting to call active_record methods should not delegate to the model: ' do
|
18
|
+
|
19
|
+
context "save" do
|
20
|
+
When(:result) { entity.save }
|
21
|
+
Then { expect(result).to have_failed(DomainDriven::ActiveRecordNotAvailableError) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "save!" do
|
25
|
+
When(:result) { entity.save! }
|
26
|
+
Then { expect(result).to have_failed(DomainDriven::ActiveRecordNotAvailableError) }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "update_attribute" do
|
30
|
+
When(:result) { entity.update_attribute }
|
31
|
+
Then { expect(result).to have_failed(DomainDriven::ActiveRecordNotAvailableError) }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "update_attribute!" do
|
35
|
+
When(:result) { entity.update_attribute! }
|
36
|
+
Then { expect(result).to have_failed(DomainDriven::ActiveRecordNotAvailableError) }
|
37
|
+
end
|
38
|
+
|
39
|
+
context "update_attributes" do
|
40
|
+
When(:result) { entity.update_attributes }
|
41
|
+
Then { expect(result).to have_failed(DomainDriven::ActiveRecordNotAvailableError) }
|
42
|
+
end
|
43
|
+
|
44
|
+
context "update_attributes!" do
|
45
|
+
When(:result) { entity.update_attributes! }
|
46
|
+
Then { expect(result).to have_failed(DomainDriven::ActiveRecordNotAvailableError) }
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -10,16 +10,11 @@ describe 'Criteria' do
|
|
10
10
|
Given { criteria1.where("id=5") }
|
11
11
|
Given { criteria2.page("3") }
|
12
12
|
|
13
|
-
context "
|
14
|
-
When(:result) { criteria1.
|
13
|
+
context "merge" do
|
14
|
+
When(:result) { criteria1.merge(criteria2) }
|
15
15
|
Then { result.to_s.should eq("target.where(id=5).page(3)") }
|
16
16
|
end
|
17
17
|
|
18
|
-
context "unshift" do
|
19
|
-
When(:result) { criteria2.unshift(criteria1) }
|
20
|
-
Then { result.to_s.should eq("target.where(id=5).page(3)") }
|
21
|
-
end
|
22
|
-
|
23
18
|
end
|
24
19
|
|
25
20
|
context "empty criteria has no effect" do
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class EntityUnderTest < DomainDriven::Entity
|
4
|
+
def some_biz_logic; 42; end
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'Entity' do
|
8
|
+
|
9
|
+
Given! (:model) { FakeModel.new }
|
10
|
+
Given! (:entity) { EntityUnderTest.wrap(model) }
|
11
|
+
|
12
|
+
context 'has model as _data ' do
|
13
|
+
Then { entity.entity?.should == true }
|
14
|
+
Then { entity._data.entity?.should == false }
|
15
|
+
Then { entity._data.should == model}
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'same as _data ' do
|
19
|
+
Then { entity._data == entity}
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'same as self' do
|
23
|
+
Then { entity == entity}
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'class name' do
|
27
|
+
context 'wrap same class as model' do
|
28
|
+
Then { entity.class.to_s == "FakeModel" }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'wrap nil is still nil' do
|
32
|
+
When(:entity) { EntityUnderTest.wrap(nil) }
|
33
|
+
Then { entity.class.to_s == "NilClass"}
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'wraps nil is still nil' do
|
37
|
+
When(:entity_list) { EntityUnderTest.wraps(nil) }
|
38
|
+
Then { entity_list.class.to_s == "NilClass"}
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'class wraps a set of models' do
|
42
|
+
When(:entity_list) { EntityUnderTest.wraps( [model, model]) }
|
43
|
+
Then { entity_list.class.to_s == "Array"}
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'class wraps each model into a set of models' do
|
47
|
+
When(:entity_list) { EntityUnderTest.wraps( [model, model]) }
|
48
|
+
Then { entity_list.each{|e| e.should be_entity } }
|
49
|
+
Then { entity_list.each{|e| e.some_biz_logic.should == 42 } }
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.wrap(entity)
|
55
|
+
entity ? new(entity) : nil
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Model' do
|
4
|
+
|
5
|
+
Given! (:model) { FakeModel.new }
|
6
|
+
|
7
|
+
Then { model._data === model }
|
8
|
+
Then { model.entity? == false }
|
9
|
+
|
10
|
+
|
11
|
+
context "find" do
|
12
|
+
When(:result) { FakeModel.find('ignore') }
|
13
|
+
Then { expect(result).to have_failed( DomainDriven::RecordNotFound, 'clever message' ) }
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
class FakeService < DomainDriven::Service
|
5
|
+
def perform_main
|
6
|
+
context[:course] = 'Its a horse-course of course'
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Service' do
|
12
|
+
|
13
|
+
context 'performing' do
|
14
|
+
When(:result) { FakeService.perform(request: {id: 123}, current_member: 'a-user',
|
15
|
+
page_param: 1,
|
16
|
+
logger: Logger.new($stdout)) }
|
17
|
+
Then { result.context[:course] == 'Its a horse-course of course' }
|
18
|
+
Then { result.context[:request][:id] == 123 }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'repository exists' do
|
22
|
+
|
23
|
+
Given(:repo) { DomainDriven::Service::Repo }
|
24
|
+
Given { repo.logger = FastSpecLogger.instance }
|
25
|
+
|
26
|
+
|
27
|
+
context 'lookup by string class name' do
|
28
|
+
Given { repo.factory = { course: 'FakeRepository' } }
|
29
|
+
|
30
|
+
context 'direct lookup ' do
|
31
|
+
When(:result) { repo.for(:course) }
|
32
|
+
Then { result.class.should == FakeRepository }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'method_missing lookup ' do
|
36
|
+
When(:result) { repo.for_course }
|
37
|
+
Then { result.class.should == FakeRepository }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'lookup by symbol' do
|
42
|
+
Given { repo.factory = { course: :fake_repository } }
|
43
|
+
When(:result) { repo.for(:course) }
|
44
|
+
Then { result.class.should == FakeRepository }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'lookup by lowercase string' do
|
48
|
+
Given { repo.factory = { course: 'fake_repository' } }
|
49
|
+
When(:result) { repo.for(:course) }
|
50
|
+
Then { result.class.should == FakeRepository }
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rspec-given'
|
3
3
|
require 'domain_driven'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
class RecordNotFound < StandardError
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class RepositoryUnderTest
|
12
|
+
include DomainDriven::Repository
|
13
|
+
end
|
14
|
+
|
15
|
+
class FakeRepository
|
16
|
+
include DomainDriven::Repository
|
17
|
+
end
|
18
|
+
|
19
|
+
class FakeBaseModel
|
20
|
+
def self.find(*)
|
21
|
+
raise(ActiveRecord::RecordNotFound.new('clever message'))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FakeModel < FakeBaseModel
|
26
|
+
include DomainDriven::Model
|
27
|
+
def course_type; 'A'; end
|
28
|
+
end
|
4
29
|
|
5
30
|
module Given
|
6
31
|
module ClassExtensions
|
@@ -8,3 +33,13 @@ module Given
|
|
8
33
|
alias Whereas! Given!
|
9
34
|
end
|
10
35
|
end
|
36
|
+
|
37
|
+
class FastSpecLogger
|
38
|
+
def self.instance
|
39
|
+
@instance ||= FastSpecLogger.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize; @last_error = ''; end
|
43
|
+
def error(value); @last_error = value; end
|
44
|
+
def info(value); @last_error = value; end
|
45
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: domain_driven
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.8.
|
4
|
+
version: 0.0.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Windholtz
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-03-
|
12
|
+
date: 2014-03-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -123,7 +123,7 @@ dependencies:
|
|
123
123
|
- - ">="
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
|
-
description:
|
126
|
+
description: domain_driven provides Rails abstract classes and generators to support
|
127
127
|
for a Domain Driven Design
|
128
128
|
email:
|
129
129
|
- mark@agiledna.com
|
@@ -148,7 +148,12 @@ files:
|
|
148
148
|
- lib/domain_driven/repository.rb
|
149
149
|
- lib/domain_driven/service.rb
|
150
150
|
- lib/domain_driven/version.rb
|
151
|
+
- spec/domain_driven/block_active_record_spec.rb
|
151
152
|
- spec/domain_driven/criteria_spec.rb
|
153
|
+
- spec/domain_driven/entity_spec.rb
|
154
|
+
- spec/domain_driven/model_spec.rb
|
155
|
+
- spec/domain_driven/repository_spec.rb
|
156
|
+
- spec/domain_driven/service_spec.rb
|
152
157
|
- spec/spec_helper.rb
|
153
158
|
homepage: https://github.com/mwindholtz/domain_driven
|
154
159
|
licenses:
|
@@ -173,7 +178,12 @@ rubyforge_project:
|
|
173
178
|
rubygems_version: 2.2.2
|
174
179
|
signing_key:
|
175
180
|
specification_version: 4
|
176
|
-
summary: Domain Driven
|
181
|
+
summary: domain_driven allows Domain Driven Design (DDD) within RubyOnRails
|
177
182
|
test_files:
|
183
|
+
- spec/domain_driven/block_active_record_spec.rb
|
178
184
|
- spec/domain_driven/criteria_spec.rb
|
185
|
+
- spec/domain_driven/entity_spec.rb
|
186
|
+
- spec/domain_driven/model_spec.rb
|
187
|
+
- spec/domain_driven/repository_spec.rb
|
188
|
+
- spec/domain_driven/service_spec.rb
|
179
189
|
- spec/spec_helper.rb
|