repositories 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/repository/active_record.rb +238 -0
- data/lib/repository/base.rb +55 -0
- data/lib/repository/cursor.rb +168 -0
- data/lib/repository/filter.rb +13 -0
- data/lib/repository/filter_factory.rb +87 -0
- data/lib/repository/memory.rb +206 -0
- data/lib/repository/sort.rb +12 -0
- metadata +196 -0
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'repository/base'
|
3
|
+
|
4
|
+
module Repository
|
5
|
+
class ActiveRecord < Base
|
6
|
+
|
7
|
+
def initialize(model_klass, options={})
|
8
|
+
@model_klass = model_klass
|
9
|
+
@domain_model_klass = options[:domain_model_klass]
|
10
|
+
end
|
11
|
+
|
12
|
+
def create!(model_or_hash={})
|
13
|
+
attrs = model_or_hash_as_attrs(model_or_hash)
|
14
|
+
build_domain_model(model_klass.create!(attrs))
|
15
|
+
end
|
16
|
+
|
17
|
+
def update!(model_or_id, attrs={})
|
18
|
+
attributes = attrs || {}
|
19
|
+
case
|
20
|
+
when model_or_id.is_a?(model_klass)
|
21
|
+
if model_or_id.persisted?
|
22
|
+
model_or_id.update_attributes(attributes)
|
23
|
+
build_domain_model(model_or_id)
|
24
|
+
else
|
25
|
+
raise ArgumentError.new("Could not update record with id: #{model_or_id.send(primary_key)} because it does not exist")
|
26
|
+
end
|
27
|
+
when model_or_id.is_a?(domain_model_klass)
|
28
|
+
id = model_or_id.send(primary_key)
|
29
|
+
if model = model_klass.where(primary_key => id).first
|
30
|
+
update!(model, model_or_id.attributes.merge(attributes))
|
31
|
+
else
|
32
|
+
raise ArgumentError.new("Could not update record with id: #{id} because it does not exist")
|
33
|
+
end
|
34
|
+
else
|
35
|
+
if model = model_klass.where(primary_key => model_or_id).first
|
36
|
+
update!(model, attributes)
|
37
|
+
else
|
38
|
+
raise ArgumentError.new("Could not update record with id: #{model_or_id} because it does not exist")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_by_id(id)
|
44
|
+
self.find.eq(model_klass.primary_key, id).first
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute_find(query)
|
48
|
+
scope = apply_filters model_klass, query[:filters]
|
49
|
+
scope = apply_sorts scope, query[:sorts]
|
50
|
+
scope = apply_limit scope, query[:limit]
|
51
|
+
scope = apply_offset scope, query[:offset]
|
52
|
+
scope.all.map { |m| build_domain_model(m) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute_count(query)
|
56
|
+
apply_filters(model_klass, query[:filters]).count
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute_remove!(query)
|
60
|
+
apply_filters(model_klass, query[:filters]).delete_all
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_reader :domain_model_klass
|
66
|
+
|
67
|
+
def update_data(model_or_id)
|
68
|
+
case
|
69
|
+
when model_or_id.is_a?(model_klass)
|
70
|
+
[model_or_id.send(primary_key), model_or_id.attributes.slice(*model_or_id.changed)]
|
71
|
+
when model_or_id.is_a?(domain_model_klass)
|
72
|
+
[model_or_id.send(primary_key), model_or_id.attributes]
|
73
|
+
else
|
74
|
+
[model_or_id, {}]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_domain_model(model)
|
79
|
+
if domain_model_klass
|
80
|
+
domain_model_klass.new(model.attributes)
|
81
|
+
else
|
82
|
+
model
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def apply_filters(scope, filters)
|
87
|
+
apply_filter(scope, Filter.new(nil, 'and', filters))
|
88
|
+
end
|
89
|
+
|
90
|
+
class FakeScope
|
91
|
+
|
92
|
+
attr_reader :wheres, :variables
|
93
|
+
|
94
|
+
def initialize
|
95
|
+
@wheres = []
|
96
|
+
@variables = []
|
97
|
+
end
|
98
|
+
|
99
|
+
def where(sql, *varaiables)
|
100
|
+
@wheres << sql
|
101
|
+
@variables += varaiables
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def apply_filter(scope, filter)
|
108
|
+
column = quoted_column(filter.field)
|
109
|
+
case filter.operator
|
110
|
+
when '='
|
111
|
+
if filter.value
|
112
|
+
scope.where("#{column} = ?", filter.value)
|
113
|
+
else
|
114
|
+
scope.where("#{column} IS NULL")
|
115
|
+
end
|
116
|
+
when '!='
|
117
|
+
if filter.value
|
118
|
+
scope.where("#{column} <> ? OR #{column} IS NULL", filter.value)
|
119
|
+
else
|
120
|
+
scope.where("#{column} IS NOT NULL")
|
121
|
+
end
|
122
|
+
when '<'
|
123
|
+
scope.where("#{column} < ?", filter.value)
|
124
|
+
when '<='
|
125
|
+
scope.where("#{column} <= ?", filter.value)
|
126
|
+
when '>'
|
127
|
+
scope.where("#{column} > ?", filter.value)
|
128
|
+
when '>='
|
129
|
+
scope.where("#{column} >= ?", filter.value)
|
130
|
+
when 'in'
|
131
|
+
apply_contains_filter(scope, column, filter.value, '', lambda { |str|
|
132
|
+
"(#{str} AND #{column} IS NOT NULL)"
|
133
|
+
}, lambda { |str|
|
134
|
+
"(#{str} OR #{column} IS NULL)"
|
135
|
+
})
|
136
|
+
when '!in'
|
137
|
+
non_nil_values, nil_values = filter.value.uniq.partition { |val| !val.nil? }
|
138
|
+
case
|
139
|
+
when non_nil_values.empty? && nil_values.empty?
|
140
|
+
scope
|
141
|
+
when non_nil_values.empty? && !nil_values.empty?
|
142
|
+
scope.where("#{column} IS NOT NULL")
|
143
|
+
when !non_nil_values.empty? && nil_values.empty?
|
144
|
+
scope.where("(#{column} NOT IN (?) OR #{column} IS NULL)", non_nil_values)
|
145
|
+
when !non_nil_values.empty? && !nil_values.empty?
|
146
|
+
scope.where("(#{column} NOT IN (?) AND #{column} IS NOT NULL)", non_nil_values)
|
147
|
+
end
|
148
|
+
when 'like'
|
149
|
+
scope.where("#{column} LIKE #{glob(filter.value)}")
|
150
|
+
when 'or'
|
151
|
+
apply_and_join_filters(scope, filter.value, ' OR ')
|
152
|
+
when 'and'
|
153
|
+
apply_and_join_filters(scope, filter.value, ' AND ')
|
154
|
+
else
|
155
|
+
scope
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def apply_and_join_filters(scope, filters, join)
|
160
|
+
fake_scope = FakeScope.new
|
161
|
+
filters.each do |filter|
|
162
|
+
apply_filter(fake_scope, filter)
|
163
|
+
end
|
164
|
+
sql = fake_scope.wheres.join(join)
|
165
|
+
sql = "(#{sql})" if fake_scope.wheres.size > 1
|
166
|
+
scope.where(sql, *fake_scope.variables)
|
167
|
+
end
|
168
|
+
|
169
|
+
def apply_contains_filter(scope, column, values, _not, if_nil, if_not_nil)
|
170
|
+
non_nil_values, nil_values = values.uniq.partition { |val| !val.nil? }
|
171
|
+
str = "#{column} #{_not} IN (?)"
|
172
|
+
if nil_values.empty?
|
173
|
+
str = if_nil.call(str)
|
174
|
+
else
|
175
|
+
str = if_not_nil.call(str)
|
176
|
+
end
|
177
|
+
scope.where(str, non_nil_values)
|
178
|
+
end
|
179
|
+
|
180
|
+
def apply_sorts(scope, sorts)
|
181
|
+
if sorts.empty?
|
182
|
+
scope
|
183
|
+
else
|
184
|
+
ar_sorts = sorts.reduce([]) do |ar_sorts, sort|
|
185
|
+
ar_sorts << "#{quoted_column(sort.field)} #{sort.order}"
|
186
|
+
ar_sorts
|
187
|
+
end.join(', ')
|
188
|
+
scope.order ar_sorts
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def apply_limit(scope, limit)
|
193
|
+
if limit
|
194
|
+
scope.limit(limit)
|
195
|
+
else
|
196
|
+
scope
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def apply_offset(scope, offset)
|
201
|
+
if offset
|
202
|
+
scope.offset(offset)
|
203
|
+
else
|
204
|
+
scope
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def quoted_column(column)
|
209
|
+
::ActiveRecord::Base.connection.quote_column_name(column)
|
210
|
+
end
|
211
|
+
|
212
|
+
def quoted_value(value)
|
213
|
+
::ActiveRecord::Base.connection.quote(value)
|
214
|
+
end
|
215
|
+
|
216
|
+
def glob(k)
|
217
|
+
quoted_value("%#{k}%")
|
218
|
+
end
|
219
|
+
|
220
|
+
def store_record!(attrs)
|
221
|
+
record = attrs.dup
|
222
|
+
if id = record.delete(primary_key)
|
223
|
+
model_klass.update_all(record, {primary_key => id}, {limit: 1})
|
224
|
+
else
|
225
|
+
model_klass.create!(record)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def primary_key
|
230
|
+
model_klass.primary_key
|
231
|
+
end
|
232
|
+
|
233
|
+
def remove_model!(model)
|
234
|
+
model_klass.delete_all(id: model.id)
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'repository/cursor'
|
3
|
+
require 'repository/filter_factory'
|
4
|
+
|
5
|
+
module Repository
|
6
|
+
class Base
|
7
|
+
|
8
|
+
def find
|
9
|
+
cursor
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter
|
13
|
+
@filter_factory ||= FilterFactory.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove_by_id!(id)
|
17
|
+
if model = find_by_id(id)
|
18
|
+
remove_model!(model)
|
19
|
+
else
|
20
|
+
raise ArgumentError.new("Could not remove record with id: #{id} because it does not exist")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def remove!(model=nil)
|
25
|
+
if model
|
26
|
+
remove_by_id!(model.send(primary_key))
|
27
|
+
else
|
28
|
+
cursor.remove!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :model_klass
|
35
|
+
|
36
|
+
def cursor
|
37
|
+
Cursor.new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def model_or_hash_as_attrs(model_or_hash)
|
41
|
+
if model_or_hash
|
42
|
+
if model_or_hash.is_a?(Hash)
|
43
|
+
model_or_hash
|
44
|
+
elsif model_or_hash.is_a?(model_klass)
|
45
|
+
model_or_hash.attributes
|
46
|
+
else
|
47
|
+
raise ArgumentError.new("A hash or a #{model_klass} must be given to create a record")
|
48
|
+
end
|
49
|
+
else
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'repository/filter'
|
2
|
+
require 'repository/sort'
|
3
|
+
require 'repository/filter_factory'
|
4
|
+
|
5
|
+
module Repository
|
6
|
+
class Cursor
|
7
|
+
|
8
|
+
def initialize(query_executor)
|
9
|
+
@query_executor = query_executor
|
10
|
+
@filters = []
|
11
|
+
@sorts = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def eq(field, value)
|
15
|
+
@filters << filter_factory.eq(field, value)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def not_eq(field, value)
|
20
|
+
@filters << filter_factory.not_eq(field, value)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def lt(field, value)
|
25
|
+
@filters << filter_factory.lt(field, value)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def lte(field, value)
|
30
|
+
@filters << filter_factory.lte(field, value)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def gt(field, value)
|
35
|
+
@filters << filter_factory.gt(field, value)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def gte(field, value)
|
40
|
+
@filters << filter_factory.gte(field, value)
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def in(field, value)
|
45
|
+
@filters << filter_factory.in(field, value)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def not_in(field, value)
|
50
|
+
@filters << filter_factory.not_in(field, value)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def like(field, value)
|
55
|
+
@filters << filter_factory.like(field, value)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def or(*filters)
|
60
|
+
@filters << filter_factory.or(*filters)
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def sort(field, order)
|
65
|
+
assert_field!(field)
|
66
|
+
assert_order!(order)
|
67
|
+
@sorts << Sort.new(field, order.to_sym)
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def limit(limit)
|
72
|
+
if limit
|
73
|
+
assert_int!('Limit', limit)
|
74
|
+
@limit = limit.to_i
|
75
|
+
end
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def offset(offset)
|
80
|
+
if offset
|
81
|
+
assert_int!('Offset', offset)
|
82
|
+
@offset = offset.to_i
|
83
|
+
end
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def count
|
88
|
+
query_executor.execute_count(query)
|
89
|
+
end
|
90
|
+
|
91
|
+
def all
|
92
|
+
query_executor.execute_find(query)
|
93
|
+
end
|
94
|
+
|
95
|
+
def first
|
96
|
+
query_executor.execute_find(
|
97
|
+
query.merge(
|
98
|
+
sorts: sorts_for_first,
|
99
|
+
limit: 1
|
100
|
+
)).first
|
101
|
+
end
|
102
|
+
|
103
|
+
def last
|
104
|
+
query_executor.execute_find(
|
105
|
+
query.merge(
|
106
|
+
sorts: sorts_for_last,
|
107
|
+
limit: 1
|
108
|
+
)).first
|
109
|
+
end
|
110
|
+
|
111
|
+
def remove!
|
112
|
+
query_executor.execute_remove!(query)
|
113
|
+
end
|
114
|
+
|
115
|
+
def first
|
116
|
+
query_executor.execute_find(
|
117
|
+
query.merge(
|
118
|
+
limit: 1
|
119
|
+
)).first
|
120
|
+
end
|
121
|
+
|
122
|
+
def query
|
123
|
+
{
|
124
|
+
:type => @type,
|
125
|
+
:filters => @filters,
|
126
|
+
:sorts => @sorts,
|
127
|
+
:offset => @offset,
|
128
|
+
:limit => @limit
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
attr_reader :query_executor
|
135
|
+
|
136
|
+
def filter_factory
|
137
|
+
@filter_factory ||= FilterFactory.new
|
138
|
+
end
|
139
|
+
|
140
|
+
def sorts_for_first
|
141
|
+
@sorts
|
142
|
+
end
|
143
|
+
|
144
|
+
def sorts_for_last
|
145
|
+
sorts_for_first.map do |sort|
|
146
|
+
Sort.new(sort.field, sort.order == :asc ? :desc : :asc)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def assert_field!(field)
|
151
|
+
unless field.is_a?(String) || field.is_a?(Symbol)
|
152
|
+
raise ArgumentError.new "Field name must be a String or Symbol but you gave #{PP.pp(field, '')}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def assert_order!(order)
|
157
|
+
unless [:asc, :desc, 'asc', 'desc'].include?(order)
|
158
|
+
raise ArgumentError.new "Sort order must be 'asc' or 'desc' but you gave #{PP.pp(order, '')}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def assert_int!(name, num)
|
163
|
+
unless num.is_a?(Integer) || (num.is_a?(String) && num =~ /\d+/)
|
164
|
+
raise ArgumentError.new "#{name} must be an integer but you gave #{PP.pp(num, '')}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'repository/filter'
|
2
|
+
|
3
|
+
module Repository
|
4
|
+
class FilterFactory
|
5
|
+
|
6
|
+
def eq(field, value)
|
7
|
+
assert_field!(field)
|
8
|
+
Filter.new(field, '=', value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def not_eq(field, value)
|
12
|
+
assert_field!(field)
|
13
|
+
Filter.new(field, '!=', value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def lt(field, value)
|
17
|
+
assert_field!(field)
|
18
|
+
assert_not_nil!('Less than', value)
|
19
|
+
Filter.new(field, '<', value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def lte(field, value)
|
23
|
+
assert_field!(field)
|
24
|
+
assert_not_nil!('Less than or equal to', value)
|
25
|
+
Filter.new(field, '<=', value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def gt(field, value)
|
29
|
+
assert_field!(field)
|
30
|
+
assert_not_nil!('Greater than', value)
|
31
|
+
Filter.new(field, '>', value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def gte(field, value)
|
35
|
+
assert_field!(field)
|
36
|
+
assert_not_nil!('Greater than or equal to', value)
|
37
|
+
Filter.new(field, '>=', value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def in(field, value)
|
41
|
+
assert_field!(field)
|
42
|
+
assert_to_a!('Inclusion', value)
|
43
|
+
Filter.new(field, 'in', value.to_a)
|
44
|
+
end
|
45
|
+
|
46
|
+
def not_in(field, value)
|
47
|
+
assert_field!(field)
|
48
|
+
assert_to_a!('Exclusion', value)
|
49
|
+
Filter.new(field, '!in', value.to_a)
|
50
|
+
end
|
51
|
+
|
52
|
+
def like(field, value)
|
53
|
+
assert_field!(field)
|
54
|
+
assert_str_or_sym!(value, 'Value')
|
55
|
+
Filter.new(field, 'like', value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def or(*filters)
|
59
|
+
Filter.new(nil, 'or', filters)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def assert_to_a!(name, value)
|
65
|
+
unless value.respond_to?(:to_a)
|
66
|
+
raise ArgumentError.new "#{name} filter value must respond to to_a but #{PP.pp(value, '')} does not"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def assert_not_nil!(name, value)
|
71
|
+
if value.nil?
|
72
|
+
raise ArgumentError.new "#{name} filter value cannot be nil"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def assert_field!(field)
|
77
|
+
assert_str_or_sym!(field, 'Field name')
|
78
|
+
end
|
79
|
+
|
80
|
+
def assert_str_or_sym!(value, name)
|
81
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
82
|
+
raise ArgumentError.new "#{name} must be a String or Symbol but you gave #{PP.pp(value, '')}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'repository/base'
|
3
|
+
require 'repository/cursor'
|
4
|
+
|
5
|
+
# for sorting on booleans
|
6
|
+
class FalseClass
|
7
|
+
def <(other)
|
8
|
+
other
|
9
|
+
end
|
10
|
+
|
11
|
+
def >(other)
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class TrueClass
|
17
|
+
def <(other)
|
18
|
+
other
|
19
|
+
end
|
20
|
+
|
21
|
+
def >(other)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Repository
|
27
|
+
class Memory < Base
|
28
|
+
|
29
|
+
def initialize(model_klass, options={})
|
30
|
+
@model_klass = model_klass
|
31
|
+
@id = 0
|
32
|
+
@store = {}
|
33
|
+
@primary_key = options[:primary_key]
|
34
|
+
end
|
35
|
+
|
36
|
+
def create!(attrs={})
|
37
|
+
_id = id
|
38
|
+
attrs = model_or_hash_as_attrs(attrs)
|
39
|
+
verify_attributes!(attrs)
|
40
|
+
attributes = attrs.merge(primary_key => _id)
|
41
|
+
store!(_id, attributes)
|
42
|
+
model_klass.new(attributes)
|
43
|
+
end
|
44
|
+
|
45
|
+
def update!(model_or_id, attributes={})
|
46
|
+
attributes ||= {}
|
47
|
+
model = case
|
48
|
+
when model_or_id.is_a?(model_klass)
|
49
|
+
if model = find_by_id(model_or_id.send(primary_key))
|
50
|
+
model_or_id
|
51
|
+
else
|
52
|
+
raise ArgumentError.new("Could not update record with id: #{model_or_id.send(primary_key)} because it does not exist") unless model
|
53
|
+
end
|
54
|
+
|
55
|
+
else
|
56
|
+
if model = find_by_id(model_or_id)
|
57
|
+
model
|
58
|
+
else
|
59
|
+
raise ArgumentError.new("Could not update record with id: #{model_or_id} because it does not exist") unless model
|
60
|
+
end
|
61
|
+
end
|
62
|
+
updated_attrs = model.attributes.merge(attributes_without_pkey(attributes))
|
63
|
+
store!(model.send(primary_key), updated_attrs)
|
64
|
+
model_klass.new(updated_attrs)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_by_id(id)
|
68
|
+
find.eq(primary_key, id).first
|
69
|
+
end
|
70
|
+
|
71
|
+
###########################
|
72
|
+
# These methods are called by the cursor. Do not call them directly. Or call them. Whatever. Either one.
|
73
|
+
|
74
|
+
def raw_find(query)
|
75
|
+
models = all_models
|
76
|
+
models = apply_transforms models, query[:transforms]
|
77
|
+
models = filter_models models, query[:filters]
|
78
|
+
models = sort_models models, query[:sorts]
|
79
|
+
models = offset_models models, query[:offset]
|
80
|
+
models = limit_models models, query[:limit]
|
81
|
+
end
|
82
|
+
|
83
|
+
def execute_find(query)
|
84
|
+
models = raw_find(query)
|
85
|
+
models.map { |h| model_klass.new(h) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def execute_count(query)
|
89
|
+
execute_find(query).size
|
90
|
+
end
|
91
|
+
|
92
|
+
def execute_remove!(query)
|
93
|
+
execute_find(query).each do |model|
|
94
|
+
remove_model!(model)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
###########################
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
attr_reader :primary_key
|
103
|
+
|
104
|
+
def verify_attributes!(attrs)
|
105
|
+
null_model = model_klass.new
|
106
|
+
allowed_attributes = null_model.attributes.keys.map(&:to_sym)
|
107
|
+
attrs.each do |key, value|
|
108
|
+
unless allowed_attributes.include?(key.to_sym)
|
109
|
+
raise ArgumentError.new("Unknown attribute: #{key}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def attributes_without_pkey(attributes)
|
115
|
+
attributes.reject { |k, v| k == primary_key }
|
116
|
+
end
|
117
|
+
|
118
|
+
def apply_transforms(models, transforms)
|
119
|
+
models.map do |model|
|
120
|
+
(transforms || []).reduce(model) do |model, transform|
|
121
|
+
transform.call(model)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def filter_models(models, filters)
|
127
|
+
models.select do |model|
|
128
|
+
(filters || []).all? do |filter|
|
129
|
+
filter_matches?(filter, model)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def filter_matches?(filter, model)
|
135
|
+
value = model[filter.field]
|
136
|
+
case filter.operator
|
137
|
+
when '='; value == filter.value
|
138
|
+
when '!='; value != filter.value
|
139
|
+
when '<'; value && value < filter.value
|
140
|
+
when '<='; value && value <= filter.value
|
141
|
+
when '>'; value && value > filter.value
|
142
|
+
when '>='; value && value >= filter.value
|
143
|
+
when 'in'; filter.value.include?(value)
|
144
|
+
when '!in'; !filter.value.include?(value)
|
145
|
+
when 'or'; filter.value.any? do |sub_filter|
|
146
|
+
filter_matches?(sub_filter, model)
|
147
|
+
end
|
148
|
+
when 'like'; value =~ /#{filter.value}/i
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def sort_models(models, sorts)
|
153
|
+
models.sort { |model1, model2| compare_models(model1, model2, sorts) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def compare_models(model1, model2, sorts)
|
157
|
+
sorts.each do |sort|
|
158
|
+
result = compare_model(model1, model2, sort)
|
159
|
+
return result if result
|
160
|
+
end
|
161
|
+
0
|
162
|
+
end
|
163
|
+
|
164
|
+
def compare_model(model1, model2, sort)
|
165
|
+
field1, field2 = model1[sort.field], model2[sort.field]
|
166
|
+
field1 == field2 ? nil :
|
167
|
+
field1 < field2 && sort.order == :asc ? -1 :
|
168
|
+
field1 > field2 && sort.order == :desc ? -1 : 1
|
169
|
+
end
|
170
|
+
|
171
|
+
def limit_models(models, limit)
|
172
|
+
if limit
|
173
|
+
models.take(limit)
|
174
|
+
else
|
175
|
+
models
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def offset_models(models, offset)
|
180
|
+
if offset
|
181
|
+
models.drop(offset)
|
182
|
+
else
|
183
|
+
models
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def all_models
|
188
|
+
@store.values.map do |raw_record|
|
189
|
+
model_klass.new(MultiJson.load(raw_record)).attributes
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def store!(id, attributes)
|
194
|
+
@store[id] = MultiJson.dump(attributes)
|
195
|
+
end
|
196
|
+
|
197
|
+
def remove_model!(model)
|
198
|
+
@store.delete(model.send(primary_key))
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def id
|
203
|
+
@id += 1
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
metadata
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: repositories
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Myles Megyesi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.11
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.11
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bson_ext
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.8.6
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.8.6
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: database_cleaner
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.9.1
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.1
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mongo
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.1
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.8.1
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: multi_json
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.5.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.5.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rake
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 10.0.3
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 10.0.3
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.12.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 2.12.0
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: sqlite3
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 1.3.6
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 1.3.6
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: virtus
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.5.3
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.5.3
|
158
|
+
description: Repository?
|
159
|
+
email:
|
160
|
+
- myles.megyesi@gmail.com
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- lib/repository/memory.rb
|
166
|
+
- lib/repository/base.rb
|
167
|
+
- lib/repository/filter_factory.rb
|
168
|
+
- lib/repository/sort.rb
|
169
|
+
- lib/repository/filter.rb
|
170
|
+
- lib/repository/active_record.rb
|
171
|
+
- lib/repository/cursor.rb
|
172
|
+
homepage:
|
173
|
+
licenses: []
|
174
|
+
post_install_message:
|
175
|
+
rdoc_options: []
|
176
|
+
require_paths:
|
177
|
+
- lib
|
178
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
179
|
+
none: false
|
180
|
+
requirements:
|
181
|
+
- - ! '>='
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
requirements: []
|
191
|
+
rubyforge_project:
|
192
|
+
rubygems_version: 1.8.24
|
193
|
+
signing_key:
|
194
|
+
specification_version: 3
|
195
|
+
summary: Store some data?
|
196
|
+
test_files: []
|