active_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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