wallaby 5.1.5 → 5.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/app/controllers/wallaby/abstract_resources_controller.rb +171 -187
  4. data/app/controllers/wallaby/application_controller.rb +15 -4
  5. data/app/controllers/wallaby/base_controller.rb +1 -3
  6. data/app/controllers/wallaby/secure_controller.rb +10 -6
  7. data/app/security/ability.rb +1 -0
  8. data/app/views/wallaby/resources/form/_float.html.erb +1 -1
  9. data/config/locales/wallaby.en.yml +5 -0
  10. data/lib/adaptors/wallaby/active_record.rb +1 -1
  11. data/lib/adaptors/wallaby/active_record/model_decorator.rb +17 -19
  12. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder.rb +7 -0
  13. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder/association_builder.rb +11 -0
  14. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder/polymorphic_builder.rb +10 -1
  15. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder/sti_builder.rb +14 -7
  16. data/lib/adaptors/wallaby/active_record/model_decorator/title_field_finder.rb +4 -0
  17. data/lib/adaptors/wallaby/active_record/model_finder.rb +4 -3
  18. data/lib/adaptors/wallaby/active_record/model_pagination_provider.rb +3 -6
  19. data/lib/adaptors/wallaby/active_record/model_service_provider.rb +12 -3
  20. data/lib/adaptors/wallaby/active_record/model_service_provider/normalizer.rb +16 -1
  21. data/lib/adaptors/wallaby/active_record/model_service_provider/permitter.rb +14 -2
  22. data/lib/adaptors/wallaby/active_record/model_service_provider/querier.rb +46 -1
  23. data/lib/adaptors/wallaby/active_record/model_service_provider/querier/transformer.rb +11 -5
  24. data/lib/adaptors/wallaby/active_record/model_service_provider/validator.rb +6 -0
  25. data/lib/concerns/wallaby/resources_helper_methods.rb +44 -0
  26. data/lib/decorators/wallaby/abstract_resource_decorator.rb +9 -4
  27. data/lib/errors/wallaby/model_not_found.rb +1 -0
  28. data/lib/errors/wallaby/resource_not_found.rb +1 -0
  29. data/lib/forms/wallaby/form_builder.rb +5 -3
  30. data/lib/helpers/wallaby/application_helper.rb +10 -1
  31. data/lib/helpers/wallaby/base_helper.rb +12 -4
  32. data/lib/helpers/wallaby/form_helper.rb +7 -4
  33. data/lib/helpers/wallaby/links_helper.rb +10 -7
  34. data/lib/helpers/wallaby/resources_helper.rb +2 -0
  35. data/lib/helpers/wallaby/secure_helper.rb +13 -7
  36. data/lib/helpers/wallaby/styling_helper.rb +1 -1
  37. data/lib/interfaces/wallaby/mode.rb +38 -16
  38. data/lib/interfaces/wallaby/model_decorator.rb +36 -27
  39. data/lib/interfaces/wallaby/model_decorator/field_helpers.rb +12 -2
  40. data/lib/interfaces/wallaby/model_finder.rb +1 -0
  41. data/lib/interfaces/wallaby/model_pagination_provider.rb +6 -5
  42. data/lib/interfaces/wallaby/model_service_provider.rb +10 -2
  43. data/lib/paginators/wallaby/abstract_resource_paginator.rb +6 -1
  44. data/lib/parsers/wallaby/parser.rb +1 -0
  45. data/lib/responders/wallaby/abstract_responder.rb +15 -2
  46. data/lib/routes/wallaby/resources_router.rb +4 -0
  47. data/lib/servicers/wallaby/abstract_model_servicer.rb +25 -1
  48. data/lib/servicers/wallaby/model_servicer.rb +1 -0
  49. data/lib/services/wallaby/link_options_normalizer.rb +13 -9
  50. data/lib/services/wallaby/lookup_context_wrapper.rb +11 -2
  51. data/lib/services/wallaby/map.rb +107 -69
  52. data/lib/services/wallaby/map/mode_mapper.rb +2 -0
  53. data/lib/services/wallaby/map/model_class_collector.rb +7 -0
  54. data/lib/services/wallaby/map/model_class_mapper.rb +15 -7
  55. data/lib/services/wallaby/partial_renderer.rb +7 -0
  56. data/lib/services/wallaby/prefixes_builder.rb +18 -0
  57. data/lib/services/wallaby/sorting/hash_builder.rb +7 -1
  58. data/lib/services/wallaby/sorting/link_builder.rb +6 -1
  59. data/lib/services/wallaby/sorting/next_builder.rb +23 -2
  60. data/lib/tree/wallaby/node.rb +2 -0
  61. data/lib/utils/wallaby/test_utils.rb +32 -0
  62. data/lib/utils/wallaby/utils.rb +51 -5
  63. data/lib/wallaby.rb +1 -0
  64. data/lib/wallaby/configuration.rb +9 -3
  65. data/lib/wallaby/configuration/features.rb +2 -1
  66. data/lib/wallaby/configuration/mapping.rb +66 -0
  67. data/lib/wallaby/configuration/metadata.rb +1 -0
  68. data/lib/wallaby/configuration/pagination.rb +2 -1
  69. data/lib/wallaby/engine.rb +4 -0
  70. data/lib/wallaby/version.rb +1 -1
  71. metadata +5 -3
  72. data/lib/tasks/wallaby_tasks.rake +0 -4
@@ -1,90 +1,128 @@
1
1
  module Wallaby
2
- # Global storage of all the maps for model classes
2
+ # @private
3
+ # Global mappings
3
4
  class Map
4
- # { model => mode }
5
- def self.mode_map
6
- @mode_map ||= ModeMapper.new(Mode.descendants).map.freeze
7
- end
5
+ class << self
6
+ # { model => mode }
7
+ # @return [Hash] { model => mode }
8
+ def mode_map
9
+ @mode_map ||= ModeMapper.new(Mode.descendants).map.freeze
10
+ end
8
11
 
9
- # [ model classes ]
10
- def self.model_classes
11
- @model_classes ||=
12
- ModelClassCollector.new(::Wallaby.configuration).collect.freeze
13
- end
12
+ # @return [Array] [ model classes ]
13
+ def model_classes
14
+ @model_classes ||= ModelClassCollector.new(configuration).collect.freeze
15
+ end
14
16
 
15
- # { model => controller }
16
- def self.controller_map(model_class)
17
- @controller_map ||= ModelClassMapper.new(ResourcesController).map
18
- @controller_map[model_class] ||= ResourcesController
19
- end
17
+ # { model => controller }
18
+ # @param model_class [Class]
19
+ # @return [Class] controller class
20
+ # default to `mapping.resources_controller`
21
+ def controller_map(model_class)
22
+ @controller_map ||=
23
+ ModelClassMapper.new(mapping.resources_controller).map
24
+ @controller_map[model_class] ||= mapping.resources_controller
25
+ end
20
26
 
21
- # { model => model decorator }
22
- def self.model_decorator_map(model_class)
23
- @model_decorator_map ||= {}
24
- @model_decorator_map[model_class] ||= begin
25
- mode = mode_map[model_class]
26
- mode.model_decorator.new model_class if mode
27
+ # { model => model decorator }
28
+ # @param model_class [Class]
29
+ # @return [Wallaby::ModelDecorator] model decorator instance
30
+ def model_decorator_map(model_class)
31
+ @model_decorator_map ||= {}
32
+ @model_decorator_map[model_class] ||= begin
33
+ mode = mode_map[model_class]
34
+ mode.model_decorator.new model_class if mode # rubocop:disable Style/SafeNavigation, Metrics/LineLength
35
+ end
27
36
  end
28
- end
29
37
 
30
- # { model => resource decorator }
31
- def self.resource_decorator_map(model_class)
32
- @resource_decorator_map ||= ModelClassMapper.new(ResourceDecorator).map
33
- @resource_decorator_map[model_class] ||= begin
34
- ResourceDecorator if mode_map[model_class]
38
+ # { model => resource decorator }
39
+ # @param model_class [Class]
40
+ # @return [Class] resource decorator class
41
+ # default to `mapping.resource_decorator`
42
+ def resource_decorator_map(model_class)
43
+ @resource_decorator_map ||=
44
+ ModelClassMapper.new(mapping.resource_decorator).map
45
+ @resource_decorator_map[model_class] ||= begin
46
+ mapping.resource_decorator if mode_map[model_class]
47
+ end
35
48
  end
36
- end
37
49
 
38
- # { model => servicer }
39
- def self.servicer_map(model_class)
40
- @servicer_map ||= ModelClassMapper.new(ModelServicer).map
41
- @servicer_map[model_class] ||= begin
42
- ModelServicer if mode_map[model_class]
50
+ # { model => servicer }
51
+ # @param model_class [Class]
52
+ # @return [Class] resource servicer class
53
+ # default to `mapping.resource_servicer`
54
+ def servicer_map(model_class)
55
+ @servicer_map ||= ModelClassMapper.new(mapping.model_servicer).map
56
+ @servicer_map[model_class] ||= begin
57
+ mapping.model_servicer if mode_map[model_class]
58
+ end
43
59
  end
44
- end
45
60
 
46
- # { model => service_provider }
47
- def self.service_provider_map(model_class)
48
- @service_provider_map ||= {}
49
- @service_provider_map[model_class] ||= begin
50
- mode = mode_map[model_class]
51
- mode.model_service_provider.new model_class if mode
61
+ # { model => service_provider }
62
+ # @param model_class [Class]
63
+ # @return [Wallaby::ModelServiceProvider] model service provider instance
64
+ def service_provider_map(model_class)
65
+ @service_provider_map ||= {}
66
+ @service_provider_map[model_class] ||= begin
67
+ mode = mode_map[model_class]
68
+ mode.model_service_provider.new model_class if mode # rubocop:disable Style/SafeNavigation, Metrics/LineLength
69
+ end
52
70
  end
53
- end
54
71
 
55
- # { model => paginator }
56
- def self.paginator_map(model_class)
57
- @paginator_map ||= ModelClassMapper.new(ResourcePaginator).map
58
- @paginator_map[model_class] ||= begin
59
- ResourcePaginator if mode_map[model_class]
72
+ # { model => paginator }
73
+ # @param model_class [Class]
74
+ # @return [Hash] { model => paginator }
75
+ def paginator_map(model_class)
76
+ @paginator_map ||= ModelClassMapper.new(mapping.resource_paginator).map
77
+ @paginator_map[model_class] ||= begin
78
+ mapping.resource_paginator if mode_map[model_class]
79
+ end
60
80
  end
61
- end
62
81
 
63
- # { model => pagination_provider }
64
- def self.pagination_provider_map(model_class)
65
- @pagination_provider_map ||= {}
66
- @pagination_provider_map[model_class] ||=
67
- mode_map[model_class].try(:model_pagination_provider)
68
- end
82
+ # { model => pagination_provider }
83
+ # @param model_class [Class]
84
+ # @return [Class] model pagination provider class
85
+ def pagination_provider_map(model_class)
86
+ @pagination_provider_map ||= {}
87
+ @pagination_provider_map[model_class] ||=
88
+ mode_map[model_class].try(:model_pagination_provider)
89
+ end
69
90
 
70
- # { model => resources name }
71
- def self.resources_name_map(model_class)
72
- @resources_name_map ||= {}
73
- @resources_name_map[model_class] ||= Utils.to_resources_name model_class
74
- end
91
+ # { model => resources name }
92
+ # @param model_class [Class]
93
+ # @return [String] resources name
94
+ def resources_name_map(model_class)
95
+ @resources_name_map ||= {}
96
+ @resources_name_map[model_class] ||= Utils.to_resources_name model_class
97
+ end
75
98
 
76
- # { resources name => model }
77
- def self.model_class_map(resources_name)
78
- @model_class_map ||= {}
79
- @model_class_map[resources_name] ||= Utils.to_model_class resources_name
80
- end
99
+ # { resources name => model }
100
+ # @param resources_name [String]
101
+ # @return [Class] model class
102
+ def model_class_map(resources_name)
103
+ @model_class_map ||= {}
104
+ @model_class_map[resources_name] ||= Utils.to_model_class resources_name
105
+ end
81
106
 
82
- # Clear all the class variables to nil
83
- def self.clear
84
- @mode_map, @model_classes, @controller_map, @model_decorator_map,
85
- @resource_decorator_map, @servicer_map, @service_provider_map,
86
- @paginator_map, @pagination_provider_map, @model_class_map,
87
- @resources_name_map = []
107
+ # Clear all the class variables to nil
108
+ def clear
109
+ @mode_map, @model_classes, @controller_map, @model_decorator_map,
110
+ @resource_decorator_map, @servicer_map, @service_provider_map,
111
+ @paginator_map, @pagination_provider_map, @model_class_map,
112
+ @resources_name_map = []
113
+ end
114
+
115
+ private
116
+
117
+ # shorthand method
118
+ def configuration
119
+ ::Wallaby.configuration
120
+ end
121
+
122
+ # shorthand method
123
+ def mapping
124
+ configuration.mapping
125
+ end
88
126
  end
89
127
  end
90
128
  end
@@ -2,10 +2,12 @@ module Wallaby
2
2
  class Map
3
3
  # To map model class to mode class so that we know how to handle a model
4
4
  class ModeMapper
5
+ # @param model_class [Class] model class
5
6
  def initialize(mode_classes)
6
7
  @mode_classes = mode_classes
7
8
  end
8
9
 
10
+ # @return [Hash] { model_class => mode }
9
11
  def map
10
12
  return {} if @mode_classes.blank?
11
13
  @mode_classes.each_with_object({}) do |mode_class, map|
@@ -2,10 +2,12 @@ module Wallaby
2
2
  class Map
3
3
  # To collect model classes that are configured to be handled by Wallaby
4
4
  class ModelClassCollector
5
+ # @param configuration [Configuration]
5
6
  def initialize(configuration)
6
7
  @configuration = configuration
7
8
  end
8
9
 
10
+ # @return [Array<Class>] model class
9
11
  def collect
10
12
  return all_models - excluded_models if configured_models.blank?
11
13
  invalid_models_check
@@ -14,6 +16,7 @@ module Wallaby
14
16
 
15
17
  private
16
18
 
19
+ # Check if the models are valid, raise if invalid
17
20
  def invalid_models_check
18
21
  invalid_models = configured_models - all_models
19
22
  return if invalid_models.blank?
@@ -21,18 +24,22 @@ module Wallaby
21
24
  raise InvalidError, message
22
25
  end
23
26
 
27
+ # @return [Wallaby::Configuration::Models]
24
28
  def models
25
29
  @configuration.models
26
30
  end
27
31
 
32
+ # @return [Array<Class>] all the models that modes recognize
28
33
  def all_models
29
34
  Map.mode_map.keys
30
35
  end
31
36
 
37
+ # @return [Array<Class>] a list of models to exclude
32
38
  def excluded_models
33
39
  models.excludes
34
40
  end
35
41
 
42
+ # @return [Array<Class>] a list of models to set
36
43
  def configured_models
37
44
  models.presence
38
45
  end
@@ -1,27 +1,35 @@
1
1
  module Wallaby
2
2
  class Map
3
- # To map model class to a klass
3
+ # @private
4
+ # To find out all descendant classes and convert them if necessary.
4
5
  class ModelClassMapper
5
- DEFAULT_BLOCK = ->(same) { same }.freeze
6
-
7
6
  def initialize(base_class)
8
7
  @base_class = base_class
9
8
  end
10
9
 
11
- def map(&block)
12
- block ||= DEFAULT_BLOCK
10
+ # @return [Array] a list of non-anonymous descendant classes
11
+ def map
13
12
  classes_array.each_with_object({}) do |klass, map|
14
13
  next if anonymous? klass
15
- map[klass.model_class] = block.call klass
14
+ begin
15
+ map[klass.model_class] = block_given? ? yield(klass) : klass
16
+ rescue Wallaby::ModelNotFound
17
+ Rails.logger.error Utils.translate_class(
18
+ self, :missing_model_class, model: klass.name
19
+ )
20
+ end
16
21
  end
17
22
  end
18
23
 
19
24
  protected
20
25
 
26
+ # @param klass [Class]
27
+ # @return [Boolean] whether the class is anonymous
21
28
  def anonymous?(klass)
22
- klass.name.blank?
29
+ Utils.anonymous_class? klass
23
30
  end
24
31
 
32
+ # @return [Array<Class>] all descendants
25
33
  def classes_array
26
34
  @base_class.try(:descendants) || EMPTY_ARRAY
27
35
  end
@@ -1,4 +1,5 @@
1
1
  module Wallaby
2
+ # @private
2
3
  # Partial renderer
3
4
  class PartialRenderer
4
5
  class << self
@@ -43,12 +44,18 @@ module Wallaby
43
44
 
44
45
  private
45
46
 
47
+ # Check and see if object and field_name are valid
48
+ # @param object [Object]
49
+ # @param field_name [String]
46
50
  def partial_arguments_check(object, field_name)
47
51
  raise ArgumentError, 'Field name is required.' if field_name.blank?
48
52
  raise ArgumentError, 'Object is not decorated.' \
49
53
  unless object.is_a? ResourceDecorator
50
54
  end
51
55
 
56
+ # Check and see if form and field_name are valid
57
+ # @param form [Object]
58
+ # @param field_name [String]
52
59
  def form_arguments_check(form, field_name)
53
60
  raise ArgumentError, 'Form is required.' if form.blank?
54
61
  raise ArgumentError, 'Field name is required.' if field_name.blank?
@@ -1,6 +1,11 @@
1
1
  module Wallaby
2
+ # @private
2
3
  # Prefix builder
3
4
  class PrefixesBuilder
5
+ # @param origin_prefixes [Array<string>] a list of all the prefixes
6
+ # @param controller_path [String] controller path
7
+ # @param resources_name [String] resources name
8
+ # @param params [ActionController::Parameters]
4
9
  def initialize(origin_prefixes, controller_path, resources_name, params)
5
10
  @origin_prefixes = origin_prefixes
6
11
  @controller_path = controller_path
@@ -8,6 +13,7 @@ module Wallaby
8
13
  @params = params
9
14
  end
10
15
 
16
+ # @return [Array<String>] a list of all the prefixes
11
17
  def build
12
18
  prefixes = minimal_prefixes
13
19
  prefixes.unshift mounted_prefix if resource_path != @controller_path
@@ -19,26 +25,35 @@ module Wallaby
19
25
 
20
26
  protected
21
27
 
28
+ # @return [Array<String>] a list of prefixes starting from wallaby
22
29
  def minimal_prefixes
23
30
  # this should contains only the current controller's path and wallaby path
24
31
  index = @origin_prefixes.index wallaby_path
25
32
  @origin_prefixes.slice 0..index
26
33
  end
27
34
 
35
+ # @return [String] a prefix of the mouted path.
36
+ # Given mounted path `admin`, and current resources `products`, it returns
37
+ # `admin/products`
28
38
  def mounted_prefix
29
39
  prefix = mounted_path.try(:slice, 1..-1) || ''
30
40
  prefix << SLASH unless prefix.empty?
31
41
  prefix << resource_path if resource_path
32
42
  end
33
43
 
44
+ # Consolidate action name (new/create/edit/update) as `form`
45
+ # @param params [ActionController::Parameters]
46
+ # @return [String]
34
47
  def build_suffix(params)
35
48
  Utils.to_partial_name params[:action]
36
49
  end
37
50
 
51
+ # @return [String] path of `wallaby/resources`
38
52
  def wallaby_path
39
53
  ResourcesController.controller_path
40
54
  end
41
55
 
56
+ # @return [String] the path that Wallaby has mounted to
42
57
  def mounted_path
43
58
  # TODO: need to find out if this will fail
44
59
  # when wallaby is mounted more than once on different namespace?
@@ -47,6 +62,9 @@ module Wallaby
47
62
  end
48
63
  end
49
64
 
65
+ # Convert the resources name
66
+ # (e.g. `namespace::products` to `namespace/products`)
67
+ # @return [String] a path of the resources
50
68
  def resource_path
51
69
  @resource_path ||= @resources_name.try :gsub, COLONS, SLASH
52
70
  end
@@ -1,9 +1,15 @@
1
1
  module Wallaby
2
2
  module Sorting
3
- # Turn a string `'name asc,id desc'` into sort hash
3
+ # @private
4
+ # Turn a string e.g.`'name asc,id desc'` into sort
5
+ # hash e.g.`{name: 'asc', id: 'desc'}`
4
6
  class HashBuilder
5
7
  SORT_REGEX = /(([^\s,]+)\s+(asc|desc))\s*,?\s*/i
6
8
 
9
+ # Turn a string e.g.`'name asc,id desc'` into sort
10
+ # hash e.g.`{name: 'asc', id: 'desc'}`
11
+ # @param sort_string [String]
12
+ # @return [Hash] { field_name => 'asc|desc' }
7
13
  def self.build(sort_string)
8
14
  ::ActiveSupport::HashWithIndifferentAccess.new.tap do |hash|
9
15
  (sort_string || EMPTY_STRING).scan SORT_REGEX do |_, key, order|
@@ -1,9 +1,13 @@
1
1
  module Wallaby
2
2
  module Sorting
3
+ # @private
3
4
  # Build the sorting link
4
5
  class LinkBuilder
5
6
  delegate :model_class, to: :@model_decorator
6
7
 
8
+ # @param model_decorator [Wallaby::ModelDecorator]
9
+ # @param params [ActionController::Parameters]
10
+ # @param helper [ActionView::Helpers]
7
11
  def initialize(model_decorator, params, helper)
8
12
  @model_decorator = model_decorator
9
13
  @params = params
@@ -35,7 +39,8 @@ module Wallaby
35
39
  @next_builder ||= NextBuilder.new @params, current_sort
36
40
  end
37
41
 
38
- # If it's non-association field or custom sorting field
42
+ # @return [Boolean]
43
+ # whether it's non-association field or custom sorting field
39
44
  def sortable?(field_name, metadata)
40
45
  @model_decorator.fields[field_name] && !metadata[:is_association] \
41
46
  || metadata[:sort_field_name]
@@ -1,15 +1,25 @@
1
1
  module Wallaby
2
2
  module Sorting
3
- # Pass field_name to generate sort param
3
+ # @private
4
+ # Pass field_name to generate sort param for its next sort order
5
+ # (e.g. from empty to `asc`, from `asc` to `desc`, from `desc` to empty)
4
6
  class NextBuilder
5
7
  ASC = 'asc'.freeze
6
8
  DESC = 'desc'.freeze
7
9
 
10
+ # @param params [ActionController::Parameters]
11
+ # @param hash [Hash, nil] sorting hash
8
12
  def initialize(params, hash = nil)
9
13
  @params = params
10
14
  @hash = hash || HashBuilder.build(params[:sort])
11
15
  end
12
16
 
17
+ # Update the `sort` parameter.
18
+ # @example for param `{sort: 'name asc'}`, it will update the parameters
19
+ # to `{sort: 'name desc'}`
20
+ # @param field_name [String] field name
21
+ # @return [ActionController::Parameters]
22
+ # updated parameters that update the sort order for given field
13
23
  def next_params(field_name)
14
24
  params = clean_params
15
25
  update params, :sort, complete_sorting_str_with(field_name)
@@ -18,10 +28,13 @@ module Wallaby
18
28
 
19
29
  protected
20
30
 
31
+ # @return [ActionController::Parameters] whitelisted parameters
21
32
  def clean_params
22
33
  @params.except :resources, :controller, :action
23
34
  end
24
35
 
36
+ # @param field_name [String] field name
37
+ # @return [String] a sort order string, e.g. `'name asc'`
25
38
  def complete_sorting_str_with(field_name)
26
39
  hash = @hash.except field_name
27
40
  current_sort = @hash[field_name]
@@ -30,13 +43,17 @@ module Wallaby
30
43
  rebuild_str_from hash
31
44
  end
32
45
 
46
+ # @param hash [Hash] sort order hash
47
+ # @return [String] a sort order string, e.g. `'name asc'`
33
48
  def rebuild_str_from(hash)
34
- hash.each_with_object '' do |(name, sort), str|
49
+ hash.each_with_object('') do |(name, sort), str|
35
50
  str << (str == EMPTY_STRING ? str : COMMA)
36
51
  str << name << SPACE << sort
37
52
  end
38
53
  end
39
54
 
55
+ # @param current [String, nil] current sort order
56
+ # @return [String, nil] next state of sort order
40
57
  def next_value_for(current)
41
58
  case current
42
59
  when ASC then DESC
@@ -45,6 +62,10 @@ module Wallaby
45
62
  end
46
63
  end
47
64
 
65
+ # Update the value for given key. Remove the key if value is blank
66
+ # @param hash [Hash] sort order hash
67
+ # @param key [String] key name
68
+ # @param value [Object, nil] value
48
69
  def update(hash, key, value)
49
70
  return hash.delete key if value.blank?
50
71
  hash[key] = value