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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 06b5583979a394c8bb5d97f869fd452349df59a7
4
+ data.tar.gz: e49fd5012a6353bd71d0190776096efe89f9cd56
5
+ SHA512:
6
+ metadata.gz: 60a701d3859d2cb0af3ad39566bc42e4d7e8548a61a73590cbfa0ec08f8e55f4b7c59eecf7836841fbb783840c25954a82e2cb422297a55f431ec0f173c4f67a
7
+ data.tar.gz: cd4dd5fa756fb02759402a60f14355cef5e263eb36da0e0bbd2254a4c97494e932d567fab742380c8f8a688a0a56345396256be6dce2d4379f43929516ce33da
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,4 @@
1
+ .ruby-gemset
2
+ .ruby-version
3
+
4
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format=doc
2
+ --color
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.1
5
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'locomotivecms_common', '~> 0.0.2', require: 'common' # path: '../common'
7
+ end
8
+
9
+ group :test do
10
+ gem 'pry'
11
+ end
12
+
13
+ # platform :jruby do
14
+ # ruby '1.9.3', engine: 'jruby', engine_version: '1.7.11'
15
+ # end
16
+ #
17
+ # platform :ruby do
18
+ # ruby '2.1.1'
19
+ # end
@@ -0,0 +1,53 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ locomotivecms_models (0.0.1.pre.alpha)
5
+ activesupport (~> 4.1.0)
6
+ i18n (~> 0.6.9)
7
+ locomotivecms_common (~> 0.0.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.1.1)
13
+ i18n (~> 0.6, >= 0.6.9)
14
+ json (~> 1.7, >= 1.7.7)
15
+ minitest (~> 5.1)
16
+ thread_safe (~> 0.1)
17
+ tzinfo (~> 1.1)
18
+ coderay (1.1.0)
19
+ colorize (0.7.3)
20
+ diff-lcs (1.2.5)
21
+ i18n (0.6.9)
22
+ json (1.8.1)
23
+ locomotivecms_common (0.0.2)
24
+ colorize
25
+ method_source (0.8.2)
26
+ minitest (5.3.5)
27
+ pry (0.9.12.6)
28
+ coderay (~> 1.0)
29
+ method_source (~> 0.8)
30
+ slop (~> 3.4)
31
+ rake (10.3.1)
32
+ rspec (2.14.1)
33
+ rspec-core (~> 2.14.0)
34
+ rspec-expectations (~> 2.14.0)
35
+ rspec-mocks (~> 2.14.0)
36
+ rspec-core (2.14.8)
37
+ rspec-expectations (2.14.5)
38
+ diff-lcs (>= 1.1.3, < 2.0)
39
+ rspec-mocks (2.14.6)
40
+ slop (3.5.0)
41
+ thread_safe (0.3.4)
42
+ tzinfo (1.2.1)
43
+ thread_safe (~> 0.1)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ locomotivecms_common (~> 0.0.2)
50
+ locomotivecms_models!
51
+ pry
52
+ rake (~> 10.3.1)
53
+ rspec (~> 2.14.1)
@@ -0,0 +1,29 @@
1
+ # LocomotiveCMS::Models
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/locomotivecms_models.svg)](http://badge.fury.io/rb/locomotivecms_models)
4
+ [![Code Climate](https://codeclimate.com/github/locomotivecms/models.png)](https://codeclimate.com/github/locomotivecms/models)
5
+ [![Dependency Status](https://gemnasium.com/locomotivecms/models.png)](https://gemnasium.com/locomotivecms/models)
6
+ [![Build Status](https://travis-ci.org/locomotivecms/models.svg?branch=master)](https://travis-ci.org/locomotivecms/models)
7
+ [![Coverage Status](https://coveralls.io/repos/locomotivecms/models/badge.png)](https://coveralls.io/r/locomotivecms/models)
8
+
9
+ LocomotiveCMS Models is persistence layer for LocomotiveCMS.
10
+
11
+ ## Configuration
12
+
13
+ Locomotive::Models.configure do |config|
14
+ config.default_adapter = Adapters::MemoryAdapter.new
15
+ end
16
+
17
+ ## Contributing
18
+
19
+ 1. Fork it
20
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
21
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
22
+ 4. Push to the branch (`git push origin my-new-feature`)
23
+ 5. Create new Pull Request
24
+
25
+ ## Contact
26
+
27
+ Feel free to contact me (did at locomotivecms dot com).
28
+
29
+ Copyright (c) 2013 NoCoffee, released under the MIT license
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rake'
6
+ require 'rspec'
7
+
8
+ require_relative 'lib/locomotive/models'
9
+
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new('spec')
12
+ task default: :spec
@@ -0,0 +1,76 @@
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
+ locale = :en
8
+
9
+ class Article
10
+ include Locomotive::Entity
11
+ attributes :title, :author, :comments
12
+ end
13
+
14
+ class Author
15
+ include Locomotive::Entity
16
+ attributes :name
17
+ end
18
+
19
+ class Comment
20
+ include Locomotive::Entity
21
+ attributes :title
22
+ end
23
+
24
+ class ArticlesRepository
25
+ include Locomotive::Repository
26
+ end
27
+
28
+ class AuthorsRepository
29
+ include Locomotive::Repository
30
+ end
31
+
32
+ class CommentsRepository
33
+ include Locomotive::Repository
34
+ end
35
+
36
+ mapper = Locomotive::Mapper.new(adapter) do
37
+ collection :articles do
38
+ entity Article
39
+ repository ArticlesRepository
40
+
41
+ attribute :title, localized: true
42
+ attribute :author, association: :authors
43
+ attribute :comments, association: :comments
44
+ end
45
+
46
+ collection :authors do
47
+ entity Author
48
+ repository AuthorsRepository
49
+
50
+ attribute :name
51
+ end
52
+
53
+ collection :comments do
54
+ entity Comment
55
+ repository CommentsRepository
56
+
57
+ attribute :title
58
+ end
59
+
60
+ end
61
+
62
+ articles_repository = Locomotive::Models[:articles]
63
+ comments_repository = Locomotive::Models[:comments]
64
+
65
+ author = Author.new(name: 'John')
66
+ authors_repository.create author
67
+
68
+ comment = Comment.new(title: 'New Comment')
69
+ comments_repository.create comment
70
+
71
+ article = Article.new(title: {en:"Title #{rand(100_000)}"}, author: author, comments: [comment])
72
+ articles_repository.create article
73
+
74
+ my_article = articles_repository.find my_article.id
75
+ my_article.author
76
+ my_article.comments
@@ -0,0 +1,35 @@
1
+ module Locomotive
2
+ module Adapters
3
+ module Memory
4
+
5
+ class Command
6
+
7
+ def initialize(dataset, collection)
8
+ @dataset, @collection = dataset, collection
9
+ end
10
+
11
+ def create(entity)
12
+ @dataset.insert(
13
+ _serialize(entity)
14
+ )
15
+ end
16
+
17
+ def update(entity)
18
+ @dataset.update(
19
+ _serialize(entity)
20
+ )
21
+ end
22
+
23
+ def destroy(entity)
24
+ @dataset.delete(entity.id)
25
+ end
26
+
27
+ private
28
+
29
+ def _serialize(entity)
30
+ @collection.serialize(entity)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,98 @@
1
+ module Locomotive
2
+ module Adapters
3
+ module Memory
4
+
5
+ class Condition
6
+
7
+ class UnsupportedOperator < StandardError; end
8
+
9
+ OPERATORS = %i(== eq ne neq matches gt gte lt lte size all in nin).freeze
10
+
11
+ attr_reader :field, :operator, :value
12
+
13
+ def initialize(operator_and_field, value, locale)
14
+ @locale = locale
15
+ @operator_and_field, @value = operator_and_field, value
16
+ @operator, @field = :==, nil
17
+
18
+ decode_operator_and_field!
19
+ end
20
+
21
+ def matches?(entry)
22
+ entry_value = entry_value(entry)
23
+
24
+ adapt_operator!(entry_value)
25
+ case @operator
26
+ when :== then entry_value == @value
27
+ when :eq then entry_value == @value
28
+ when :ne then entry_value != @value
29
+ when :neq then entry_value != @value
30
+ when :matches then @value =~ entry_value
31
+ when :gt then entry_value > @value
32
+ when :gte then entry_value >= @value
33
+ when :lt then entry_value < @value
34
+ when :lte then entry_value <= @value
35
+ when :size then entry_value.size == @value
36
+ when :all then array_contains?([*@value], entry_value)
37
+ when :in, :nin then value_is_in_entry_value?(entry_value)
38
+ else
39
+ raise UnknownConditionInScope.new("#{@operator} is unknown or not implemented.")
40
+ end
41
+ end
42
+
43
+ def to_s
44
+ "#{field} #{operator} #{@value.to_s}"
45
+ end
46
+
47
+ protected
48
+
49
+ def entry_value(entry)
50
+ case (value = entry.send(@field))
51
+ when Hash
52
+ value.fetch(@locale.to_s) { nil }
53
+ else
54
+ value
55
+ end
56
+ end
57
+
58
+ def decode_operator_and_field!
59
+ if match = @operator_and_field.match(/^(?<field>[a-z0-9_-]+)\.(?<operator>.*)$/)
60
+ @field = match[:field].to_sym
61
+ @operator = match[:operator].to_sym
62
+ check_operator!
63
+ end
64
+
65
+ @operator = :matches if @value.is_a?(Regexp)
66
+ end
67
+
68
+ def adapt_operator!(value)
69
+ case value
70
+ when Array
71
+ @operator = :in if @operator == :==
72
+ end
73
+ end
74
+
75
+ def value_is_in_entry_value?(value)
76
+ _matches = if value.is_a?(Array)
77
+ array_contains?([*value], [*@value])
78
+ else
79
+ [*@value].include?(value)
80
+ end
81
+ @operator == :in ? _matches : !_matches
82
+ end
83
+
84
+ private
85
+
86
+ def check_operator!
87
+ raise UnsupportedOperator.new unless OPERATORS.include?(@operator)
88
+ end
89
+
90
+ def array_contains?(source, target)
91
+ source & target == target
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,75 @@
1
+ module Locomotive
2
+ module Adapters
3
+ module Memory
4
+
5
+ class Dataset
6
+
7
+ class PrimaryKey
8
+ def initialize
9
+ @current = 0
10
+ end
11
+
12
+ def increment!
13
+ yield(@current += 1)
14
+ @current
15
+ end
16
+ end
17
+
18
+ attr_reader :records, :name
19
+
20
+ def initialize(name)
21
+ @name = name
22
+ clear!
23
+ end
24
+
25
+ def insert(record)
26
+ @primary_key.increment! do |id|
27
+ record[identity] = id
28
+ records[id] = record
29
+ end
30
+ end
31
+
32
+ def update(record)
33
+ records[record[identity]] = records[record[identity]].deep_merge(record)
34
+ end
35
+
36
+ def delete(id)
37
+ records.delete(id)
38
+ end
39
+
40
+ def size
41
+ records.size
42
+ end
43
+
44
+ def all
45
+ records.values
46
+ end
47
+
48
+ def find id
49
+ records.fetch(id) do
50
+ raise Locomotive::Repository::RecordNotFound, "could not find #{name} with #{identity} = #{id}"
51
+ end
52
+ end
53
+
54
+ def exists? id
55
+ !!id && records.has_key?(id)
56
+ end
57
+
58
+ def query
59
+ Query.new(self)
60
+ end
61
+
62
+ def clear!
63
+ @records = {}
64
+ @primary_key = PrimaryKey.new
65
+ end
66
+
67
+ private
68
+
69
+ def identity
70
+ @identity ||= :id
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,106 @@
1
+ require 'forwardable'
2
+
3
+ module Locomotive
4
+ module Adapters
5
+ module Memory
6
+
7
+ class Query
8
+
9
+ include Enumerable
10
+ extend Forwardable
11
+
12
+ def_delegators :all, :each, :to_s, :to_a, :empty?, :size
13
+
14
+ alias :length :size
15
+ alias :count :size
16
+
17
+ attr_reader :conditions
18
+
19
+ def initialize(dataset, locale=nil, &block)
20
+ @dataset = dataset
21
+ @conditions = []
22
+ @sorting = nil
23
+ @limit = nil
24
+ @offset = 0
25
+ @locale = locale
26
+ instance_eval(&block) if block_given?
27
+ end
28
+
29
+ def where(conditions = {})
30
+ @conditions += conditions.map { |name, value| Condition.new(name, value, @locale) }
31
+ self
32
+ end
33
+
34
+ def +(query)
35
+ @conditions += query.conditions
36
+ self
37
+ end
38
+
39
+ def order_by(order_string)
40
+ @sorting = order_string.downcase.split.map(&:to_sym) unless order_string.empty?
41
+ self
42
+ end
43
+
44
+ def limit(num)
45
+ @limit = num
46
+ self
47
+ end
48
+
49
+ def offset(num)
50
+ @offset = num
51
+ self
52
+ end
53
+
54
+ def ==(other)
55
+ if other.kind_of? Array
56
+ all == other
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def all
63
+ limited sorted(filtered)
64
+ end
65
+
66
+ def sorted(entries)
67
+ return entries if @sorting.nil?
68
+
69
+ name, direction = @sorting.first, (@sorting.last || :asc)
70
+ if direction == :asc
71
+ entries.sort { |a, b| a.send(name) <=> b.send(name) }
72
+ else
73
+ entries.sort { |a, b| b.send(name) <=> a.send(name) }
74
+ end
75
+ end
76
+
77
+ def limited(entries)
78
+ return [] if @limit == 0
79
+ return entries if @offset == 0 && @limit.nil?
80
+
81
+ subentries = entries.drop(@offset || 0)
82
+ if @limit.kind_of? Integer
83
+ subentries.take(@limit)
84
+ else
85
+ subentries
86
+ end
87
+ end
88
+
89
+ def filtered
90
+ @dataset.all.dup.find_all do |entry|
91
+ accepted = true
92
+
93
+ @conditions.each do |_condition|
94
+ unless _condition.matches?(entry)
95
+ accepted = false
96
+ break # no to go further
97
+ end
98
+ end
99
+ accepted
100
+ end
101
+ end # filtered
102
+
103
+ end
104
+ end
105
+ end
106
+ end