appfuel 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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