active_mapper 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +62 -0
  9. data/Rakefile +1 -0
  10. data/active_mapper.gemspec +26 -0
  11. data/lib/active_mapper/adapter/active_record/order/attribute.rb +34 -0
  12. data/lib/active_mapper/adapter/active_record/order.rb +30 -0
  13. data/lib/active_mapper/adapter/active_record/query/attribute.rb +63 -0
  14. data/lib/active_mapper/adapter/active_record/query/expression.rb +63 -0
  15. data/lib/active_mapper/adapter/active_record/query.rb +23 -0
  16. data/lib/active_mapper/adapter/active_record.rb +96 -0
  17. data/lib/active_mapper/adapter/memory/order/attribute.rb +40 -0
  18. data/lib/active_mapper/adapter/memory/order.rb +31 -0
  19. data/lib/active_mapper/adapter/memory/query/attribute.rb +61 -0
  20. data/lib/active_mapper/adapter/memory/query/expression.rb +81 -0
  21. data/lib/active_mapper/adapter/memory/query.rb +22 -0
  22. data/lib/active_mapper/adapter/memory.rb +80 -0
  23. data/lib/active_mapper/adapter.rb +7 -0
  24. data/lib/active_mapper/mapper.rb +107 -0
  25. data/lib/active_mapper/relation.rb +133 -0
  26. data/lib/active_mapper/version.rb +3 -0
  27. data/lib/active_mapper.rb +24 -0
  28. data/spec/active_mapper/adapter/active_record/order_spec.rb +11 -0
  29. data/spec/active_mapper/adapter/active_record_spec.rb +164 -0
  30. data/spec/active_mapper/adapter/memory_spec.rb +158 -0
  31. data/spec/active_mapper/mapper_spec.rb +195 -0
  32. data/spec/active_mapper/relation_spec.rb +190 -0
  33. data/spec/active_mapper_spec.rb +25 -0
  34. data/spec/features/active_record_spec.rb +7 -0
  35. data/spec/features/memory_spec.rb +5 -0
  36. data/spec/spec_helper.rb +30 -0
  37. data/spec/support/models/user.rb +20 -0
  38. data/spec/support/shared/active_mapper_integration.rb +129 -0
  39. metadata +162 -0
@@ -0,0 +1,22 @@
1
+ require 'active_mapper/adapter/memory/query/attribute'
2
+ require 'active_mapper/adapter/memory/query/expression'
3
+
4
+ module ActiveMapper
5
+ module Adapter
6
+ class Memory
7
+ class Query
8
+ def initialize(&block)
9
+ @block = block
10
+ end
11
+
12
+ def to_proc
13
+ @block ? @block.call(self).to_proc : proc { true }
14
+ end
15
+
16
+ def method_missing(name, *args, &block)
17
+ Attribute.new(name)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ require 'active_mapper/adapter/memory/query'
2
+ require 'active_mapper/adapter/memory/order'
3
+
4
+ module ActiveMapper
5
+ module Adapter
6
+ class Memory
7
+ def initialize
8
+ @collection = {}
9
+ end
10
+
11
+ def find(klass, id)
12
+ collection(klass)[id]
13
+ end
14
+
15
+ def where(klass, options = {}, &block)
16
+ query = Query.new(&block)
17
+ order = Order.new(&options[:order])
18
+
19
+ records = collection(klass).values.select(&query.to_proc)
20
+ records = records.sort(&order.to_proc)
21
+ records = records.drop(options[:offset]) if options[:offset]
22
+ records = records.take(options[:limit]) if options[:limit]
23
+
24
+ records
25
+ end
26
+
27
+ def count(klass, &block)
28
+ where(klass, &block).count
29
+ end
30
+
31
+ def minimum(klass, attribute, &block)
32
+ where(klass, order: proc { |object| [object.send(attribute)] }, &block).first.send(attribute)
33
+ end
34
+
35
+ def maximum(klass, attribute, &block)
36
+ where(klass, order: proc { |object| [-object.send(attribute)] }, &block).first.send(attribute)
37
+ end
38
+
39
+ def average(klass, attribute, &block)
40
+ sum(klass, attribute, &block).to_f / count(klass, &block)
41
+ end
42
+
43
+ def sum(klass, attribute, &block)
44
+ where(klass, &block).sum(&:"#{attribute}")
45
+ end
46
+
47
+ def insert(klass, object)
48
+ object = object.dup
49
+ object.id = (collection(klass).keys.last || 0) + 1
50
+
51
+ collection(klass)[object.id] = object
52
+ object.id
53
+ end
54
+
55
+ def update(klass, object)
56
+ collection(klass)[object.id] = object.dup
57
+ end
58
+
59
+ def delete(klass, object)
60
+ collection(klass).delete(object.id)
61
+ end
62
+
63
+ def delete_all(klass, &block)
64
+ where(klass, &block).each do |object|
65
+ delete(klass, object)
66
+ end
67
+ end
68
+
69
+ def unserialize(klass, object)
70
+ object
71
+ end
72
+
73
+ private
74
+
75
+ def collection(klass)
76
+ @collection[klass] ||= {}
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,7 @@
1
+ require 'active_mapper/adapter/memory'
2
+ require 'active_mapper/adapter/active_record'
3
+
4
+ module ActiveMapper
5
+ module Adapter
6
+ end
7
+ end
@@ -0,0 +1,107 @@
1
+ module ActiveMapper
2
+ class Mapper
3
+ attr_reader :mapped_class, :adapter
4
+
5
+ def initialize(mapped_class, adapter)
6
+ @mapped_class = mapped_class
7
+ @adapter = adapter
8
+ end
9
+
10
+ def all?(&block)
11
+ count == count(&block)
12
+ end
13
+
14
+ def any?(&block)
15
+ select(&block).any?
16
+ end
17
+
18
+ def none?(&block)
19
+ select(&block).none?
20
+ end
21
+
22
+ def one?(&block)
23
+ select(&block).one?
24
+ end
25
+
26
+ def count(&block)
27
+ select(&block).count
28
+ end
29
+
30
+ def min(attribute)
31
+ find_all.min(attribute)
32
+ end
33
+ alias :min_by :min
34
+ alias :minimum :min
35
+
36
+ def max(attribute)
37
+ find_all.max(attribute)
38
+ end
39
+ alias :max_by :max
40
+ alias :maximum :max
41
+
42
+ def minmax(attribute)
43
+ find_all.minmax(attribute)
44
+ end
45
+ alias :minmax_by :minmax
46
+
47
+ def avg(attribute)
48
+ find_all.avg(attribute)
49
+ end
50
+ alias :average :avg
51
+
52
+ def sum(attribute)
53
+ find_all.sum(attribute)
54
+ end
55
+
56
+ def find(id = nil, &block)
57
+ id ? first { |object| object.id == id } : first(&block)
58
+ end
59
+
60
+ def first(&block)
61
+ select(&block).first
62
+ end
63
+ alias :detect :first
64
+
65
+ def last(&block)
66
+ select(&block).last
67
+ end
68
+
69
+ def select(&block)
70
+ Relation.new(mapped_class, adapter, &block)
71
+ end
72
+ alias :find_all :select
73
+
74
+ def reject(&block)
75
+ select { |object| !block.call(object) }
76
+ end
77
+
78
+ def save(object)
79
+ return false unless object.valid?
80
+
81
+ if object.id
82
+ adapter.update(mapped_class, object)
83
+ else
84
+ object.id = adapter.insert(mapped_class, object)
85
+ end
86
+
87
+ object
88
+ end
89
+
90
+ def delete(object)
91
+ adapter.delete(mapped_class, object)
92
+ end
93
+
94
+ def delete_if(&block)
95
+ adapter.delete_all(mapped_class, &block)
96
+ end
97
+
98
+ def keep_if(&block)
99
+ delete_if { |object| !block.call(object) }
100
+ end
101
+
102
+ def clear
103
+ adapter.delete_all(mapped_class)
104
+ end
105
+ alias :delete_all :clear
106
+ end
107
+ end
@@ -0,0 +1,133 @@
1
+ require 'delegate'
2
+
3
+ module ActiveMapper
4
+ class Relation
5
+ extend Forwardable
6
+
7
+ def_delegators :to_a, :each, :map
8
+
9
+ def initialize(mapped_class, adapter, &block)
10
+ @mapped_class = mapped_class
11
+ @adapter = adapter
12
+ @block = block
13
+ end
14
+
15
+ def initialize_copy(other)
16
+ super
17
+ @to_a = nil
18
+ end
19
+
20
+ def any?
21
+ count > 0
22
+ end
23
+
24
+ def none?
25
+ !any?
26
+ end
27
+ alias :empty? :none?
28
+
29
+ def one?
30
+ count == 1
31
+ end
32
+
33
+ def count
34
+ @count ||= @adapter.count(@mapped_class, &@block)
35
+ end
36
+ alias :length :count
37
+ alias :size :count
38
+
39
+ def min(attribute)
40
+ @min ||= @adapter.minimum(@mapped_class, attribute, &@block)
41
+ end
42
+ alias :minimum :min
43
+
44
+ def max(attribute)
45
+ @max ||= @adapter.maximum(@mapped_class, attribute, &@block)
46
+ end
47
+ alias :maximum :max
48
+
49
+ def minmax(attribute)
50
+ [min(attribute), max(attribute)]
51
+ end
52
+
53
+ def avg(attribute)
54
+ @avg ||= @adapter.average(@mapped_class, attribute, &@block)
55
+ end
56
+ alias :average :avg
57
+
58
+ def sum(attribute)
59
+ @sum ||= @adapter.sum(@mapped_class, attribute, &@block)
60
+ end
61
+
62
+ def drop(number)
63
+ @offset = number
64
+ dup
65
+ end
66
+
67
+ def take(number)
68
+ @limit = number
69
+ dup
70
+ end
71
+
72
+ def select(&block)
73
+ if @block
74
+ query = @block.dup
75
+
76
+ @block = proc { |object| (query.call(object)) & block.call(object) }
77
+ else
78
+ @block = block
79
+ end
80
+
81
+ dup
82
+ end
83
+
84
+ def reject(&block)
85
+ select { |object| !block.call(object) }
86
+ end
87
+
88
+ def first(number = 1)
89
+ objects = drop(0).take(number).to_a
90
+
91
+ if number == 1
92
+ objects.first
93
+ else
94
+ objects
95
+ end
96
+ end
97
+
98
+ def last(number = 1)
99
+ objects = drop(0).take(number).reverse.to_a
100
+
101
+ if number == 1
102
+ objects.first
103
+ else
104
+ objects
105
+ end
106
+ end
107
+
108
+ def sort_by(&block)
109
+ @order = block
110
+ dup
111
+ end
112
+
113
+ def reverse
114
+ if @order
115
+ block = @order.dup
116
+
117
+ sort_by { |object| [block.call(object)].flatten.map(&:reverse) }
118
+ else
119
+ sort_by { |object| -object.id }
120
+ end
121
+ end
122
+
123
+ def to_a
124
+ @to_a ||= @adapter.where(@mapped_class, options, &@block).map { |record| @adapter.unserialize(@mapped_class, record) }
125
+ end
126
+
127
+ private
128
+
129
+ def options
130
+ { offset: @offset, limit: @limit, order: @order }
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveMapper
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_mapper/mapper'
2
+ require 'active_mapper/relation'
3
+ require 'active_mapper/adapter'
4
+ require 'active_mapper/version'
5
+
6
+ module ActiveMapper
7
+ def self.[](klass)
8
+ mappers[klass] ||= Class.new(ActiveMapper::Mapper).new(klass, adapter)
9
+ end
10
+
11
+ def self.adapter
12
+ @adapter ||= ActiveMapper::Adapter::Memory.new
13
+ end
14
+
15
+ def self.adapter=(adapter)
16
+ @adapter = adapter
17
+ end
18
+
19
+ private
20
+
21
+ def self.mappers
22
+ @mappers ||= {}
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveMapper::Adapter::ActiveRecord::Order do
4
+ let(:order) { described_class.new { |object| [object.name, -object.age] } }
5
+
6
+ describe '#to_sql' do
7
+ it 'converts attributes to hash' do
8
+ expect(order.to_sql).to eq({ name: :asc, age: :desc })
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveMapper::Adapter::ActiveRecord do
4
+ let(:user) { User.new(name: 'user', age: 28) }
5
+ let(:other_user) { User.new(name: 'other', age: 35) }
6
+ let(:adapter) { ActiveMapper::Adapter::ActiveRecord.new }
7
+
8
+ setup_active_record('active_record')
9
+
10
+ before do
11
+ user.id = adapter.insert(User, user)
12
+ other_user.id = adapter.insert(User, other_user)
13
+ end
14
+
15
+ after { adapter.delete_all(User) }
16
+
17
+ describe '#find' do
18
+ it 'finds the record with the matching id' do
19
+ record = adapter.find(User, user.id)
20
+
21
+ expect(record.id).to eq(user.id)
22
+ end
23
+ end
24
+
25
+ describe '#where' do
26
+ it 'finds the matching records' do
27
+ ids = adapter.where(User) { |u| u.age < 35 }.load.map(&:id)
28
+
29
+ expect(ids).to include(user.id)
30
+ expect(ids).to_not include(other_user.id)
31
+ end
32
+
33
+ it 'finds and sorts the matching records in ascending order' do
34
+ records = adapter.where(User, order: proc { |object| [object.name] })
35
+
36
+ expect(records.first.id).to eq(other_user.id)
37
+ expect(records.last.id).to eq(user.id)
38
+ end
39
+
40
+ it 'finds and sorts the matching records in descending order' do
41
+ records = adapter.where(User, order: proc { |object| [-object.age] })
42
+
43
+ expect(records.first.id).to eq(other_user.id)
44
+ expect(records.last.id).to eq(user.id)
45
+ end
46
+
47
+ it 'limits the number of matching records' do
48
+ records = adapter.where(User, limit: 1)
49
+
50
+ expect(records.count).to eq(1)
51
+ end
52
+
53
+ it 'offsets the matching records' do
54
+ ids = adapter.where(User, offset: 1).load.map(&:id)
55
+
56
+ expect(ids).to include(other_user.id)
57
+ expect(ids).to_not include(user.id)
58
+ end
59
+ end
60
+
61
+ describe '#count' do
62
+ it 'counts the number of total records' do
63
+ expect(adapter.count(User)).to eq(2)
64
+ end
65
+
66
+ it 'counts the number of matching records' do
67
+ expect(adapter.count(User) { |u| u.age < 35 }).to eq(1)
68
+ end
69
+ end
70
+
71
+ describe '#minimum' do
72
+ it 'calculates the minimum value' do
73
+ expect(adapter.minimum(User, :age)).to eq(28)
74
+ end
75
+
76
+ it 'calculates the minimum value of a subset of records' do
77
+ expect(adapter.minimum(User, :age) { |user| user.age > 30 }).to eq(35)
78
+ end
79
+ end
80
+
81
+ describe '#maximum' do
82
+ it 'calculates the maximum value' do
83
+ expect(adapter.maximum(User, :age)).to eq(35)
84
+ end
85
+
86
+ it 'calculates the maximum value of a subset of records' do
87
+ expect(adapter.maximum(User, :age) { |user| user.age < 30 }).to eq(28)
88
+ end
89
+ end
90
+
91
+ describe '#average' do
92
+ it 'calculates the average value' do
93
+ expect(adapter.average(User, :age)).to eq(31.5)
94
+ end
95
+
96
+ it 'calculates the avarage value of a subset of records' do
97
+ expect(adapter.average(User, :age) { |user| user.age < 35 }).to eq(28)
98
+ end
99
+ end
100
+
101
+ describe '#sum' do
102
+ it 'calculates the total value' do
103
+ expect(adapter.sum(User, :age)).to eq(63)
104
+ end
105
+
106
+ it 'calculates the total value of a subset of records' do
107
+ expect(adapter.sum(User, :age) { |user| user.age < 35 }).to eq(28)
108
+ end
109
+ end
110
+
111
+ describe '#insert' do
112
+ it 'inserts the record into the database' do
113
+ user = User.new(name: 'test', age: 18)
114
+ id = adapter.insert(User, user)
115
+ record = adapter.find(User, id)
116
+
117
+ expect(record.id).to eq(id)
118
+ end
119
+ end
120
+
121
+ describe '#update' do
122
+ it 'updates the record in the database' do
123
+ user.name = 'Changed'
124
+ user.age = 18
125
+ adapter.update(User, user)
126
+ record = adapter.find(User, user.id)
127
+
128
+ expect(record.name).to eq('Changed')
129
+ expect(record.age).to eq(18)
130
+ end
131
+ end
132
+
133
+ describe '#delete' do
134
+ it 'deletes the record from the database' do
135
+ adapter.delete(User, user)
136
+
137
+ expect(adapter.find(User, user.id)).to be_nil
138
+ end
139
+ end
140
+
141
+ describe '#delete_all' do
142
+ it 'deletes all the records' do
143
+ adapter.delete_all(User)
144
+
145
+ expect(adapter.count(User)).to eq(0)
146
+ end
147
+
148
+ it 'deletes the matching records' do
149
+ adapter.delete_all(User) { |u| u.name == 'user' }
150
+
151
+ expect(adapter.find(User, user.id)).to be_nil
152
+ expect(adapter.find(User, other_user.id)).to_not be_nil
153
+ end
154
+ end
155
+
156
+ describe '#unserialize' do
157
+ it 'unserializes the object' do
158
+ record = adapter.find(User, user.id)
159
+ unserialized = adapter.unserialize(User, record)
160
+
161
+ expect(unserialized).to eq(user)
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,158 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveMapper::Adapter::Memory do
4
+ let(:user) { User.new(name: 'user', age: 28) }
5
+ let(:other_user) { User.new(name: 'other', age: 35) }
6
+ let(:adapter) { ActiveMapper::Adapter::Memory.new }
7
+
8
+ before do
9
+ user.id = adapter.insert(User, user)
10
+ other_user.id = adapter.insert(User, other_user)
11
+ end
12
+
13
+ describe '#find' do
14
+ it 'finds the record with the matching id' do
15
+ expect(adapter.find(User, user.id)).to eq(user)
16
+ end
17
+ end
18
+
19
+ describe '#where' do
20
+ it 'finds the matching records' do
21
+ records = adapter.where(User) { |u| u.age < 35 }
22
+
23
+ expect(records).to include(user)
24
+ expect(records).to_not include(other_user)
25
+ end
26
+
27
+ it 'finds and sorts the matching records in ascending order' do
28
+ records = adapter.where(User, order: proc { |object| [object.name] })
29
+
30
+ expect(records.first).to eq(other_user)
31
+ expect(records.last).to eq(user)
32
+ end
33
+
34
+ it 'finds and sorts the matching records in descending order' do
35
+ records = adapter.where(User, order: proc { |object| [-object.age] })
36
+
37
+ expect(records.first).to eq(other_user)
38
+ expect(records.last).to eq(user)
39
+ end
40
+
41
+ it 'limits the number of matching records' do
42
+ records = adapter.where(User, limit: 1)
43
+
44
+ expect(records.count).to eq(1)
45
+ end
46
+
47
+ it 'offsets the matching records' do
48
+ records = adapter.where(User, offset: 1)
49
+
50
+ expect(records).to include(other_user)
51
+ expect(records).to_not include(user)
52
+ end
53
+ end
54
+
55
+ describe '#count' do
56
+ it 'counts the number of total records' do
57
+ expect(adapter.count(User)).to eq(2)
58
+ end
59
+
60
+ it 'counts the number of matching records' do
61
+ expect(adapter.count(User) { |u| u.age < 35 }).to eq(1)
62
+ end
63
+ end
64
+
65
+ describe '#minimum' do
66
+ it 'calculates the minimum value' do
67
+ expect(adapter.minimum(User, :age)).to eq(28)
68
+ end
69
+
70
+ it 'calculates the minimum value of a subset of records' do
71
+ expect(adapter.minimum(User, :age) { |user| user.age > 30 }).to eq(35)
72
+ end
73
+ end
74
+
75
+ describe '#maximum' do
76
+ it 'calculates the maximum value' do
77
+ expect(adapter.maximum(User, :age)).to eq(35)
78
+ end
79
+
80
+ it 'calculates the maximum value of a subset of records' do
81
+ expect(adapter.maximum(User, :age) { |user| user.age < 30 }).to eq(28)
82
+ end
83
+ end
84
+
85
+ describe '#average' do
86
+ it 'calculates the average value' do
87
+ expect(adapter.average(User, :age)).to eq(31.5)
88
+ end
89
+
90
+ it 'calculates the average value of a subset of records' do
91
+ expect(adapter.average(User, :age) { |user| user.age < 35 }).to eq(28)
92
+ end
93
+ end
94
+
95
+ describe '#sum' do
96
+ it 'calculates the total value' do
97
+ expect(adapter.sum(User, :age)).to eq(63)
98
+ end
99
+
100
+ it 'calculates the total value of a subset of records' do
101
+ expect(adapter.sum(User, :age) { |user| user.age < 35 }).to eq(28)
102
+ end
103
+ end
104
+
105
+ describe '#insert' do
106
+ it 'inserts the record into the collection' do
107
+ user = User.new(name: 'test', age: 18)
108
+ id = adapter.insert(User, user)
109
+ record = adapter.find(User, id)
110
+
111
+ expect(record.id).to eq(id)
112
+ end
113
+ end
114
+
115
+ describe '#update' do
116
+ it 'updates the record in the collection' do
117
+ user.name = 'Changed'
118
+ user.age = 18
119
+ adapter.update(User, user)
120
+ record = adapter.find(User, user.id)
121
+
122
+ expect(record.name).to eq('Changed')
123
+ expect(record.age).to eq(18)
124
+ end
125
+ end
126
+
127
+ describe '#delete' do
128
+ it 'deletes the record from the collection' do
129
+ adapter.delete(User, user)
130
+
131
+ expect(adapter.find(User, user.id)).to be_nil
132
+ end
133
+ end
134
+
135
+ describe '#delete_all' do
136
+ it 'deletes all the records' do
137
+ adapter.delete_all(User)
138
+
139
+ expect(adapter.count(User)).to eq(0)
140
+ end
141
+
142
+ it 'deletes the matching records' do
143
+ adapter.delete_all(User) { |u| u.name == 'user' }
144
+
145
+ expect(adapter.find(User, user.id)).to be_nil
146
+ expect(adapter.find(User, other_user.id)).to eq(other_user)
147
+ end
148
+ end
149
+
150
+ describe '#unserialize' do
151
+ it 'unserializes the object' do
152
+ record = adapter.find(User, user.id)
153
+ unserialized = adapter.unserialize(User, record)
154
+
155
+ expect(unserialized).to eq(user)
156
+ end
157
+ end
158
+ end