repositories 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.
- 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: []
|