appfuel 0.2.0

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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +19 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +9 -0
  9. data/README.md +38 -0
  10. data/Rakefile +6 -0
  11. data/appfuel.gemspec +42 -0
  12. data/bin/console +7 -0
  13. data/bin/setup +8 -0
  14. data/lib/appfuel.rb +210 -0
  15. data/lib/appfuel/application.rb +4 -0
  16. data/lib/appfuel/application/app_container.rb +223 -0
  17. data/lib/appfuel/application/container_class_registration.rb +22 -0
  18. data/lib/appfuel/application/container_key.rb +201 -0
  19. data/lib/appfuel/application/qualify_container_key.rb +76 -0
  20. data/lib/appfuel/application/root.rb +140 -0
  21. data/lib/appfuel/cli_msg_request.rb +19 -0
  22. data/lib/appfuel/configuration.rb +14 -0
  23. data/lib/appfuel/configuration/definition_dsl.rb +175 -0
  24. data/lib/appfuel/configuration/file_loader.rb +61 -0
  25. data/lib/appfuel/configuration/populate.rb +95 -0
  26. data/lib/appfuel/configuration/search.rb +45 -0
  27. data/lib/appfuel/db_model.rb +16 -0
  28. data/lib/appfuel/domain.rb +7 -0
  29. data/lib/appfuel/domain/criteria.rb +436 -0
  30. data/lib/appfuel/domain/domain_name_parser.rb +44 -0
  31. data/lib/appfuel/domain/dsl.rb +247 -0
  32. data/lib/appfuel/domain/entity.rb +242 -0
  33. data/lib/appfuel/domain/entity_collection.rb +87 -0
  34. data/lib/appfuel/domain/expr.rb +127 -0
  35. data/lib/appfuel/domain/value_object.rb +7 -0
  36. data/lib/appfuel/errors.rb +104 -0
  37. data/lib/appfuel/feature.rb +2 -0
  38. data/lib/appfuel/feature/action_loader.rb +25 -0
  39. data/lib/appfuel/feature/initializer.rb +43 -0
  40. data/lib/appfuel/handler.rb +6 -0
  41. data/lib/appfuel/handler/action.rb +17 -0
  42. data/lib/appfuel/handler/base.rb +103 -0
  43. data/lib/appfuel/handler/command.rb +18 -0
  44. data/lib/appfuel/handler/inject_dsl.rb +88 -0
  45. data/lib/appfuel/handler/validator_dsl.rb +256 -0
  46. data/lib/appfuel/initialize.rb +70 -0
  47. data/lib/appfuel/initialize/initializer.rb +68 -0
  48. data/lib/appfuel/msg_request.rb +207 -0
  49. data/lib/appfuel/predicates.rb +10 -0
  50. data/lib/appfuel/presenter.rb +18 -0
  51. data/lib/appfuel/presenter/base.rb +7 -0
  52. data/lib/appfuel/repository.rb +73 -0
  53. data/lib/appfuel/repository/base.rb +72 -0
  54. data/lib/appfuel/repository/initializer.rb +19 -0
  55. data/lib/appfuel/repository/mapper.rb +203 -0
  56. data/lib/appfuel/repository/mapping_dsl.rb +210 -0
  57. data/lib/appfuel/repository/mapping_entry.rb +76 -0
  58. data/lib/appfuel/repository/mapping_registry.rb +121 -0
  59. data/lib/appfuel/repository_runner.rb +60 -0
  60. data/lib/appfuel/request.rb +53 -0
  61. data/lib/appfuel/response.rb +96 -0
  62. data/lib/appfuel/response_handler.rb +79 -0
  63. data/lib/appfuel/root_module.rb +31 -0
  64. data/lib/appfuel/run_error.rb +9 -0
  65. data/lib/appfuel/storage.rb +3 -0
  66. data/lib/appfuel/storage/db.rb +4 -0
  67. data/lib/appfuel/storage/db/active_record_model.rb +42 -0
  68. data/lib/appfuel/storage/db/mapper.rb +213 -0
  69. data/lib/appfuel/storage/db/migration_initializer.rb +42 -0
  70. data/lib/appfuel/storage/db/migration_runner.rb +15 -0
  71. data/lib/appfuel/storage/db/migration_tasks.rb +18 -0
  72. data/lib/appfuel/storage/db/repository.rb +231 -0
  73. data/lib/appfuel/storage/db/repository_query.rb +13 -0
  74. data/lib/appfuel/storage/file.rb +1 -0
  75. data/lib/appfuel/storage/file/base.rb +32 -0
  76. data/lib/appfuel/storage/memory.rb +2 -0
  77. data/lib/appfuel/storage/memory/mapper.rb +30 -0
  78. data/lib/appfuel/storage/memory/repository.rb +37 -0
  79. data/lib/appfuel/types.rb +53 -0
  80. data/lib/appfuel/validation.rb +80 -0
  81. data/lib/appfuel/validation/validator.rb +59 -0
  82. data/lib/appfuel/validation/validator_pipe.rb +47 -0
  83. data/lib/appfuel/version.rb +3 -0
  84. metadata +335 -0
@@ -0,0 +1,14 @@
1
+ require_relative 'configuration/file_loader'
2
+ require_relative 'configuration/search'
3
+ require_relative 'configuration/populate'
4
+ require_relative 'configuration/definition_dsl'
5
+
6
+ module Appfuel
7
+ module Configuration
8
+ def self.define(key, &block)
9
+ definition = DefinitionDsl.new(key)
10
+ definition.instance_eval(&block)
11
+ definition
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,175 @@
1
+ module Appfuel
2
+ module Configuration
3
+ # A configuration definition holds the methods that are exposed in
4
+ # Config dsl. This definition allows you to define a given configuration
5
+ # as it would exist in a hash. The dsl collects information like where
6
+ # the file that holds the config data is stored, validation for that
7
+ # data and default values. Just like hashes can have nested hashes
8
+ # you can have nested definitions using the "define" method
9
+ #
10
+ # NOTE: currently we only support yaml config files
11
+ #
12
+ # @example of dsl usage
13
+ #
14
+ # Appfuel::Configuration.define :foo do
15
+ # file /etc/startplus/offers.yml
16
+ # defaults bar: 'bif',
17
+ # baz: 'biz'
18
+ #
19
+ # env FOO_BAR: :bar,
20
+ # FOO_BAZ: :baz
21
+ #
22
+ # unsafe :some_key, :other_key
23
+ #
24
+ # validator {
25
+ # required(:name).filled
26
+ # }
27
+ #
28
+ # define :bam do
29
+ # defaults bat: 'hit',
30
+ # rat: 'cheese'
31
+ #
32
+ # validator {
33
+ # required(:cheese_type).filled
34
+ # }
35
+ # end
36
+ # end
37
+ #
38
+ # Results in something like this
39
+ #
40
+ # hash = {
41
+ # foo: {
42
+ # bar: 'bif',
43
+ # baz: 'baz',
44
+ # name: <user supplied>,
45
+ # bam: {
46
+ # bat: 'hit',
47
+ # rat: 'cheese',
48
+ # cheese_type: <user supplied>
49
+ # }
50
+ # }
51
+ # }
52
+ #
53
+ class DefinitionDsl
54
+ include FileLoader
55
+ include Search
56
+ include Populate
57
+ attr_reader :key
58
+
59
+ # A definition must be created with a key that will be used in the
60
+ # resulting configuration hash that is built
61
+ #
62
+ # @param key Symbol|String key used config hash
63
+ # @return Definition
64
+ def initialize(key)
65
+ @key = key
66
+ @defaults = {}
67
+ @file = []
68
+ @validator = nil
69
+ @children = {}
70
+ @env = {}
71
+ end
72
+
73
+ # Dsl command used to set the file path. When used without params
74
+ # it returns the file path set.
75
+ #
76
+ # @param path String
77
+ # @return String | nil
78
+ def file(path = nil)
79
+ return @file if path.nil?
80
+ path = [path] if path.is_a?(String)
81
+
82
+ unless path.is_a?(Array)
83
+ fail "file path must be a String or Array of Strings"
84
+ end
85
+ @file = path
86
+ end
87
+
88
+ def file?
89
+ !@file.empty?
90
+ end
91
+
92
+ # Dsl used when you expected to manually pass in the configuration data
93
+ # and ignore the configuration in the file
94
+ #
95
+ # @return nil
96
+ def delete_file
97
+ @file = []
98
+ end
99
+
100
+ # Dsl command used to set default values. When used without params
101
+ # it returns the full default hash
102
+ #
103
+ # @param settings Hash
104
+ # @return Hash
105
+ def defaults(settings = nil)
106
+ return @defaults if settings.nil?
107
+ unless settings.is_a?(Hash)
108
+ fail ArgumentError, 'defaults must be a hash'
109
+ end
110
+
111
+ @defaults = settings
112
+ end
113
+
114
+ # Dsl command used to define what env variables will me mapped to config
115
+ # keys
116
+ #
117
+ # @param settings Hash
118
+ # @option <key>=><value> The key is the env variable and the value is
119
+ # the config key it maps to
120
+ # @return Hash
121
+ def env(settings = nil)
122
+ return @env if settings.nil?
123
+ unless settings.is_a?(Hash)
124
+ fail ArgumentError, 'config env settings must be a hash'
125
+ end
126
+
127
+ @env = settings
128
+ end
129
+
130
+ # Dsl to assign validator. When no params are given then it returns
131
+ # the assigned validator. We use the validation library dry-validation
132
+ # http://dry-rb.org/gems/dry-validation/. We will consider any object
133
+ # that implements `call` method a validator.
134
+ #
135
+ # @params validator Dry::Validation::Schema
136
+ # @return validator
137
+ def validator(&block)
138
+ return @validator unless block_given?
139
+
140
+ @validator = Dry::Validation.Schema(&block)
141
+ end
142
+
143
+ def validator?
144
+ !@validator.nil?
145
+ end
146
+
147
+ # Dsl to add a configuration definition as a child of another
148
+ # definition
149
+ #
150
+ # @param key Symbol
151
+ # @return Details
152
+ def define(key, &block)
153
+ definition = self.class.new(key)
154
+ definition.instance_eval(&block)
155
+ self << definition
156
+ end
157
+
158
+ def delete(name)
159
+ @children.delete(name)
160
+ end
161
+
162
+ # Append a definition to this definition's children
163
+ #
164
+ # @param definitions Array | Definition
165
+ def <<(definitions)
166
+ list = definitions.is_a?(Array) ? definitions : [definitions]
167
+ list.each {|item| children[item.key] = item}
168
+ end
169
+
170
+ protected
171
+ attr_accessor :children
172
+
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,61 @@
1
+ module Appfuel
2
+ module Configuration
3
+ # Handle loading files and parsing them correctly based on their type.
4
+ # The file loader used for loading configuration data into a definition
5
+ module FileLoader
6
+ attr_writer :file_module, :json_module, :yaml_module
7
+
8
+ def file_module
9
+ @file_module ||= ::File
10
+ end
11
+
12
+ def json_module
13
+ @json_module || ::JSON
14
+ end
15
+
16
+ def yaml_module
17
+ @yaml_module ||= YAML
18
+ end
19
+
20
+ # @param path [String]
21
+ # @return [Hash]
22
+ def parse_json(path)
23
+ file = file_module.read(path)
24
+ json_module.parse(file)
25
+ end
26
+
27
+ # @param path [String]
28
+ # @return [Hash]
29
+ def parse_yaml(path)
30
+ yaml_module.load_file(path)
31
+ end
32
+
33
+ # Load file will search through a configuration's definition file
34
+ # paths and use the first on that exists. It parse it based on
35
+ # the file type.
36
+ #
37
+ # @raises [RuntimeException] when no files are found
38
+ #
39
+ # @param definition [DefinitionDsl]
40
+ # @return [Hash]
41
+ def load_file(definition)
42
+ paths = definition.file
43
+ key = definition.key
44
+
45
+ paths.each do |path|
46
+ ext = file_module.extname(path).strip.downcase[1..-1]
47
+ parse_method = "parse_#{ext}"
48
+ unless respond_to?(parse_method)
49
+ fail "extension (#{ext}), for (#{key}: #{path}) is not valid, " +
50
+ "only yaml and json are supported"
51
+ end
52
+
53
+ return public_send(parse_method, path) if file_module.exists?(path)
54
+ end
55
+
56
+ list = paths.join(',')
57
+ fail "none of :#{key} config files exist at (#{list})"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,95 @@
1
+ module Appfuel
2
+ module Configuration
3
+ module Populate
4
+ # This converts a definition into a hash of configuation values. It does
5
+ # this using the following steps
6
+ #
7
+ # 1. load config data from a yaml or json file if a file is defined
8
+ # 2. populate all children
9
+ # 3. merge defaults into config data that has been given or resolved
10
+ # from the config file
11
+ # 4. merge override data into the results from step 3
12
+ # 5. run validation and assign clean data to config with the key
13
+ #
14
+ # @throws RuntimeError when validation fails
15
+ # @param data Hash holds overrides and config source data
16
+ # @return Hash
17
+ def populate(data = {})
18
+ overrides = data[:overrides] || {}
19
+ config = data[:config] || {}
20
+ env_data = data[:env] || ENV
21
+
22
+ if overrides.key?(:config_file) && !overrides[:config_file].nil?
23
+ file overrides[:config_file]
24
+ end
25
+
26
+ if file?
27
+ config = load_file(self)
28
+ config = config[key]
29
+ end
30
+
31
+ config ||= {}
32
+
33
+ config = defaults.deep_merge(config)
34
+ config = config.deep_merge(load_env(env_data, self))
35
+ config = config.deep_merge(overrides || {})
36
+
37
+ populate_children(children, config, env_data) unless children.empty?
38
+
39
+ handle_validation(config)
40
+ end
41
+
42
+ #
43
+ # @param definition [DefinitionDsl]
44
+ # @return [Hash]
45
+ def load_env(env_data, definition)
46
+ config = {}
47
+ definition.env.each do |env_key, config_key|
48
+ env_key = env_key.to_s
49
+ next unless env_data.key?(env_key)
50
+ config[config_key] = env_data[env_key]
51
+ end
52
+ config
53
+ end
54
+
55
+ protected
56
+
57
+ def populate_children(child_hash, data, env_data = {})
58
+ child_hash.each do |(def_key, definition)|
59
+
60
+ data[def_key] ||= {}
61
+ data[def_key] = load_file(definition) if definition.file?
62
+ data[def_key] = definition.defaults.deep_merge(data[def_key])
63
+ data[def_key] = data[def_key].deep_merge(load_env(env_data, definition))
64
+ unless definition.children.empty?
65
+ populate_children(definition.children, data[def_key], env_data)
66
+ end
67
+
68
+ data[def_key] = definition.handle_validation(data[def_key])
69
+ end
70
+ end
71
+
72
+ def handle_validation(data)
73
+ return data unless validator?
74
+
75
+ result = validator.call(data)
76
+ if result.failure?
77
+ msg = validation_error_message(result.errors(full: true))
78
+ fail msg
79
+ end
80
+ result.to_h
81
+ end
82
+
83
+ def validation_error_message(errors)
84
+ msg = ''
85
+ errors.each do |(error_key, values)|
86
+ if values.is_a?(Hash)
87
+ values = values.values.uniqu
88
+ end
89
+ msg << "[#{key}] #{error_key}: #{values.join("\n")}\n"
90
+ end
91
+ msg
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,45 @@
1
+ module Appfuel
2
+ module Configuration
3
+ module Search
4
+ # Allow you to access child definitions as if it were a hash.
5
+ # If you add a space separated list of names this will traverse
6
+ # the child hierarchy and return the last name in the list
7
+ #
8
+ # @param name String name or names to search
9
+ # @return Definition | nil
10
+ def [](name)
11
+ find @children, name.to_s.split(" ")
12
+ end
13
+
14
+ # Allows you to search child definitions using an array of names
15
+ # instead of a space separated string
16
+ #
17
+ # @param names Array of strings
18
+ # @return Definition | nil
19
+ def search(*names)
20
+ return nil if names.empty?
21
+ find children, names
22
+ end
23
+
24
+ protected
25
+
26
+ # Recursively locate a child definition in the hierarchy
27
+ #
28
+ # @param child_list Hash
29
+ # @param terms Array of definition keys
30
+ def find(child_list, terms)
31
+ while term = terms.shift
32
+ child_list.each do |(definition_key, definition)|
33
+ next unless definition_key == term
34
+ result = if terms.empty?
35
+ definition
36
+ else
37
+ find(definition.children, terms)
38
+ end
39
+ return result
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ module Appfuel
2
+ class DbModel < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ extend Appfuel::Application::ContainerKey
5
+ extend Appfuel::Application::ContainerClassRegistration
6
+
7
+ def self.inherited(klass)
8
+ super
9
+ register_container_class(klass)
10
+ end
11
+
12
+ def entity_attributes
13
+ attributes.symbolize_keys.select {|_,value| !value.nil?}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ require_relative "domain/domain_name_parser"
2
+ require_relative "domain/dsl"
3
+ require_relative "domain/expr"
4
+ require_relative "domain/entity"
5
+ require_relative "domain/value_object"
6
+ require_relative "domain/entity_collection"
7
+ require_relative "domain/criteria"
@@ -0,0 +1,436 @@
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