locomotivecms_models 0.0.1.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +19 -0
  7. data/Gemfile.lock +53 -0
  8. data/README.md +29 -0
  9. data/Rakefile +12 -0
  10. data/example.rb +76 -0
  11. data/lib/locomotive/adapters/memory/command.rb +35 -0
  12. data/lib/locomotive/adapters/memory/condition.rb +98 -0
  13. data/lib/locomotive/adapters/memory/dataset.rb +75 -0
  14. data/lib/locomotive/adapters/memory/query.rb +106 -0
  15. data/lib/locomotive/adapters/memory/wrapper.rb +23 -0
  16. data/lib/locomotive/adapters/memory_adapter.rb +72 -0
  17. data/lib/locomotive/core_ext.rb +2 -0
  18. data/lib/locomotive/core_ext/hash.rb +44 -0
  19. data/lib/locomotive/core_ext/string.rb +13 -0
  20. data/lib/locomotive/decorators.rb +1 -0
  21. data/lib/locomotive/decorators/i18n_decorator.rb +45 -0
  22. data/lib/locomotive/entity.rb +28 -0
  23. data/lib/locomotive/fields/i18n_field.rb +62 -0
  24. data/lib/locomotive/mapper.rb +47 -0
  25. data/lib/locomotive/mapping.rb +16 -0
  26. data/lib/locomotive/mapping/coercer.rb +64 -0
  27. data/lib/locomotive/mapping/collection.rb +68 -0
  28. data/lib/locomotive/mapping/dereferencer.rb +40 -0
  29. data/lib/locomotive/mapping/referencer.rb +66 -0
  30. data/lib/locomotive/mapping/virtual_proxy.rb +30 -0
  31. data/lib/locomotive/models.rb +57 -0
  32. data/lib/locomotive/models/configuration.rb +13 -0
  33. data/lib/locomotive/models/version.rb +10 -0
  34. data/lib/locomotive/repository.rb +51 -0
  35. data/locomotive_models.gemspec +28 -0
  36. data/non_persisted_example.rb +70 -0
  37. data/presenter_example.rb +49 -0
  38. data/relation_example.rb +119 -0
  39. data/spec/fixtures/example_entities.rb +23 -0
  40. data/spec/fixtures/example_mapper.rb +40 -0
  41. data/spec/fixtures/example_repositories.rb +19 -0
  42. data/spec/integration/criteria_spec.rb +67 -0
  43. data/spec/integration/persistence_entity_spec.rb +75 -0
  44. data/spec/integration/relations_spec.rb +114 -0
  45. data/spec/spec_helper.rb +22 -0
  46. data/spec/support/adapters/memory.rb +39 -0
  47. data/spec/unit/adapters/memory/condition_spec.rb +120 -0
  48. data/spec/unit/adapters/memory/dataset_spec.rb +71 -0
  49. data/spec/unit/adapters/memory/query_spec.rb +69 -0
  50. data/spec/unit/decorators/i18n_decorator_spec.rb +133 -0
  51. data/spec/unit/entity_spec.rb +30 -0
  52. data/spec/unit/fields/i18n_field_spec.rb +60 -0
  53. data/spec/unit/mapper_spec.rb +60 -0
  54. data/spec/unit/mapping/coercer_spec.rb +30 -0
  55. data/spec/unit/mapping/collection_spec.rb +32 -0
  56. data/spec/unit/models_spec.rb +14 -0
  57. metadata +190 -0
@@ -0,0 +1,23 @@
1
+ module Locomotive
2
+ module Example
3
+ class Article
4
+ include Locomotive::Entity
5
+ attributes :title, :content, :author_id, :author, :comments
6
+ end
7
+
8
+ class Author
9
+ include Locomotive::Entity
10
+ attributes :name, :articles
11
+ end
12
+
13
+ class Comment
14
+ include Locomotive::Entity
15
+ attributes :title, :content, :article_id, :article
16
+ end
17
+
18
+ class Product
19
+ include Locomotive::Entity
20
+ attributes :title, :price
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ collection :products do
2
+ entity Locomotive::Example::Product
3
+ repository Locomotive::Example::ProductsRepository
4
+
5
+ attribute :title, localized: true
6
+ attribute :price
7
+ end
8
+
9
+ collection :articles do
10
+ entity Locomotive::Example::Article
11
+ repository Locomotive::Example::ArticlesRepository
12
+
13
+ attribute :title, localized: true
14
+ attribute :content, klass: String
15
+
16
+ attribute :author_id # Make reflection around keep this field or store id on author direclty
17
+ attribute :author, association: { type: :belongs_to, key: :author_id, name: :authors }
18
+
19
+ attribute :comments, association: { type: :has_many, key: :article_id, name: :comments }
20
+ end
21
+
22
+ collection :authors do
23
+ entity Locomotive::Example::Author
24
+ repository Locomotive::Example::AuthorsRepository
25
+
26
+ attribute :name
27
+
28
+ attribute :articles, association: { type: :has_many, key: :author_id, name: :articles }
29
+ end
30
+
31
+ collection :comments do
32
+ entity Locomotive::Example::Comment
33
+ repository Locomotive::Example::CommentsRepository
34
+
35
+ attribute :title
36
+ attribute :content
37
+
38
+ attribute :article_id
39
+ attribute :article, association: { type: :belongs_to, key: :article_id, name: :articles }
40
+ end
@@ -0,0 +1,19 @@
1
+ module Locomotive
2
+ module Example
3
+ class ArticlesRepository
4
+ include Locomotive::Repository
5
+ end
6
+
7
+ class AuthorsRepository
8
+ include Locomotive::Repository
9
+ end
10
+
11
+ class ProductsRepository
12
+ include Locomotive::Repository
13
+ end
14
+
15
+ class CommentsRepository
16
+ include Locomotive::Repository
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'query' do
4
+ include_context 'memory'
5
+
6
+ context 'with record', pending: true do
7
+
8
+ let(:entity) { Example::Article.new(record) }
9
+ let(:records) do
10
+ [ { title: { en: 'Screen 1', fr: 'Ecran 1' }, content: 'content 1' },
11
+ { title: { en: 'Screen 2', fr: 'Ecran 2' }, content: 'content 2' } ]
12
+ end
13
+
14
+ before do fill_articles! end
15
+
16
+ specify do
17
+ expect(articles_repository.all.size).to eq(2)
18
+ end
19
+
20
+ context 'en' do
21
+ let(:locale) { :en }
22
+
23
+ specify('can be chained') do
24
+ expect(
25
+ articles_repository.query(locale) do
26
+ where('content.eq' => 'content 1').
27
+ where('id.lt' => 2)
28
+ end.first.title[:en]
29
+ ).to eq('Screen 1')
30
+ end
31
+
32
+ specify('i18n field') do
33
+ expect(
34
+ articles_repository.query(locale) do
35
+ where('title.eq' => 'Screen 2')
36
+ where('id.gt' => 1)
37
+ end.first.title[:en]
38
+ ).to eq('Screen 2')
39
+ end
40
+
41
+ end
42
+
43
+ context 'fr' do
44
+ let(:locale) { :fr }
45
+
46
+ specify('can be chained') do
47
+ expect(
48
+ articles_repository.query(locale) do
49
+ where('content.eq' => 'content 1').
50
+ where('id.lt' => 2)
51
+ end.first.title.fr
52
+ ).to eq('Ecran 1')
53
+ end
54
+
55
+ specify('i18n field') do
56
+ expect(
57
+ articles_repository.query(locale) do
58
+ where('title.eq' => 'Ecran 2')
59
+ where('content.matches' => /content/)
60
+ where('id.gt' => 1)
61
+ end.first.title.fr
62
+ ).to eq('Ecran 2')
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ module Locomotive
4
+
5
+ describe '', pending: true do
6
+ include_context 'memory'
7
+
8
+ let(:entity) { Example::Article.new(record) }
9
+ let(:record) {{ title: { en: 'new article' }, content: 'nothing has changed' }}
10
+ let(:records) {[ record ]}
11
+ let(:locale) { :en }
12
+
13
+ describe '' do
14
+ let(:records) do
15
+ [ { title: { en: 'new article' }, content: 'nothing has changed' },
16
+ { title: { en: 'new article' }, content: 'nothing has changed' }]
17
+ end
18
+
19
+ before do fill_articles! end
20
+
21
+ specify do
22
+ expect(articles_repository.all.size).to eq(2)
23
+ end
24
+ end
25
+
26
+ describe 'write an entity' do
27
+ before do
28
+ articles_repository.create(entity)
29
+ end
30
+ specify do
31
+ expect(articles_repository.all.size).to eq(1)
32
+ end
33
+ it 'gives an ID to the entity' do
34
+ entity.id.should_not be_nil
35
+ end
36
+ end
37
+
38
+ describe 'finding an entity by its ID' do
39
+ context 'when entity exists' do
40
+ before { articles_repository.create(entity) }
41
+ subject { articles_repository.find(entity.id) }
42
+
43
+ it { should be_kind_of Example::Article }
44
+ its(:id) { should_not be_nil }
45
+ end
46
+
47
+ context 'when entity could not be found' do
48
+ subject { articles_repository.find(1234) }
49
+ it 'raises an error' do
50
+ expect { subject }.to raise_error Repository::RecordNotFound, 'could not find articles with id = 1234'
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'update an entity' do
56
+ before do
57
+ articles_repository.create(entity)
58
+ entity.title << { en: 'Jane Doe' }
59
+ articles_repository.update(entity)
60
+ end
61
+
62
+ it 'does not create a new record' do
63
+ expect(articles_repository.all.size).to eq(1)
64
+ end
65
+ end
66
+
67
+ describe 'destroying an entity' do
68
+ before { articles_repository.create(entity) }
69
+ it 'destroys an entry' do
70
+ articles_repository.destroy(entity)
71
+ expect(articles_repository.all.size).to eq(0)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'fixtures/example_entities'
3
+
4
+ module Locomotive
5
+ describe 'Handling relationships' do
6
+
7
+ include_context 'memory'
8
+
9
+ let(:article) do
10
+ Example::Article.new(title: { en:'My title' }, content: 'The article content', comments: [])
11
+ end
12
+ let(:author) { Example::Author.new(name: 'John') }
13
+ let(:comment) do
14
+ Example::Comment.new(
15
+ { title: 'awesome comment', content: 'Lorem ipsum dolor sit amet, ...' }
16
+ )
17
+ end
18
+
19
+ # let(:locale) { :en }
20
+
21
+ describe 'n-1 relationship' do
22
+
23
+ context 'set reference' do
24
+
25
+ it 'save and retrive article reference' do
26
+ articles_repository.create article
27
+ article.id.should_not be_nil
28
+ article.comments << comment
29
+ articles_repository.update article
30
+
31
+ comment.should respond_to :article_id
32
+ comment.article_id.should eq article.id
33
+
34
+ article_double = articles_repository.find(article.id)
35
+ comment = article_double.comments.first
36
+
37
+ comment.should respond_to :article_id
38
+ comment.article_id.should eq article.id
39
+ end
40
+ end
41
+
42
+ describe 'Saving and retreiving' do
43
+ before do
44
+ comments_repository.create comment
45
+ authors_repository .create author
46
+ articles_repository.create article
47
+ end
48
+
49
+ it 'allows to retreive associated record id', pending: true do
50
+ article_double = articles_repository.find(article.id)
51
+ article_double.author.id.should eql author.id
52
+ end
53
+
54
+ it 'Lazily loads the associated record', pending: true do
55
+ article_double = articles_repository.find(article.id)
56
+ article_double.author.name.should eq 'John'
57
+ article_double.author.should be_kind_of Example::Author
58
+ article_double.comments.first.should be_kind_of Example::Comment
59
+ article_double.comments.first.title.should eq 'awesome comment'
60
+ end
61
+ end
62
+
63
+ context 'when associated objects are non persisted', pending: true do
64
+
65
+ # context 'set reference' do
66
+ # let(:article) do
67
+ # Example::Article.new(title: { en:'My title' }, content: 'The article content', comments: [comment])
68
+ # end
69
+ #
70
+ # it '' do
71
+ # comment.should_not respond_to :article_id
72
+ # articles_repository.create article
73
+ # comment.should respond_to :article_id
74
+ # comment.article_id.should eq article.id
75
+ # end
76
+ # end
77
+
78
+ context 'one to many' do
79
+ before do
80
+ articles_repository.create article
81
+ article.comments << comment
82
+ articles_repository.update article
83
+ end
84
+
85
+ it 'Lazily loads the associated record' do
86
+ article_double = articles_repository.find(article.id)
87
+
88
+ comment = article_double.comments.first
89
+ comment.should be_kind_of Example::Comment
90
+ comment.id.should eq 1
91
+ comment.title.should eq 'awesome'
92
+ # comment.article_id.should eq article.id
93
+ end
94
+ end
95
+
96
+ context 'belongs to' do
97
+ before do
98
+ articles_repository.create article
99
+ article.author = author
100
+ articles_repository.update article
101
+ end
102
+
103
+ it 'Lazily loads the associated record' do
104
+ article_double = articles_repository.find(article.id)
105
+ article_double.author.name.should eq 'John'
106
+ article_double.author.should be_kind_of Example::Author
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'common'
5
+
6
+ begin
7
+ require 'pry'
8
+ rescue LoadError
9
+ end
10
+
11
+ require_relative '../lib/locomotive/models'
12
+
13
+ ENV['ADAPTER'] ||= 'memory'
14
+ load File.dirname(__FILE__) + "/support/adapters/#{ENV['ADAPTER']}.rb"
15
+ require_relative "../lib/locomotive/adapters/#{ENV['ADAPTER']}_adapter"
16
+
17
+ RSpec.configure do |config|
18
+ config.filter_run focused: true
19
+ config.run_all_when_everything_filtered = true
20
+ # config.before { }
21
+ # config.after { }
22
+ end
@@ -0,0 +1,39 @@
1
+ require 'fixtures/example_entities'
2
+ require 'fixtures/example_repositories'
3
+
4
+ RSpec.shared_context 'memory' do
5
+
6
+ let(:mapper_file) do
7
+ File.expand_path('../../../fixtures/example_mapper.rb', __FILE__)
8
+ end
9
+
10
+ let(:adapter) do
11
+ Locomotive::Adapters::MemoryAdapter
12
+ end
13
+
14
+ let!(:mapper) do
15
+ Locomotive::Mapper.load_from_file! adapter, mapper_file
16
+ end
17
+
18
+ let(:articles_repository) do
19
+ Locomotive::Example::ArticlesRepository.new(mapper)
20
+ end
21
+
22
+ let(:authors_repository) do
23
+ Locomotive::Example::AuthorsRepository.new(mapper)
24
+ end
25
+
26
+ let(:comments_repository) do
27
+ Locomotive::Example::CommentsRepository.new(mapper)
28
+ end
29
+
30
+ let(:records) do
31
+ [{ title: { en: 'new article' }, content: 'nothing has changed' }]
32
+ end
33
+
34
+ def fill_articles!
35
+ records.each do |record|
36
+ articles_repository.create(Locomotive::Example::Article.new(record))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Locomotive::Adapters::Memory::Condition do
4
+ let(:entry) {{ title: { en: 'Awesome Site' }, content: 'foo' }}
5
+ let(:locale) { :en }
6
+ let(:field) { :title }
7
+ let(:operator) { :eq }
8
+ let(:name) { "#{field}.#{operator}"}
9
+ let(:value) { 'Awesome Site' }
10
+
11
+ subject { Locomotive::Adapters::Memory::Condition.new(name, value, locale) }
12
+
13
+ describe '#entry_value' do
14
+ context 'i18n' do
15
+ let(:name) { 'title.eq' }
16
+ let(:value) { 'Awesome Site' }
17
+
18
+ context 'single entry' do
19
+ specify('should be match') do
20
+ expect(subject.matches?(entry)).to be_true
21
+ end
22
+
23
+ specify('return value') do
24
+ expect(subject.send(:entry_value, entry)).to eq(value)
25
+ end
26
+ end
27
+ end
28
+ context 'regular way' do
29
+ let(:name) { 'content.eq' }
30
+ let(:value) { 'foo' }
31
+
32
+ context 'single entry' do
33
+ specify('should be match') do
34
+ expect(subject.matches?(entry)).to be_true
35
+ end
36
+
37
+ specify('return value') do
38
+ expect(subject.send(:entry_value, entry)).to eq(value)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#decode_operator_and_field!' do
45
+ before { subject.send(:decode_operator_and_field!) }
46
+
47
+ context 'with normal value' do
48
+ specify('name should be left part of dot') { expect(subject.field).to eq(field) }
49
+ specify('operator should be right part of dot') { expect(subject.operator).to eq(operator) }
50
+ specify('right_operand should be value') { expect(subject.value).to eq(value) }
51
+ end
52
+
53
+ context 'with regex value' do
54
+ let(:value) { /^[a-z]$/ }
55
+ specify('operator should be matchtes') { expect(subject.operator).to eq(:matches) }
56
+ end
57
+ end
58
+
59
+ describe '#decode_operator_and_field!' do
60
+ context 'with unsupported operator' do
61
+ let(:name) { 'domains.unsupported' }
62
+ specify('should be throw Exception') do
63
+ expect do
64
+ subject.send(:decode_operator_and_field!)
65
+ end.to raise_error Locomotive::Adapters::Memory::Condition::UnsupportedOperator
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#adapt_operator!' do
71
+ let(:name) { 'domains.==' }
72
+ before do
73
+ subject.send(:decode_operator_and_field!)
74
+ subject.send(:adapt_operator!, value)
75
+ end
76
+ context 'with single value' do
77
+ let(:value) { 'sample.example.com' }
78
+ specify('operator should be :==') { expect(subject.operator).to eq(:==) }
79
+ end
80
+ context 'with array of values' do
81
+ let(:value) { ['sample.example.com'] }
82
+ specify('operator should be :in') { expect(subject.operator).to eq(:in) }
83
+ end
84
+ end
85
+
86
+ describe '#array_contains?' do
87
+ let(:source) { [1, 2, 3, 4] }
88
+ let(:target) { [1, 2, 3] }
89
+ context 'with target contains in source' do
90
+ specify('should be true') do
91
+ expect(subject.send(:array_contains?, source, target)).to be_true
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '#value_in_right_operand?' do
97
+ context 'value contains in right operand' do
98
+ let(:value) { [1, 2, 3, 4] }
99
+ let(:right_operand) { [1, 2, 3] }
100
+
101
+ before do
102
+ subject.stub(operator: operator, right_operand: right_operand)
103
+ end
104
+
105
+ context 'with operator :in' do
106
+ let(:operator) { :in }
107
+ specify('should return true') do
108
+ expect(subject.send(:value_is_in_entry_value?, value)).to be_true
109
+ end
110
+ end
111
+
112
+ context 'with other operator' do
113
+ let(:operator) { :nin }
114
+ specify('should not return true') do
115
+ expect(subject.send(:value_is_in_entry_value?, value)).to be_false
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end