appfuel 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +78 -1
  4. data/appfuel.gemspec +1 -0
  5. data/docs/images/appfuel_basic_flow.png +0 -0
  6. data/lib/appfuel/application/app_container.rb +24 -1
  7. data/lib/appfuel/application/container_class_registration.rb +29 -4
  8. data/lib/appfuel/application/root.rb +1 -0
  9. data/lib/appfuel/application.rb +0 -1
  10. data/lib/appfuel/domain/base_criteria.rb +171 -0
  11. data/lib/appfuel/domain/criteria_builder.rb +248 -0
  12. data/lib/appfuel/domain/criteria_settings.rb +156 -0
  13. data/lib/appfuel/domain/dsl.rb +5 -1
  14. data/lib/appfuel/domain/entity.rb +1 -2
  15. data/lib/appfuel/domain/exists_criteria.rb +57 -0
  16. data/lib/appfuel/domain/expr.rb +66 -97
  17. data/lib/appfuel/domain/expr_conjunction.rb +27 -0
  18. data/lib/appfuel/domain/expr_parser.rb +199 -0
  19. data/lib/appfuel/domain/expr_transform.rb +68 -0
  20. data/lib/appfuel/domain/search_criteria.rb +137 -0
  21. data/lib/appfuel/domain.rb +6 -1
  22. data/lib/appfuel/feature/initializer.rb +5 -0
  23. data/lib/appfuel/handler/action.rb +3 -0
  24. data/lib/appfuel/handler/base.rb +11 -1
  25. data/lib/appfuel/handler/command.rb +4 -0
  26. data/lib/appfuel/repository/base.rb +16 -2
  27. data/lib/appfuel/repository/mapper.rb +41 -7
  28. data/lib/appfuel/repository/mapping_dsl.rb +4 -4
  29. data/lib/appfuel/repository/mapping_entry.rb +2 -2
  30. data/lib/appfuel/repository.rb +0 -1
  31. data/lib/appfuel/storage/db/active_record_model.rb +32 -28
  32. data/lib/appfuel/storage/db/mapper.rb +38 -125
  33. data/lib/appfuel/storage/db/repository.rb +6 -10
  34. data/lib/appfuel/storage/memory/repository.rb +4 -0
  35. data/lib/appfuel/types.rb +0 -1
  36. data/lib/appfuel/version.rb +1 -1
  37. data/lib/appfuel.rb +6 -10
  38. metadata +26 -7
  39. data/lib/appfuel/application/container_key.rb +0 -201
  40. data/lib/appfuel/application/qualify_container_key.rb +0 -76
  41. data/lib/appfuel/db_model.rb +0 -16
  42. data/lib/appfuel/domain/criteria.rb +0 -436
  43. data/lib/appfuel/repository/mapping_registry.rb +0 -121
@@ -1,436 +0,0 @@
1
- module Appfuel
2
- module Domain
3
-
4
- # The Criteria represents the interface between the repositories and actions
5
- # or commands. The allow you to find entities in the application storage (
6
- # a database) without knowledge of that storage system. The criteria will
7
- # always refer to its queries in the domain language for which the repo is
8
- # responsible for mapping that query to its persistence layer.
9
- class Criteria
10
- include DomainNameParser
11
-
12
- DEFAULT_PAGE = 1
13
- DEFAULT_PER_PAGE = 20
14
-
15
- attr_reader :domain, :domain_name, :feature, :repo_name, :exprs, :order,
16
- :exists, :exec, :all
17
-
18
- # Parse out the domain into feature, domain, determine the name of the
19
- # repo this criteria is for and initailize basic settings.
20
- #
21
- # @example
22
- # SpCore::Domain::Criteria('foo', single: true)
23
- # Types.Criteria('foo.bar', single: true)
24
- #
25
- # === Options
26
- # error_on_empty: will cause the repo to fail when query returns an
27
- # an empty dataset. The failure will have the message
28
- # with key as domain and text is "<domain> not found"
29
- #
30
- # single: will cause the repo to return only one, the first,
31
- # entity in the dataset
32
- #
33
- # @param domain [String] fully qualified domain name
34
- # @param opts [Hash] options for initializing criteria
35
- # @return [Criteria]
36
- def initialize(domain, opts = {})
37
- @feature, @domain, @domain_name = parse_domain_name(domain)
38
- @exists = nil
39
- @exprs = []
40
- @order = []
41
- @limit = nil
42
- @exec = nil
43
- @all = false
44
- @first = false
45
- @last = false
46
- @params = {}
47
- @page = DEFAULT_PAGE
48
- @per_page = DEFAULT_PER_PAGE
49
- @disable_pagination = opts[:disable_pagination] == true
50
- @repo_name = "#{(opts[:repo] || @domain).classify}Repository"
51
-
52
- empty_dataset_is_valid!
53
- if opts[:error_on_empty] == true
54
- error_on_empty_dataset!
55
- end
56
-
57
- # default is to expect a collection for this critria unless you declare
58
- # you want a single
59
- collection
60
- public_send(:first) if opts[:single] == true || opts[:first] == true
61
- public_send(:last) if opts[:last] == true
62
- end
63
-
64
- # Add param to the instantiated criteria
65
- #
66
- # @example
67
- # criteria.add_param('foo', 100)
68
- #
69
- # @param key [Symbol, String] The key name where we want to keep the value
70
- # @param value [String, Integer] The value that belongs to the key param
71
- # @return [String, Integer] The saved value
72
- def add_param(key, value)
73
- fail 'key should not be nil' if key.nil?
74
-
75
- @params[key.to_sym] = value
76
- end
77
-
78
- # @param key [String, Symbol]
79
- # @return [String, Integer, Boolean] the found value
80
- def param(key)
81
- @params[key.to_sym]
82
- end
83
-
84
- # @param key [String, Symbol]
85
- # @return [Boolean]
86
- def param?(key)
87
- @params.key?(key.to_sym)
88
- end
89
-
90
- # @return [Boolean] if the @params variable has values
91
- def params?
92
- !@params.empty?
93
- end
94
-
95
- # Remove operators and keep key filters. It returns a cleaned
96
- # list of filters.
97
- #
98
- # @example
99
- # criteria.filter([
100
- # {projects.offer.id: 6},
101
- # {last_name: 'SirFooish', op: 'like'},
102
- # {first_name: Bob, op: 'like', or: false}
103
- # ])
104
- #
105
- # @raise [RuntimeError] when the attribute is not an array
106
- # @raise [RuntimeError] when the filter is not a Hash
107
- #
108
- # @param list [Array<Hash>] The list of filters to implement in criteria
109
- # @return [Array<Hash>, nil] List of filters with values or nil in case of array empty
110
- def filter(list)
111
- fail 'the attribute must be an Array' unless list.is_a? Array
112
-
113
- list.each do |item|
114
- fail 'filters must be a Hash' unless item.is_a? Hash
115
-
116
- operator = extract_relational_operator(item)
117
- relational_or = extract_relational_condition(item)
118
- key, value = item.first
119
-
120
- where(key, operator => value, or: relational_or)
121
- end
122
- end
123
-
124
- def where?
125
- !exprs.empty? || all?
126
- end
127
-
128
- # Adds an expression to the list that will be joined to
129
- # the next expression with an `:and` operator
130
- #
131
- # @param domain_attr [String]
132
- # @param data [Hash]
133
- # @option <operator> the key is the operator like :eq and value is value
134
- # @option :or with a value of true will join this expression with the
135
- # previous expression using a relation or. relation and
136
- # is by default
137
- #
138
- # @return [Criteria]
139
- def where(domain_attr, data)
140
- domain_attr = domain_attr.to_s
141
-
142
- relational_op = :and
143
- if data.key?(:or)
144
- value = data.delete(:or)
145
- relational_op = :or if value == true
146
- end
147
-
148
- domain_entity = domain_name
149
- if domain_attr.count('.') == 2
150
- domain_entity, domain_attr = parse_domain_attr(domain_attr)
151
- end
152
-
153
- expr = {
154
- expr: create_expr(domain_entity, domain_attr, data),
155
- relational_op: relational_op
156
- }
157
- exprs << expr
158
- self
159
- end
160
-
161
- alias_method :and, :where
162
-
163
- # Adds an expression to the list that will be joined to
164
- # the next expression with an `:or` operator
165
- #
166
- # @param attr [String]
167
- # @param value [Hash]
168
- # @return [Criteria]
169
- def or(domain_attr, data)
170
- data[:or] = true
171
- where(domain_attr, data)
172
- end
173
-
174
- # Adds an expression to order list.
175
- #
176
- # @param list [list]
177
- # @return [Criteria]
178
- def order_by(dict, order_dir = 'ASC')
179
- if dict.is_a?(String) || dict.is_a?(Symbol)
180
- dict = {dict.to_s => order_dir}
181
- end
182
-
183
- dict.each do |domain_attr, dir|
184
- domain_entity = domain_name
185
-
186
- if domain_attr.count('.') == 2
187
- domain_entity, domain_attr = parse_domain_attr(domain_attr)
188
- end
189
-
190
- @order << create_expr(domain_entity, domain_attr, eq: dir.to_s.upcase)
191
- end
192
-
193
- self
194
- end
195
-
196
- def order?
197
- !@order.empty?
198
- end
199
-
200
- def limit?
201
- !@limit.nil?
202
- end
203
-
204
- # @param limit [Integer]
205
- # @return [Criteria]
206
- def limit(value = nil)
207
- return @limit if value.nil?
208
-
209
- value = Integer(value)
210
- fail "limit must be an integer gt 0" unless value > 0
211
- @limit = value
212
- self
213
- end
214
-
215
- # @param page [Integer]
216
- # @return [Criteria]
217
- def page(value = nil)
218
- return @page if value.nil?
219
-
220
- @page = Integer(value)
221
- self
222
- end
223
-
224
- # @param per_page [Integer]
225
- # @return [Criteria]
226
- def per_page(value=nil)
227
- return @per_page if value.nil?
228
-
229
- @per_page = Integer(value)
230
- self
231
- end
232
-
233
- # The repo uses this to determine what kind of dataset to return
234
- #
235
- # @return [Boolean]
236
- def single?
237
- first? || last?
238
- end
239
-
240
- # Tell the repo to only return a single entity for this criteria
241
- #
242
- # @return [Criteria]
243
- def single
244
- first
245
- self
246
- end
247
-
248
- # Set false @first and @last instance variables
249
- def clear_single
250
- clear_first
251
- clear_last
252
- end
253
-
254
- def first
255
- @first = true
256
- clear_last
257
- self
258
- end
259
-
260
- def first?
261
- @first
262
- end
263
-
264
- def clear_first
265
- @first = false
266
- end
267
-
268
- def last?
269
- @last
270
- end
271
-
272
- def last
273
- clear_first
274
- @last = true
275
- self
276
- end
277
-
278
- def clear_last
279
- @last = false
280
- end
281
-
282
- def disable_pagination?
283
- @disable_pagination
284
- end
285
-
286
- def disable_pagination
287
- @disable_pagination = true
288
- end
289
-
290
- # Tell the repo to return a collection for this criteria. This is the
291
- # default setting
292
- #
293
- # @return Criteria
294
- def collection
295
- clear_single
296
- self
297
- end
298
-
299
- def all?
300
- @all
301
- end
302
-
303
- # Tell the repo to return all records for this criteria. It is import
304
- # to understand that for database queries you are calling all on the
305
- # map for the specified domain.
306
- #
307
- # @example
308
- # Criteria.new('projects.offer').all
309
- #
310
- # UserRepository has a mapper with a map for 'offer' in this case
311
- # all will be called on the db class for this map.
312
- #
313
- # @return [Criteria]
314
- def all
315
- @all = true
316
- collection
317
- self
318
- end
319
-
320
- # Used to determin if this criteria belongs to a feature
321
- #
322
- # @return [Boolean]
323
- def feature?
324
- !@feature.nil?
325
- end
326
-
327
- # Used to determin if this criteria belongs to a global domain
328
- #
329
- # @return [Boolean]
330
- def global_domain?
331
- !feature?
332
- end
333
-
334
- # Determines if a domain exists in this repo
335
- #
336
- # @param attr [String]
337
- # @param value [Mixed]
338
- # @return [Criteria]
339
- def exists(domain_attr, value)
340
- domain_attr = domain_attr.to_s
341
- domain_entity = domain_name
342
- if domain_attr.count('.') == 3
343
- domain_entity, domain_attr = parse_domain_attr(domain_attr)
344
- end
345
- @exists = create_expr(domain_entity, domain_attr, eq: value)
346
- self
347
- end
348
-
349
- # @return [DbEntityExpr]
350
- def exists_expr
351
- @exists
352
- end
353
-
354
- # exec is used to indicate we want a custom method on the repo
355
- # to used with this criteria
356
- #
357
- # @param name [String]
358
- # @return [String, Criteria] when used as a dsl it returns the criteria
359
- def exec(name = nil)
360
- return @exec if name.nil?
361
-
362
- @exec = name.to_sym
363
- self
364
- end
365
-
366
- def exec?
367
- !@exec.nil?
368
- end
369
-
370
- # @yield expression and operator
371
- # @return [Enumerator] when no block is given
372
- def each
373
- return exprs.each unless block_given?
374
-
375
- exprs.each do |expr|
376
- yield expr[:expr], expr[:relational_op]
377
- end
378
- end
379
-
380
- def error_on_empty_dataset?
381
- @error_on_empty
382
- end
383
-
384
- # Tells the repo to return an error when entity is not found
385
- #
386
- # @return Criteria
387
- def error_on_empty_dataset!
388
- @error_on_empty = true
389
- self
390
- end
391
-
392
- # Tells the repo to return and empty collection, or nil if single is
393
- # invoked, if the entity is not found
394
- #
395
- # @return Criteria
396
- def empty_dataset_is_valid!
397
- @error_on_empty = false
398
- self
399
- end
400
-
401
- private
402
- def parse_domain_name(name)
403
- if !name.is_a?(String) && !name.respond_to?(:domain_name)
404
- fail "domain name must be a string or implement method :domain_name"
405
- end
406
-
407
- name = name.domain_name if name.respond_to?(:domain_name)
408
- feature, domain = name.split('.', 2)
409
- if domain.nil?
410
- domain = feature
411
- feature = nil
412
- end
413
- [feature, domain, name]
414
- end
415
-
416
- def create_expr(domain_name, domain_attr, value)
417
- Expr.new(domain_name, domain_attr, value)
418
- end
419
-
420
- def extract_relational_condition(filter_item)
421
- relational_or = (filter_item.delete(:or) == true) if filter_item.key?(:or)
422
- relational_or
423
- end
424
-
425
- def extract_relational_operator(filter_item)
426
- operator = "eq"
427
- operator = filter_item.delete(:op) if filter_item.key?(:op)
428
- if filter_item[:insensitive]
429
- operator = "ilike"
430
- filter_item.delete(:insensitive)
431
- end
432
- operator
433
- end
434
- end
435
- end
436
- end
@@ -1,121 +0,0 @@
1
- module Appfuel
2
- module Repository
3
- # The mapping registry holds all entity to db mappings. Mappings are
4
- # contained within a DbEntityMapEntry object and are arranged by
5
- # entity name. Each entity will hold a hash where the keys are the
6
- # attribute names and the value is the entry
7
- class MappingRegistry
8
- attr_reader :map, :container_root_name
9
-
10
- def initialize(app_name, map)
11
- @container_root_name = app_name
12
- @map = map
13
- end
14
-
15
- # Determine if an entity has been added
16
- #
17
- # @param entity [String]
18
- # @return [Boolean]
19
- def domain?(domain_name)
20
- map.key?(domain_name)
21
- end
22
-
23
- # Determine if an attribute is mapped for a given entity
24
- #
25
- # @param entity [String] name of the entity
26
- # @param attr [String] name of the attribute
27
- # @return [Boolean]
28
- def domain_attr?(domain_name, domain_attr)
29
- return false unless entity?(domain_name)
30
- map[domain_name].key?(domain_attr)
31
- end
32
-
33
- # Returns a mapping entry for a given entity
34
- #
35
- # @raise [RuntimeError] when entity not found
36
- # @raise [RuntimeError] when attr not found
37
- #
38
- # @param entity [String] name of the entity
39
- # @param attr [String] name of the attribute
40
- # @return [Boolean]
41
- def find(domain_name, domain_attr)
42
- validate_entity(domain_name)
43
-
44
- unless map[domain_name].key?(domain_attr)
45
- fail "Entity (#{domain_name}) attr (#{domain_attr}) not registered"
46
- end
47
- map[domain_name][domain_attr]
48
- end
49
-
50
- # Iterates over all entries for a given entity
51
- #
52
- # @yield [attr, entry] expose the entity attr name and entry
53
- #
54
- # @param entity [String] name of the entity
55
- # @return [void]
56
- def each_domain_attr(domain_name)
57
- validate_domain(domain_name)
58
-
59
- map[domain_name].each do |attr, entry|
60
- yield attr, entry
61
- end
62
- end
63
-
64
- # Determine if an column is mapped for a given entity
65
- #
66
- # @param entity [String] name of the entity
67
- # @param attr [String] name of the attribute
68
- # @return [Boolean]
69
- def persistence_attr_mapped?(domain_name, persistence_attr)
70
- result = false
71
- each_domain_attr(entity) do |_attr, entry|
72
- result = true if persistence_attr == entry.persistence_attr
73
- end
74
- result
75
- end
76
-
77
- # Returns a column name for an entity's attribute
78
- #
79
- # @raise [RuntimeError] when entity not found
80
- # @raise [RuntimeError] when attr not found
81
- #
82
- # @param entity [String] name of the entity
83
- # @param attr [String] name of the attribute
84
- # @return [String]
85
- def persistence_attr(domain_name, attr)
86
- find(entity, attr).persistence_attr
87
- end
88
-
89
- # Returns the db model for a given entity attr
90
- # container:
91
- # domains:
92
- # domain_name -> domain
93
- # persistence
94
- # db:
95
- # persistence_name: -> class
96
- #
97
- # container[persistence.db.name]
98
- #
99
- # @raise [RuntimeError] when entity not found
100
- # @raise [RuntimeError] when attr not found
101
- # @raise [Dry::Contriner::Error] when db_class is not registered
102
- #
103
- # @param entity [String] name of the entity
104
- # @param attr [String] name of the attribute
105
- # @return [Object]
106
- def persistence_class(type, domain_name, attr)
107
- entry = find(domain_name, attr)
108
- name = entry.persistence[type]
109
- key = "persistence.#{type}.#{name}"
110
- Appfuel.app_container(root_name)[key]
111
- end
112
-
113
- private
114
- def validate_entity(entity)
115
- unless entity?(entity)
116
- fail "Entity (#{entity}) is not registered"
117
- end
118
- end
119
- end
120
- end
121
- end