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,40 @@
1
+ module Locomotive
2
+ module Mapping
3
+ class Dereferencer < Struct.new(:entity, :name, :options, :record)
4
+
5
+ def deference!
6
+ case value
7
+ when Array
8
+ entity.send(:"#{name}=", Locomotive::Mapping::VirtualProxy.new do
9
+ entity.send(:"#{name}=", entities_reference(value))
10
+ end)
11
+ else
12
+ entity.send(:"#{name}=", Locomotive::Mapping::VirtualProxy.new do
13
+ entity.send(:"#{name}=", entity_reference(record[options[:association].fetch(:key)]))
14
+ end)
15
+
16
+ entity.send(:"#{options[:association].fetch(:key)}=", record[options[:association].fetch(:key)])
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def value
23
+ record[name]
24
+ end
25
+
26
+ def entity_reference value
27
+ Models[options[:association].fetch(:name)].query do
28
+ where('id.eq' => value)
29
+ end.all.first
30
+ end
31
+
32
+ def entities_reference value
33
+ Models[options[:association].fetch(:name)].query do
34
+ where('id.in' => value)
35
+ end.all
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,66 @@
1
+ module Locomotive
2
+ module Mapping
3
+ class Referencer < Struct.new(:collection, :attributes, :name, :options, :value)
4
+
5
+ def reference!
6
+ return unless value
7
+
8
+ Array(value).each do |entity| fill_associations! entity end
9
+
10
+ case value
11
+ when Array
12
+ attributes[name] = value.map(&:id)
13
+ else
14
+ attributes[name] = value.id
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def assign_parent_reference! entity
21
+ raise "Please persit parent object first" unless attributes[:id]
22
+
23
+ identity_reference_field = options[:association].fetch(:key) # article_id
24
+ entity.send :"#{identity_reference_field}=", attributes[:id]
25
+ end
26
+
27
+ def assign_child_reference! entity
28
+ identity_reference_field = options[:association].fetch(:key)
29
+ attributes[identity_reference_field] = identity(entity)
30
+ end
31
+
32
+ def fill_associations! entity
33
+ type = options[:association].fetch(:type)
34
+
35
+ if type == :has_many
36
+ assign_parent_reference! entity
37
+ if persisted? entity
38
+ Locomotive::Models[options[:association].fetch(:name)].update entity # save reference
39
+ else
40
+ Locomotive::Models[options[:association].fetch(:name)].create entity # save reference
41
+ end
42
+ else # belongs_to
43
+ assign_child_reference! entity
44
+ end
45
+ end
46
+
47
+ def identity entity
48
+ persist!(entity) if persisted?(entity)
49
+ entity.id
50
+ end
51
+
52
+ def persisted? entity
53
+ entity.id
54
+ end
55
+
56
+ def persist! entity
57
+ Locomotive::Models[options[:association].fetch(:name)].update entity
58
+ end
59
+
60
+ def reference_field
61
+ collection.name[0..-2] + '_id'
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ module Locomotive
2
+ module Mapping
3
+
4
+ class VirtualProxy < BasicObject
5
+
6
+ def initialize(&loader)
7
+ @loader = loader
8
+ @object = nil
9
+ end
10
+
11
+ def method_missing(name, *args, &block)
12
+ __load__
13
+ @object.public_send(name, *args, &block)
14
+ end
15
+
16
+ def inspect
17
+ "VirtualProxy(#{@object ? @object.inspect : ''})"
18
+ end
19
+
20
+ def __object__
21
+ @object
22
+ end
23
+
24
+ def __load__
25
+ @object ||= @loader.call
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ # Force encoding to UTF-8
2
+ Encoding.default_internal = Encoding.default_external = 'UTF-8'
3
+
4
+ # Remove I18n warnings
5
+ require 'i18n'
6
+ I18n.config.enforce_available_locales = true
7
+
8
+ require 'active_support'
9
+ require 'active_support/core_ext'
10
+
11
+ require_relative 'core_ext'
12
+ require_relative 'fields/i18n_field'
13
+ require_relative 'entity'
14
+ require_relative 'mapper'
15
+ require_relative 'mapping'
16
+ require_relative 'repository'
17
+
18
+ require_relative 'models/configuration'
19
+
20
+ module Locomotive
21
+ module Models
22
+ class << self
23
+ attr_writer :configuration
24
+ end
25
+
26
+ def self.configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def self.reset
31
+ @configuration = Configuration.new
32
+ end
33
+
34
+ def self.configure
35
+ yield(configuration)
36
+ end
37
+
38
+ def self.[] name
39
+ @mapper[name]
40
+ end
41
+
42
+ def self.mapper mapper = nil
43
+ if mapper
44
+ @mapper = mapper
45
+ else
46
+ @mapper
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # Locomotive::Common.reset
53
+ # Locomotive::Common.configure do |config|
54
+ # path = File.join(File.expand_path('log/models.log'))
55
+ # config.notifier = Locomotive::Common::Logger.setup(path)
56
+ # end
57
+ # Locomotive::Common::Logger.info 'Models...'
@@ -0,0 +1,13 @@
1
+ module Locomotive
2
+ module Models
3
+
4
+ class Configuration
5
+ # attr_accessor :default_adapter
6
+
7
+ def initialize
8
+ # self.default_adapter = Adapters::MemoryAdapter.new
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # http://semver.org/
2
+ # MAJOR.MINOR.PATCH format.
3
+ # 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
4
+ module Locomotive
5
+ module Models
6
+
7
+ VERSION = '0.0.1-alpha'
8
+
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ module Locomotive
2
+
3
+ module Repository
4
+
5
+ class RecordNotFound < StandardError; end
6
+
7
+ attr_reader :mapper
8
+
9
+ def initialize(mapper)
10
+ @mapper = mapper
11
+ @adapter = mapper.adapter
12
+ end
13
+
14
+ def all
15
+ @adapter.all(collection)
16
+ end
17
+
18
+ def find(id)
19
+ @adapter.find(collection, id)
20
+ end
21
+
22
+ def query(locale=nil, &block)
23
+ @adapter.query(collection, locale, &block)
24
+ end
25
+
26
+ def load_association! object, relation, request
27
+ mapper.load_association! object, relation, request
28
+ nil
29
+ end
30
+
31
+ def create(entity)
32
+ entity.id = @adapter.create(collection, entity)
33
+ end
34
+
35
+ def persisted?(entity)
36
+ !!entity.id && @adapter.persisted?(collection, entity)
37
+ end
38
+
39
+ def update(entity)
40
+ @adapter.update(collection, entity)
41
+ end
42
+
43
+ def destroy(entity)
44
+ @adapter.destroy(collection, entity)
45
+ end
46
+
47
+ def collection
48
+ self.class.name.split("::").last.sub(/Repository\Z/, '').scan(/[A-Z][a-z]*/).join("_").downcase.to_sym
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'lib/locomotive/models/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'locomotivecms_models'
5
+ spec.version = Locomotive::Models::VERSION
6
+ spec.platform = Gem::Platform::RUBY
7
+ spec.authors = ['Didier Lafforgue', 'Arnaud Sellenet', 'Joel Azemar']
8
+ spec.email = ['did@locomotivecms.com', 'arnaud@sellenet.fr', 'joel.azemar@gmail.com']
9
+ spec.homepage = 'http://www.locomotivecms.com'
10
+ spec.summary = 'LocomotiveCMS Models'
11
+ spec.description = 'Persistence framework for LocomotiveCMS. Default adapters: YAML'
12
+
13
+ spec.required_rubygems_version = '>= 1.3.6'
14
+ spec.rubyforge_project = 'locomotivecms_models'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+ # spec.executables = ['models']
21
+
22
+ spec.add_dependency 'activesupport', '~> 4.1.0'
23
+ spec.add_dependency 'i18n', '~> 0.6.9'
24
+ spec.add_dependency 'locomotivecms_common', '~> 0.0.1'
25
+
26
+ spec.add_development_dependency 'rake', '~> 10.3.1'
27
+ spec.add_development_dependency 'rspec', '~> 2.14.1'
28
+ end
@@ -0,0 +1,70 @@
1
+ # irb -r locomotive/models -I ./lib
2
+
3
+ require 'pry'
4
+
5
+ adapter = Locomotive::Adapters::MemoryAdapter
6
+
7
+ class Article
8
+ include Locomotive::Entity
9
+ attributes :title, :author, :comments
10
+ end
11
+
12
+ class Author
13
+ include Locomotive::Entity
14
+ attributes :name
15
+ end
16
+
17
+ class Comment
18
+ include Locomotive::Entity
19
+ attributes :title
20
+ end
21
+
22
+ class ArticlesRepository
23
+ include Locomotive::Repository
24
+ end
25
+
26
+ class AuthorsRepository
27
+ include Locomotive::Repository
28
+ end
29
+
30
+ class CommentsRepository
31
+ include Locomotive::Repository
32
+ end
33
+
34
+ mapper = Locomotive::Mapper.new(adapter) do
35
+ collection :articles do
36
+ entity Article
37
+ repository ArticlesRepository
38
+
39
+ attribute :title, localized: true
40
+ attribute :author, association: :authors
41
+ attribute :comments, association: :comments
42
+ end
43
+
44
+ collection :authors do
45
+ entity Author
46
+ repository AuthorsRepository
47
+
48
+ attribute :name
49
+ end
50
+
51
+ collection :comments do
52
+ entity Comment
53
+ repository CommentsRepository
54
+
55
+ attribute :title
56
+ end
57
+
58
+ end
59
+
60
+ articles_repository = Locomotive::Models[:articles]
61
+
62
+ author = Author.new(name: 'John')
63
+ comment = Comment.new(title: 'New Comment')
64
+ article = Article.new(title: { en: "Title #{rand(100_000)}" }, author: author, comments: [comment])
65
+
66
+ articles_repository.create article
67
+
68
+ my_article = articles_repository.find author.id
69
+ my_article.author
70
+ my_article.comments
@@ -0,0 +1,49 @@
1
+ # irb -r locomotive/models -I ./lib
2
+
3
+ require 'pry'
4
+ require_relative 'lib/locomotive/adapters/memory_adapter'
5
+
6
+ adapter = Locomotive::Adapters::MemoryAdapter
7
+
8
+ class Article
9
+ include Locomotive::Entity
10
+ attributes :title
11
+ end
12
+
13
+ class ArticlesRepository
14
+ include Locomotive::Repository
15
+ end
16
+
17
+ mapper = Locomotive::Mapper.new(adapter) do
18
+ collection :articles do
19
+ entity Article
20
+ repository ArticlesRepository
21
+
22
+ attribute :title, localized: true
23
+ end
24
+ end
25
+
26
+ articles_repository = Locomotive::Models[:articles]
27
+
28
+ article = Article.new(title: { en: "Title #{rand(100_000)}" })
29
+ articles_repository.create article
30
+
31
+ my_article = articles_repository.find article.id
32
+ my_article.title
33
+
34
+ my_article.title << { fr: "Titre #{rand(100_000)}" }
35
+ articles_repository.update my_article
36
+
37
+ my_1_article = articles_repository.find my_article.id
38
+ my_1_article.title
39
+
40
+ my_1_article.title
41
+ # => "Title 97960"
42
+ my_1_article.title.en
43
+ # => "Title 97960"
44
+ my_1_article.title[:en]
45
+ # => "Title 97960"
46
+ my_1_article.title.fr
47
+ # => "Titre 35579"
48
+ my_1_article.title[:fr]
49
+ # => "Titre 35579"
@@ -0,0 +1,119 @@
1
+ # irb -r locomotive/models -I ./lib
2
+
3
+ require 'pry'
4
+ require_relative 'lib/locomotive/adapters/memory_adapter'
5
+
6
+ adapter = Locomotive::Adapters::MemoryAdapter
7
+
8
+ class Article
9
+ include Locomotive::Entity
10
+ # attributes :title, :author, :comments
11
+ attributes :title, :comments
12
+ end
13
+
14
+ class Comment
15
+ include Locomotive::Entity
16
+ # attributes :title, :published, :article_id
17
+ attributes :title, :published, :article_id, :article
18
+ end
19
+
20
+ class ArticlesRepository
21
+ include Locomotive::Repository
22
+
23
+ def with_published_comments id
24
+ article = find(id)
25
+ load_association! article, :comments, Locomotive::Models[:comments].published_for_article_id(id)
26
+ article
27
+ end
28
+ end
29
+
30
+ class CommentsRepository
31
+ include Locomotive::Repository
32
+
33
+ def published_for_article_id id
34
+ article(id).tap do |wrapper|
35
+ wrapper.query + published.query
36
+ end
37
+ end
38
+
39
+ def published
40
+ query do
41
+ where 'published.eq' => true
42
+ end
43
+ end
44
+
45
+ def article id
46
+ query do
47
+ where 'article_id.eq' => id
48
+ end
49
+ end
50
+ end
51
+
52
+ Locomotive::Mapper.new(adapter) do
53
+ collection :articles do
54
+ entity Article
55
+ repository ArticlesRepository
56
+
57
+ attribute :title, localized: true
58
+ # attribute :comments, association: :comments
59
+ attribute :comments, association: { type: :has_many, key: :article_id, name: :comments }
60
+ end
61
+
62
+ collection :comments do
63
+ entity Comment
64
+ repository CommentsRepository
65
+
66
+ attribute :title
67
+ attribute :published
68
+ attribute :article_id
69
+ attribute :article, association: { type: :belongs_to, key: :article_id, name: :articles }
70
+ end
71
+ end
72
+
73
+ articles_repository = Locomotive::Models[:articles]
74
+ comments_repository = Locomotive::Models[:comments]
75
+
76
+ unpublished_comment = Comment.new(title: 'New Comment', published: false)
77
+ published_comment = Comment.new(title: 'New Comment', published: true)
78
+ another_comment = Comment.new(title: 'New Comment', published: true)
79
+
80
+ comments_repository.create unpublished_comment
81
+ comments_repository.create published_comment
82
+ comments_repository.create another_comment
83
+
84
+ article = Article.new(title: { en: "Title #{rand(100_000)}" }, comments: [])
85
+ articles_repository.create article
86
+
87
+ article.comments << unpublished_comment
88
+ article.comments << published_comment
89
+
90
+ articles_repository.update article
91
+
92
+ my_article = articles_repository.find article.id
93
+ my_article
94
+ my_article.comments
95
+ # => VirtualProxy()
96
+ my_article.comments.first.article
97
+ # => VirtualProxy()
98
+ my_article.comments.first.article.title
99
+ # => Title 60716
100
+ my_article.comments.first.article
101
+ => #<Article:0x00000101394b10 @title=Title 60716, @comments=VirtualProxy(), @id=1>
102
+ my_article.comments.first.article.comments
103
+ # => VirtualProxy()
104
+ my_article.comments.first.article.comments.first
105
+ # => #<Comment:0x0000010136f978 @title="New Comment", @published=false, @id=1, @article_id=1, @article=VirtualProxy()>
106
+ my_article.comments.first.article.comments.first.title
107
+ # => "New Comment"
108
+
109
+ my_article = articles_repository.with_published_comments article.id
110
+ my_article.comments.all
111
+
112
+ Locomotive::Models[:comments].all.size
113
+
114
+ # comments_repository.article article.id
115
+
116
+ # articles_repository.load_association! my_article, :comments, comments_repository.published
117
+ #
118
+ # my_article = articles_repository.with_published_comments article.id
119
+ # my_article.comments