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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ec8fcf3de5624419dfb3b092481e0b1d5a8c581
4
- data.tar.gz: 7816976958c040abda4270e68ecf8ba88cfe744f
3
+ metadata.gz: 6aa8eb41aca3f2b82a2b9affbb29399c8407a1ed
4
+ data.tar.gz: 9edc5e0b91bf9acde76210f9e0b1fd84b5d96f78
5
5
  SHA512:
6
- metadata.gz: 7abff44b24913fe1afd206a23b7e34e885c547fefa2c48ec06869b1fd0ca6b384ea9425ccc08fb601408a2fe584b5214dd26c37b93f57bde3f583455734696d3
7
- data.tar.gz: 6ad6e1e1e8004aaff9c2cf07c77eaf4ab82f835d10d6412b17945e7239f0e2a5e58e0c72d6e8687c81c9eb8d6c7726773957bf066f7f70cc02bdf624b1cdd859
6
+ metadata.gz: 9f779a784215090476418ff5932c6a8b26ad625805494e7851ae469ec4c1b8da5cc452daa7a6815d270fc3a1f82f65e056d50e4139bd23101e738aad8a9f0634
7
+ data.tar.gz: 57867104cf6bc9650d2c7b17ac45547222893ca43ee8c67add88cc9d878ccfd4283d69a5abdb81d1b606b6f711243305a8965298ed75707bb7ac9e7736e56cae
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file. (Pending approval) This project adheres to [Semantic Versioning](http://semver.org/). You can read more on this at [keep a change log](http://keepachangelog.com/)
3
+
4
+ # [Unreleased]
5
+
6
+ # Releases
7
+ ## [[0.2.5]](https://github.com/rsb/appfuel/releases/tag/0.2.4) 2017-05-23
8
+ ### Changed
9
+ - `Appfuel::Domain::BaseCriteria` can now qualify `domain exprs`
10
+ - refactored and tested repo mapper
11
+ - starting to work on db mapper interfaces
12
+
13
+
14
+ ### Added
15
+ - added exists interface to db mapper, will finalize the repo on next release
data/README.md CHANGED
@@ -23,7 +23,84 @@ Or install it yourself as:
23
23
 
24
24
  $ gem install appfuel
25
25
 
26
- ## Usage
26
+ ## Overview
27
+ Appfuel exists to solve two problems, aligning application boundaries & code scalability.
28
+
29
+ ### Application Boundaries
30
+ We align our application boundaries by isolating our business logic into its own gem and exposing a set of interfaces to interact with that logic. Your ruby app (web, console, daemon) would represent its own boundary that simply interacted with your business gem. It would do this by declaring the business gem as dependency it needs to use and treating it like any other gem. As a result, we establish a standard set of inputs that always deliver a predictable output.
31
+
32
+ ### Code Scalability
33
+ We address the issue of how well code scales by using object oriented design principles, mainly, `single point of responsibility`. This however, creates an illusion, instead of having a small number of very large files, we produce a large number of smaller files. While we have more files, they are simpler to read and mentally reason their intent.
34
+
35
+ We also tackle complexity using `dependency inversion` where we build all of our dependencies into an application container and rely on the fact that every dependency can be found using a key that will resolve to that particular dependency.
36
+
37
+ We will cover the basic here, more detailed docs can be found on our [gitbook](https://rsb.gitbooks.io/appfuel/).
38
+
39
+ ## Architecture
40
+ ![Basic Flow](docs/images/appfuel_basic_flow.png)
41
+
42
+ ## Directory Structure, Ruby Namespaces & the Application Container
43
+ This is not required but we assume your are moving your business code behind a `rubygem` and as such you should be following [rubygems guidelines](http://guides.rubygems.org/patterns/). From the gem's `lib` we usually declare the root module as the gem module. For example for a gem named `FooBar` we have a lib director that looks like:
44
+ ```
45
+ $ ls
46
+ foo_bar/ foo_bar.rb
47
+ ```
48
+
49
+ We loosely follow the guideline that directories match namespaces, however to try and prevent overly long namespaces where we can, we sometimes break with this guideline.
50
+
51
+ All Appfuel dependencies are registered into a dependency injection (Inversion of Control IoC) container. There are two general types of dependencies:
52
+
53
+ 1. Classes
54
+ - `Actions`, `Commands`, `Repositories` & `Domains`
55
+ - Auto register with container using its ruby namespace with a few rules.
56
+ - Container Key Rules:
57
+ - the root namespace is never in the key, it is the name of the container
58
+ - the module directly under the root is either `global` or `the name of a feature module`
59
+ - ex) `FooBar::Global::Search` => `global.actions.search`
60
+ - ex) `FooBar::Users::Search` => `features.users.actions.search`
61
+
62
+ 2. Blocks defined from a DSL
63
+ - `initializers`, `validators`, `domain builders` & `presenters`
64
+ - register with container via DSL.
65
+ - DSL handles registration key
66
+
67
+ ## Setting Up Appfuel
68
+ In order to use Appfuel we mixin `Appfuel::Application::Root` into our `application root class` and configure it as follows:
69
+
70
+ ```ruby
71
+ module FooBar
72
+ extend Appfuel::Application::Root
73
+
74
+ setup_appfuel root: self,
75
+ root_path: File.dirname(__FILE__),
76
+ config_definition: Configuration.definition,
77
+ on_after_setup: ->(_container) {
78
+ require_relative 'foo_bar/initializers'
79
+ }
80
+
81
+ end
82
+ ```
83
+
84
+ 1. Mixin the application root to allow us to use `setup_appfuel` and `call`
85
+ - `call` with run any action with a route of format `feature/action`
86
+ 2. `setup_appfuel` allows `appfuel` configure & initialize your system
87
+ - `root`: is the root module, its name with be the name of the `app_container`
88
+ - `root_path`: allows appfuel to auto load features
89
+ - `config_definition`: is a Dsl that setups configuration data
90
+ - `on_after_setup`: is a hook that will call your lambda once setup is done
91
+
92
+ After `setup_appfuel` is called the application container named `foo_bar` is initialized and ready. To retrieve the container from `appfuel`:
93
+
94
+ ```ruby
95
+ Appfuel.app_container('foo_bar')
96
+ ```
97
+
98
+ Since there are no other app container this is also the default container so you can simply call:
99
+ ```ruby
100
+ Appfuel.app_container
101
+ ```
102
+ This is will return the same container as above.
103
+
27
104
 
28
105
 
29
106
  ## Development
data/appfuel.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency "dry-validation", "~> 0.10.5"
30
30
  spec.add_dependency "dry-monads", "~> 0.2"
31
31
  spec.add_dependency "dry-configurable", "~> 0.6"
32
+ spec.add_dependency "parslet", "~> 1.8.0"
32
33
 
33
34
  spec.add_development_dependency "bundler", "~> 1.13"
34
35
  spec.add_development_dependency "rake", "~> 10.0"
Binary file
@@ -40,6 +40,7 @@ module Appfuel
40
40
  def container_path?
41
41
  !@container_path.nil?
42
42
  end
43
+
43
44
  # @param list [Array] list of container namespace parts including root
44
45
  # @return [Array]
45
46
  def container_path=(list)
@@ -113,6 +114,28 @@ module Appfuel
113
114
  @container_path.last
114
115
  end
115
116
 
117
+ # Allow the concrete class that is using this mixing to change the
118
+ # key which would otherwise be the lowercase, underscored class name
119
+ #
120
+ # @return nil
121
+ def container_class_key(value = nil)
122
+ return @container_class_key if value.nil?
123
+ p "[container-class-key] #{self} => #{value}"
124
+ @container_class_key = value
125
+ end
126
+
127
+ def container_class_id
128
+ container_class_key || container_key_basename
129
+ end
130
+
131
+ def container_class_type
132
+ nil
133
+ end
134
+
135
+ def container_class_path
136
+ "#{top_container_key}.#{container_class_type}.#{container_class_id}"
137
+ end
138
+
116
139
  # Fully qualified key, meaning you can access the class this was mixed into
117
140
  # if you stored it into the container using this key
118
141
  #
@@ -135,6 +158,7 @@ module Appfuel
135
158
  @container_global_name ||= 'global'
136
159
  end
137
160
 
161
+ #
138
162
  # Convert the injection key to a fully qualified namespaced key that
139
163
  # is used to pull an item out of the app container.
140
164
  #
@@ -177,7 +201,6 @@ module Appfuel
177
201
  # convert_to_qualified_container_key('global.baz', 'container')
178
202
  #
179
203
  # returns 'baz'
180
- #
181
204
  # @param key [String] partial key to be built into fully qualified key
182
205
  # @param type_ns [String] namespace for key
183
206
  # @return [String] fully qualified namespaced key
@@ -7,16 +7,41 @@ module Appfuel
7
7
  # container key, so we simply need to obtain the qualified namespace
8
8
  # key for this class extending this, that does not belong to appfuel.
9
9
  #
10
+ # types of classes:
11
+ # repositories
12
+ # db
13
+ # domains
14
+ #
15
+ # features.repositories.key
10
16
  # @param klass [Class] the handler class that is inheriting this
11
17
  # @return [Boolean]
12
- def register_container_class(klass)
18
+ def stage_class_for_registration(klass)
19
+ if !klass.respond_to?(:register_class?) || !klass.register_class?
20
+ return false
21
+ end
22
+
23
+ unless klass.respond_to?(:container_root_name)
24
+ fail "#{klass} must implement :container_root_name"
25
+ end
13
26
  root = klass.container_root_name
14
27
  return false if root == 'appfuel'
15
28
 
16
- container = Appfuel.app_container(root)
17
- container.register(klass.container_qualified_key, klass)
18
- true
29
+ container = Appfuel.app_container(klass.container_root_name)
30
+ container[:auto_register_classes] << klass
19
31
  end
32
+
33
+ def disable_class_registration
34
+ @is_class_registration = false
35
+ end
36
+
37
+ def enable_class_registration
38
+ @is_class_registration = true
39
+ end
40
+
41
+ def register_class?
42
+ @is_class_registration ||= true
43
+ end
44
+
20
45
  end
21
46
  end
22
47
  end
@@ -109,6 +109,7 @@ module Appfuel
109
109
  container.register(:root, root)
110
110
  container.register(:root_name, root_name)
111
111
  container.register(:root_path, root_path)
112
+ container.register(:auto_register_classes, [])
112
113
  container.register(:repository_mappings, {})
113
114
  container.register(:repository_initializer, repo_initializer)
114
115
  container.register(:features_path, "#{root_name}/features")
@@ -1,4 +1,3 @@
1
1
  require_relative 'application/root'
2
2
  require_relative 'application/app_container'
3
- require_relative 'application/container_key'
4
3
  require_relative 'application/container_class_registration'
@@ -0,0 +1,171 @@
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
+ #
10
+ # global.user
11
+ # memberships.user
12
+ #
13
+ # exist: 'foo.bar exists id = 6'
14
+ # search: 'foo.bar filter id = 6 and bar = "foo" order id asc limit 6'
15
+ #
16
+ # search:
17
+ # domain: 'foo.bar',
18
+ #
19
+ # filters: 'id = 6 or id = 8 and id = 9'
20
+ # filters: [
21
+ # 'id = 6',
22
+ # {or: 'id = 8'}
23
+ # {and: id = 9'}
24
+ # ]
25
+ #
26
+ # order: 'foo.bar.id asc'
27
+ # order: 'foo.bar.id'
28
+ # order: [
29
+ # 'foo.bar.id',
30
+ # {desc: 'foo.bar.id'},
31
+ # {asc: 'foo.bar.id'}
32
+ # ]
33
+ # limit: 1
34
+ #
35
+ # settings:
36
+ # page: 1
37
+ # per_page: 2
38
+ # disable_pagination
39
+ # first
40
+ # all
41
+ # last
42
+ # error_on_empty
43
+ # parser
44
+ # transform
45
+ #
46
+ # exists:
47
+ # domain:
48
+ # expr:
49
+ #
50
+ #
51
+ class BaseCriteria
52
+ include DomainNameParser
53
+
54
+
55
+ attr_reader :domain_basename, :domain_name, :feature, :settings, :filters
56
+
57
+ # Parse out the domain into feature, domain, determine the name of the
58
+ # repo this criteria is for and initailize basic settings.
59
+ # global.user
60
+ #
61
+ # membership.user
62
+ # foo.id filter name like "foo" order foo.bar.id asc limit 2
63
+ # foo.id exists foo.id = 5
64
+ #
65
+ # @example
66
+ # SpCore::Domain::Criteria('foo', single: true)
67
+ # Types.Criteria('foo.bar', single: true)
68
+ #
69
+ # === Options
70
+ # error_on_empty: will cause the repo to fail when query returns an
71
+ # an empty dataset. The failure will have the message
72
+ # with key as domain and text is "<domain> not found"
73
+ #
74
+ # single: will cause the repo to return only one, the first,
75
+ # entity in the dataset
76
+ #
77
+ # @param domain [String] fully qualified domain name
78
+ # @param opts [Hash] options for initializing criteria
79
+ # @return [Criteria]
80
+ def initialize(domain_name, data = {})
81
+ @feature, @domain_basename, @domain_name = parse_domain_name(domain_name)
82
+ @settings = data[:settings] || CriteriaSettings.new(data)
83
+ @filters = nil
84
+ @params = {}
85
+ end
86
+
87
+ def clear_filters
88
+ @filters = nil
89
+ end
90
+
91
+ def filters?
92
+ !filters.nil?
93
+ end
94
+
95
+ def global?
96
+ !feature?
97
+ end
98
+
99
+ def feature?
100
+ @feature != 'global'
101
+ end
102
+
103
+ # @example
104
+ # criteria.add_param('foo', 100)
105
+ #
106
+ # @param key [Symbol, String] The key name where we want to keep the value
107
+ # @param value [String, Integer] The value that belongs to the key param
108
+ # @return [String, Integer] The saved value
109
+ def add_param(key, value)
110
+ fail 'key should not be nil' if key.nil?
111
+
112
+ @params[key.to_sym] = value
113
+ end
114
+
115
+ # @param key [String, Symbol]
116
+ # @return [String, Integer, Boolean] the found value
117
+ def param(key)
118
+ @params[key.to_sym]
119
+ end
120
+
121
+ # @param key [String, Symbol]
122
+ # @return [Boolean]
123
+ def param?(key)
124
+ @params.key?(key.to_sym)
125
+ end
126
+
127
+ # @return [Boolean] if the @params variable has values
128
+ def params?
129
+ !@params.empty?
130
+ end
131
+
132
+ private
133
+ def parse_expr(str)
134
+ if !(settings.parser && settings.parser.respond_to?(:parse))
135
+ fail "expression parser must implement to :parse"
136
+ end
137
+
138
+ if !(settings.transform && settings.transform.respond_to?(:apply))
139
+ fail "expression transform must implement :apply"
140
+ end
141
+
142
+ begin
143
+ tree = settings.parser.parse(str)
144
+ rescue Parslet::ParseFailed => e
145
+ msg = "The expression (#{str}) failed to parse"
146
+ err = RuntimeError.new(msg)
147
+ err.set_backtrace(e.backtrace)
148
+ raise err
149
+ end
150
+
151
+ result = settings.transform.apply(tree)
152
+ result = result[:domain_expr] || result[:root]
153
+ unless result
154
+ fail "unable to parse (#{str}) correctly"
155
+ end
156
+ result
157
+ end
158
+
159
+ def qualify_expr(domain_expr)
160
+ return domain_expr if domain_expr.qualified?
161
+ if global?
162
+ domain_expr.qualify_global(domain_basename)
163
+ return domain_expr
164
+ end
165
+
166
+ domain_expr.qualify_feature(feature, domain_basename)
167
+ domain_expr
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,248 @@
1
+ module SpCore
2
+ module Domain
3
+
4
+ # CriteriaBuilder is an interface creating new Criteria as well as
5
+ # building up an existing Criteria instance
6
+ class CriteriaBuilder
7
+
8
+ # For the list of defined inputs and maps, it builds new Criteria object
9
+ # following build pattern
10
+ #
11
+ # @example
12
+ #
13
+ # inputs = {
14
+ # search: "projects.offer",
15
+ # repo_map: { "projects.offer" => "foo" },
16
+ # filters: [
17
+ # {foo: 'bar', ep: 1}
18
+ # ],
19
+ # order: [
20
+ # {created_at: :desc}
21
+ # ],
22
+ # limit: 5,
23
+ # all: true,
24
+ # page: 1,
25
+ # per_page: 10
26
+ # }
27
+ #
28
+ # maps = {
29
+ # feature: "projects",
30
+ # attr_map: {
31
+ # foo: "users.user.foo"
32
+ # }
33
+ # }
34
+ #
35
+ # criteria_builder = SpCore::Domain::CriteriaBuilder.build(inputs, maps)
36
+ #
37
+ # @param inputs [Hash] input data
38
+ # @param maps [Hash] mapping data
39
+ # @return [Criteria]
40
+ def self.build(inputs, maps = {})
41
+ builder = new(inputs, maps)
42
+ criteria = builder.create
43
+ builder.build(criteria, inputs)
44
+ criteria
45
+ end
46
+
47
+ # Builds new Criteria object
48
+ #
49
+ # @param inputs [Hash] input data
50
+ # @option inputs [String] :search entity/domain to search for
51
+ # @option inputs [Hash] :repo_map repository mapping by entity/domain
52
+ # @option inputs [Array] :filters list of filter items
53
+ # @option inputs [Array] :order list of order definitions
54
+ # @option inputs [Integer] :limit number of records to limit to
55
+ # @option inputs [Boolean] :all flag for returning all records
56
+ # @option inputs [Boolean] :first flag for returning only first record
57
+ # @option inputs [Boolean] :last flag for returning only last record
58
+ # @option inputs [Integer] :page number of the page in collection
59
+ # @option inputs [Integer] :per_page num. of items per page in collection
60
+ # @option inputs [Boolean] :single_page flag for getting single page only
61
+ # @param maps [Hash] mapping data
62
+ # @option maps [Hash] :attr_map mapping by attr to domain.attribute_name
63
+ # @return [Criteria]
64
+ def initialize(inputs, maps)
65
+ domain_name = inputs.fetch(:search).to_s
66
+ repo_map = inputs[:repo_map] || {}
67
+ fail ":repo_map must be a Hash" unless repo_map.is_a?(Hash)
68
+
69
+ @attr_map = maps[:attr_map] || {}
70
+ @domain_name = "#{domain_name}"
71
+ @repo_name = repo(repo_map, domain_name)
72
+ end
73
+
74
+ # Creates new Criteria instance.
75
+ # Actually it's just a delegator to Criteria initializer
76
+ #
77
+ # @param domain [String] fully qualified domain name
78
+ # @param options [Hash] options for initializing Criteria
79
+ # @return [Criteria]
80
+ def create
81
+ Criteria.new(domain_name, repo: repo_name)
82
+ end
83
+
84
+ # Build criteria clauses
85
+ #
86
+ # @param inputs [Hash]
87
+ def build(criteria, inputs)
88
+ self.filters(criteria, inputs, attr_map)
89
+ .order(criteria, inputs, attr_map)
90
+ .limit(criteria, inputs)
91
+ .scope(criteria, inputs)
92
+ .pagination(criteria, inputs)
93
+ end
94
+
95
+ # Updates Criteria object filters with the list of given filters
96
+ # Each filter item must be a Hash.
97
+ #
98
+ # @example
99
+ # inputs = {
100
+ # filters: [
101
+ # {first_name: 'Bob'},
102
+ # {last_name: 'Doe', op: 'like'},
103
+ # {last_name: 'Johnson', op: 'like', or: false}
104
+ # ]
105
+ # }
106
+ #
107
+ # @param criteria [Criteria] criteria object to build
108
+ # @param inputs [Hash] input data
109
+ # @option inputs [Array] :filters list of filter items
110
+ # @param attr_map [Hash] mapping by attr to domain.attribute_name
111
+ # @return [CriteriaBuilder]
112
+ def filters(criteria, inputs, attr_map = {})
113
+ if inputs.key?(:filters)
114
+ filters = inputs.fetch(:filters)
115
+ fail ":filters must be an Array" unless filters.is_a?(Array)
116
+
117
+ filters.each do |filter_item|
118
+ attr_name, value = filter_item.first
119
+ qualified_name = lookup_attribute(attr_name, attr_map)
120
+ if qualified_name != attr_name
121
+ filter_item.delete(attr_name)
122
+ filter_item[qualified_name] = value
123
+ end
124
+ end
125
+
126
+ criteria.filter(filters)
127
+ end
128
+ self
129
+ end
130
+
131
+ # Updates given Criteria ordering clause.
132
+ # It expects a list of order definitions.
133
+ # Order definition might be either Hash, String or Symbol.
134
+ # If item is String or Symbol, ordering direction is ascending(:asc)
135
+ #
136
+ # @example
137
+ # inputs = {
138
+ # order: [
139
+ # {created_at: :desc},
140
+ # 'created_at',
141
+ # :created_at
142
+ # ]
143
+ # }
144
+ #
145
+ # @param criteria [Criteria] criteria object to build
146
+ # @param inputs [Hash] input data
147
+ # @option inputs [Array] :order list of order definitions
148
+ # @param attr_map [Hash] mapping by attr to domain.attribute_name
149
+ # @return [CriteriaBuilder]
150
+ def order(criteria, inputs, attr_map = {})
151
+ return self unless inputs.key?(:order)
152
+ list = inputs.fetch(:order)
153
+
154
+ fail ":order must implement :each" unless list.respond_to?(:each)
155
+ return self if list.empty?
156
+
157
+ final_order = {}
158
+ list.each do |order|
159
+ domain_attr_name = order
160
+ order_dir = "asc"
161
+
162
+ if order.is_a?(Hash)
163
+ domain_attr_name, order_dir = order.first
164
+ end
165
+
166
+ qualified_name = lookup_attribute(domain_attr_name, attr_map)
167
+ final_order[qualified_name.to_s] = order_dir.to_s
168
+ criteria.order_by(final_order)
169
+ end
170
+ self
171
+ end
172
+
173
+ # Updates given Criteria record limit
174
+ #
175
+ # @param criteria [Criteria] criteria to build
176
+ # @param inputs [Hash] input data
177
+ # @option inputs [Integer] :limit number of records to limit to
178
+ # @return [CriteriaBuilder]
179
+ def limit(criteria, inputs)
180
+ if inputs.key?(:limit)
181
+ criteria.limit(inputs.fetch(:limit))
182
+ end
183
+ self
184
+ end
185
+
186
+ # Updates given Criteria collection limits.
187
+ # Those are :disable_pagination, :all, :first and :last properties.
188
+ #
189
+ # @param criteria [Criteria] criteria to build
190
+ # @param inputs [Hash] input data
191
+ # @option inputs [Boolean] :all flag for returning all records
192
+ # @option inputs [Boolean] :first flag for returning onnly first record
193
+ # @option inputs [Boolean] :last flag for returning only last record
194
+ # @return [CriteriaBuilder]
195
+ def scope(criteria, inputs)
196
+ if inputs[:first] == true
197
+ criteria.first
198
+ elsif inputs[:last] == true
199
+ criteria.last
200
+ elsif inputs[:disable_pagination] == true
201
+ criteria.disable_pagination
202
+ elsif inputs[:all] == true
203
+ criteria.all
204
+ end
205
+ self
206
+ end
207
+
208
+ # Updates given Criteria pagination params.
209
+ #
210
+ # @param criteria [Criteria] criteria to build
211
+ # @param inputs [Hash] input data
212
+ # @option inputs [Integer] :page number of the page within collection
213
+ # @option inputs [Integer] :per_page number of items per page
214
+ # @option inputs [Boolean] :single_page flag for getting single page only
215
+ # @param attr_map [Hash] mapping by attr to domain.attribute_name
216
+ # @return [CriteriaBuilder]
217
+ def pagination(criteria, inputs)
218
+ criteria.disable_pagination if inputs[:single_page]==true || inputs[:disable_pagination] == true
219
+ criteria.page(inputs[:page]) if inputs[:page]
220
+ criteria.per_page(inputs[:per_page]) if inputs[:per_page]
221
+ self
222
+ end
223
+
224
+ private
225
+
226
+ attr_reader :attr_map, :domain_name, :repo_name
227
+
228
+ def repo(repo_map, domain_name)
229
+ repo_map.key?(domain_name) ? repo_map[domain_name].to_s : domain_name
230
+ end
231
+
232
+ # It looks for given attribute name in the mapping and returns
233
+ # domain.attribute_name string if found. Else it returns attribute
234
+ # name itself.
235
+ #
236
+ # @private
237
+ #
238
+ # @param name [String] attribute name
239
+ # @param map [Hash] attribute to domain.attribute_name mapping
240
+ # @return [String]
241
+ def lookup_attribute(name, map)
242
+ return name unless map.is_a?(Hash)
243
+ return name unless map.key?(name) || map.key?(name.to_sym)
244
+ map[name] || map[name.to_sym]
245
+ end
246
+ end
247
+ end
248
+ end