url_params_manager 0.2.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ebb47a5d6d6197871e1283d76419797d8926ac5b
4
- data.tar.gz: d2db61a2aae5eb6eb74dcd4e17c088b2573cf15b
3
+ metadata.gz: bf37bee0652052fef68a8af38060681bbb126918
4
+ data.tar.gz: 7324ae64c1d098a22f95f0cd036eeabccf1cf694
5
5
  SHA512:
6
- metadata.gz: f45fc8c47d9272c81731f44ccb795e7a40e780a2b0a1cee7fe36c29db5179131424a5778edd85dab2f065e8a65ab30557f9dae6ccf426a002c43f004bd422ad6
7
- data.tar.gz: 8e47b4f4eb4a004835206bba032d636b5bfdbaba1c7012ab10f26a162179d354d933313ba8e9b4669835846d15a6c727bd52d4e2d90120c42e74b9fac3d2f56a
6
+ metadata.gz: d5cfbb60aef90c0f51730ce0a00b2f7ec4fb5542930ab1158abb235e04e5e2294a870cddbae126f016bafef55c7614f4f9c43646ee9a2a2fc22573977b156938
7
+ data.tar.gz: 7928e4654ce2ef213de734269972a6ae87d09a9d5c6a70af1d04e3056c564068dc75ae1cd867a31c42cc38e3ef3f266e0ae0838386d16eb61dc1837054fe3ad1
data/README.md CHANGED
@@ -84,7 +84,7 @@ url_to_filter_params = {
84
84
  cap: :capacity, # indexed => in URL
85
85
  some: :something, # not indexed => in querystring
86
86
  ndef: :non_default, # value with default
87
- page: :page,
87
+ # page: :page, # if it is the same and it is present in `indexed_url_params_order`, we don't need to pass it.
88
88
  }
89
89
 
90
90
  # Default values for the params
@@ -108,6 +108,14 @@ default_params = {
108
108
  The `filter_params` will be passed to that callable object before returning on `filters_from_url_params` calls
109
109
  as well as at the beginning of `url_args_from_filters` calls.
110
110
 
111
+ ### `always_lists_fields`
112
+
113
+ `UrlParamsManager` object can receive a `always_lists_fields` option with:
114
+
115
+ - a list of fields to be converted to Array (ie: `[:multi, :another_multi]`)
116
+ - a hash having the fields as keys and values either `true` (convert to Array) or a regex or string to use to `split`): (ie: `{ multi: true, another_multi: ',' }`)
117
+
118
+
111
119
  ### Building URLs
112
120
 
113
121
  ```ruby
@@ -181,6 +189,73 @@ With a URL like we built before: `'/search/feat-helipad/feat-swimming-pool/cap-2
181
189
  }
182
190
  ```
183
191
 
192
+ ## With Position-Defined params
193
+
194
+ Sometimes we want the first url parts to have a preassigned meaning. For example, we may want something like
195
+ `/search/my-location/my-category/page-3`, where the first 2 params will be interpreted as `params[:locations]` and `params[:categories]`.
196
+
197
+ With the `position_defined_url_params` option we can pass a hash with the config for these kind of params.
198
+
199
+ Important observations:
200
+
201
+ - the position-defined params hash is **ordered**. The position will be determined by the order of the keys of the config hash.
202
+ - all position defined param needs a **placeholder**. This will be:
203
+ - ignored when translating from url to filter
204
+ - added to the url in its place if a posterior param is present, so we can respect the position order.
205
+ - when the param has multiple values, instead of adding them in separate parts, we'll concatenate them using a separator:
206
+ - it can be passed as `multiple_separator` option
207
+ - if not passed, it will use a default separator `--`.
208
+ - when recognising params from the url, we'll **stop looking for position defined params when we first recognise a prefix of a normal param**
209
+
210
+ The last part is important because it allows us to combine normal indexed params with position based params.
211
+
212
+ Example:
213
+
214
+ ```ruby
215
+ url_to_filter_params = {}
216
+ indexed_url_params_order = [:by_prefix, :page]
217
+ position_defined_url_params = {
218
+ locations: { placeholder: 'all-locations', multiple_separator: '--' },
219
+ categories: { placeholder: 'all-categories', multiple_separator: '_AND_' },
220
+ themes: { placeholder: 'all-themes', multiple_separator: '--' },
221
+ offers: { placeholder: 'all-offers' } # uses default separator
222
+ }
223
+
224
+ @upm = UrlParamsManager.for url_to_filter_params: url_to_filter_params, # Param Name Map
225
+ indexed_url_params_order: indexed_url_params_order, # Indexed Params list
226
+ position_defined_url_params: position_defined_url_params, # Position Defined params config
227
+ app_url_helpers: Rails.application.routes.url_helpers, # Object to receive the URL's Path calls (usually Rails URL Helpers)
228
+
229
+ # FILTERS -> URL
230
+ pars = {
231
+ categories: ['my-cat', 'second-cat'],
232
+ offers: 'my-offer',
233
+ by_prefix_filter: ['hey', 'you'],
234
+ some: ['other', 'another'],
235
+ page: '2'
236
+ }
237
+
238
+ # it adds the needed placeholders
239
+ expected_path = @upm.my_search_path(pars)
240
+ # => '/search/all-locations/my-cat_AND_second-cat/all-themes/my-offer/by_prefix-hey/by_prefix-you/page-2?some%5B%5D=another&some%5B%5D=other'
241
+
242
+
243
+ # example with less position based because we encounter one normal arg (page)
244
+ url_params = {
245
+ filters: 'all-locations/my-cat_AND_second-cat/one-theme--second-theme/page-2'
246
+ }
247
+ expected_params = filters_from_url_params(url_params)
248
+ # => {
249
+ categories: ['my-cat', 'second-cat'],
250
+ themes: ['one-theme', 'second-theme'],
251
+ page: '2'
252
+ }
253
+
254
+ # `all-locations`, being `locations` placeholder, is ignored
255
+ # `page-2` is recognised as `page`, not as `offers`.
256
+ ```
257
+
258
+
184
259
  ## Installation
185
260
 
186
261
  Add this line to your application's Gemfile:
@@ -216,3 +291,12 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
216
291
  ### v.0.2.0
217
292
 
218
293
  - added `filter_params_treatment` option.
294
+
295
+ ### v.0.3.0: YANKED!
296
+
297
+ - added `position_defined_url_params` option.
298
+
299
+
300
+ ### v.0.4.0
301
+ - fixed typo in `position_defined_url_params` (it was position_defined_url_parms)
302
+ - added `always_lists_fields` option.
@@ -8,12 +8,17 @@ module UrlParamsManager
8
8
  indexed_url_params_order: nil,
9
9
  app_url_helpers: nil,
10
10
  default_params: {},
11
+ always_lists_fields: {},
12
+ position_defined_url_params: nil,
11
13
  filter_params_treatment: ->(filter_params) { filter_params }
12
14
  )
13
- Service.new url_to_filter_params: url_to_filter_params,
14
- indexed_url_params_order: indexed_url_params_order,
15
- app_url_helpers: app_url_helpers,
16
- default_params: default_params,
17
- filter_params_treatment: filter_params_treatment
15
+ Service.new url_to_filter_params: url_to_filter_params,
16
+ indexed_url_params_order: indexed_url_params_order,
17
+ app_url_helpers: app_url_helpers,
18
+ default_params: default_params,
19
+ filter_params_treatment: filter_params_treatment,
20
+ position_defined_url_params: position_defined_url_params,
21
+ always_lists_fields: always_lists_fields
22
+
18
23
  end
19
24
  end
@@ -1,24 +1,36 @@
1
1
  module UrlParamsManager
2
2
  class Service
3
+ EMPTY_STRING = ''.freeze
4
+ PREFIX_SEPARATOR = '-'.freeze
5
+ DEFAULT_PLACEHOLDER = '_'.freeze
6
+ DEFAULT_MULTIPLE_SEPARATOR_FOR_POSITION = '--'.freeze
7
+ DEFAULT_FILTER_PARAMS_TREATMENT = ->(filter_params) { filter_params }
8
+
3
9
  attr_reader :app_url_helpers,
4
10
  :url_to_filter_params,
5
11
  :indexed_url_params_order,
6
12
  :filter_to_url_params,
7
13
  :default_params,
8
- :filter_params_treatment
14
+ :position_defined_url_params,
15
+ :filter_params_treatment,
16
+ :always_lists_fields
9
17
 
10
18
  def initialize(url_to_filter_params: nil,
11
19
  indexed_url_params_order: nil,
12
20
  app_url_helpers: nil,
13
21
  default_params: {},
14
- filter_params_treatment: ->(filter_params) { filter_params }
22
+ always_lists_fields: {},
23
+ position_defined_url_params: nil,
24
+ filter_params_treatment: DEFAULT_FILTER_PARAMS_TREATMENT
15
25
  )
16
- @url_to_filter_params = url_to_filter_params
17
- @filter_to_url_params = url_to_filter_params.invert
18
- @indexed_url_params_order = indexed_url_params_order
19
- @app_url_helpers = app_url_helpers
20
- @default_params = default_params
21
- @filter_params_treatment = filter_params_treatment
26
+ @url_to_filter_params = url_to_filter_params
27
+ @filter_to_url_params = url_to_filter_params.invert
28
+ @indexed_url_params_order = indexed_url_params_order.map &:to_s
29
+ @app_url_helpers = app_url_helpers
30
+ @default_params = default_params
31
+ @position_defined_url_params = position_defined_url_params.presence || {}
32
+ @filter_params_treatment = filter_params_treatment
33
+ @always_lists_fields = prepare_always_lists_fields(always_lists_fields)
22
34
  end
23
35
 
24
36
  # for url/path methods from filter args
@@ -30,21 +42,11 @@ module UrlParamsManager
30
42
  def filters_from_url_params(params)
31
43
  params = params.to_h.symbolize_keys
32
44
 
33
- filters = {}.merge default_params
34
-
35
- params.delete(:filters).to_s.split('/').each do |part|
36
- filter, value = recognise_prefix(part)
37
- if filters.has_key? filter
38
- filters[filter] = Array(filters[filter])
39
- filters[filter] << value
40
- else
41
- filters[filter] = value
42
- end
43
- end
45
+ filters = unfold_filters(params)
44
46
 
45
47
  pars = filters.merge querystring_to_filters(params)
46
48
 
47
- filter_params_treatment.call pars
49
+ filter_params(pars)
48
50
  end
49
51
 
50
52
  def querystring_to_filters(querystring_params)
@@ -56,58 +58,67 @@ module UrlParamsManager
56
58
  end
57
59
 
58
60
  private
61
+
62
+ #################
63
+ # FILTER -> URL #
64
+ #################
65
+
59
66
  def path_method(method, filter_args)
60
67
  url_args = url_args_from_filters(filter_args)
61
68
  app_url_helpers.send(method, url_args)
62
69
  end
63
70
 
64
- EMPTY_STRING = ''.freeze
65
-
66
- def recognise_prefix(url_part)
67
- url_to_filter_params.each do |url_prefix, filter|
68
- pref = "#{url_prefix}-"
69
- if url_part.start_with? pref
70
- return [filter, url_part.sub(pref, EMPTY_STRING)]
71
- end
72
- end
73
-
74
- raise UnrecognisedPrefixError, "url part: #{url_part}"
75
- end
76
71
 
77
72
  def url_args_from_filters(filter_args)
78
- filter_pars = filter_params_treatment.call filter_args
73
+ filter_pars = filter_params(filter_args)
79
74
  valid_filter_args = remove_defaults(filter_pars)
80
- bare_args = translate_filter_keys(valid_filter_args)
81
75
 
82
- in_uri_args = bare_args.select { |(k, _)| indexed_url_params_order.include? k }
83
- query_string_args = bare_args.reject { |(k, _)| indexed_url_params_order.include? k }
76
+ bare_args = translate_filter_keys(valid_filter_args)
77
+
78
+ in_uri_args = bare_args.select { |(url_param, _)| url_param_in_path?(url_param) }
79
+ query_string_args = bare_args.reject { |(url_param, _)| url_param_in_path?(url_param) }
80
+
81
+ # adding placeholders
82
+ with_placeholders = add_placeholders in_uri_args
84
83
 
85
84
  # sorting indexed part
86
- sorted_uri_args = sort_uri_filters(in_uri_args)
85
+ sorted_uri_args = sort_uri_filters with_placeholders
87
86
 
88
- #converting into string
89
- filters_uri_part = generate_uri_part(sorted_uri_args)
87
+ # converting into string
88
+ filters_uri_part = generate_uri_part sorted_uri_args
90
89
 
91
90
  # query string args + filter param for the urlpath helper methods
92
91
  query_string_args.merge filters: filters_uri_part.join('/')
93
92
  end
94
93
 
95
- def sort_uri_filters(in_uri_args)
96
- keys_sorted = Hash[
97
- in_uri_args.to_a.sort_by do |k, _|
98
- @indexed_url_params_order.index(k)
94
+ def add_placeholders(sorted_uri_args)
95
+ position_based_keys = position_defined_url_params.keys.reverse
96
+ need_placeholder = false
97
+
98
+ position_based_keys.inject(sorted_uri_args) do |args, key|
99
+ options = position_defined_url_params[key] || {}
100
+ need_placeholder = need_placeholder || args[key].present?
101
+
102
+ if need_placeholder && args[key].blank?
103
+ args[key] = options[:placeholder] || DEFAULT_PLACEHOLDER
99
104
  end
100
- ]
101
105
 
102
- Hash[
103
- keys_sorted.map do |k, v|
104
- sorted_v = v.respond_to?(:sort) ? v.sort : v
105
- [k, sorted_v]
106
+ args
107
+ end
108
+ end
109
+
110
+ def remove_defaults(filter_args)
111
+ default_params.each do |k, v|
112
+ next unless filter_args.key?(k) && filter_args[k].present?
113
+ if filter_args[k] == v || Array(filter_args[k]).sort == Array(v).sort
114
+ filter_args.delete k
106
115
  end
107
- ]
116
+ end
108
117
 
118
+ filter_args
109
119
  end
110
120
 
121
+
111
122
  def translate_filter_keys(filter_args)
112
123
  Hash[
113
124
  filter_args.map do |k, v|
@@ -119,8 +130,64 @@ module UrlParamsManager
119
130
  ]
120
131
  end
121
132
 
133
+ def url_param_in_path?(url_param)
134
+ indexed_url_params_order.include?(url_param.to_s) || position_defined_url_params.key?(url_param)
135
+ end
136
+
137
+ def sort_uri_filters(in_uri_args)
138
+ position_uri_args = in_uri_args.select { |k, _| position_defined_url_params.key? k }
139
+ prefix_uri_args = in_uri_args.reject { |k, _| position_defined_url_params.key? k }
140
+
141
+ final_list = []
142
+
143
+ final_list = final_list.concat sort_uri_filters_by_position(position_uri_args)
144
+ final_list = final_list.concat sort_uri_filters_by_prefix(prefix_uri_args)
145
+
146
+ final_list.map do |k, v|
147
+ sorted_v = v.respond_to?(:sort) ? v.sort : v
148
+ [k, sorted_v]
149
+ end.to_h
150
+ end
151
+
152
+ def sort_uri_filters_by_position(position_uri_args)
153
+ position_keys = position_defined_url_params.keys
154
+ position_uri_args.to_a.sort_by do |k, _|
155
+ position_keys.index(k)
156
+ end
157
+ end
158
+
159
+ def sort_uri_filters_by_prefix(prefix_uri_args)
160
+ prefix_uri_args.to_a.sort_by do |k, _|
161
+ indexed_url_params_order.index(k.to_s)
162
+ end
163
+ end
164
+
122
165
  def generate_uri_part(in_uri_args)
123
- in_uri_args.inject([]) do |uri, (key, value)|
166
+ position_uri_args = in_uri_args.select { |k, _| position_defined_url_params.key? k }
167
+ prefix_uri_args = in_uri_args.reject { |k, _| position_defined_url_params.key? k }
168
+
169
+ generate_uri_parts_by_position(position_uri_args).concat generate_uri_parts_by_prefix(prefix_uri_args)
170
+ end
171
+
172
+ def generate_uri_parts_by_position(position_uri_args)
173
+ position_uri_args.inject([]) do |uri, (key, value)|
174
+ options = position_defined_url_params[key]
175
+
176
+ prefix = options[:prefix].present? ? "#{options[:prefix]}-" : EMPTY_STRING
177
+ uri_values = []
178
+ Array(value).each do |v|
179
+ uri_values << "#{prefix}#{v}"
180
+ end
181
+
182
+ separator = get_separator_from_position_options(options)
183
+ uri << uri_values.join(separator)
184
+
185
+ uri
186
+ end
187
+ end
188
+
189
+ def generate_uri_parts_by_prefix(prefix_uri_args)
190
+ prefix_uri_args.inject([]) do |uri, (key, value)|
124
191
  Array(value).each do |v|
125
192
  uri << "#{key}-#{v}"
126
193
  end
@@ -128,15 +195,127 @@ module UrlParamsManager
128
195
  end
129
196
  end
130
197
 
131
- def remove_defaults(filter_args)
132
- default_params.each do |k, v|
133
- next unless filter_args.key?(k) && filter_args[k].present?
134
- if filter_args[k] == v || Array(filter_args[k]).sort == Array(v).sort
135
- filter_args.delete k
198
+
199
+ #################
200
+ # URL -> FILTER #
201
+ #################
202
+
203
+
204
+ def unfold_filters(params)
205
+ filters = {}.merge default_params
206
+
207
+ raw = params.delete(:filters).to_s.split('/')
208
+
209
+ used_positions = unfold_filters_by_position(filters, raw)
210
+ unfold_filters_by_prefix(filters, raw.drop(used_positions))
211
+
212
+ filters
213
+ end
214
+
215
+ def unfold_filters_by_position(filters, raw)
216
+
217
+ # the moment we recognise one param with a prefix based, we assume that the position based have stopped
218
+ prefix_based_recognised = false
219
+ position_defined_raw = raw.inject([]) do |list, url_part|
220
+ prefix_based_recognised ||= recognised_prefix? url_part
221
+ list << url_part unless prefix_based_recognised
222
+ list
223
+ end
224
+
225
+ available_position_params = position_defined_url_params.to_a.take(position_defined_raw.size)
226
+ used_positions = 0
227
+
228
+ available_position_params.each_with_index do |(key, options), index|
229
+ used_positions += 1
230
+ value = raw[index]
231
+
232
+ next if value == options[:placeholder]
233
+
234
+ separator = get_separator_from_position_options(options)
235
+ values = value.split(separator)
236
+
237
+ filters[key] = values.size > 1 ? values : values.first
238
+ end
239
+
240
+ used_positions
241
+ end
242
+
243
+ def unfold_filters_by_prefix(filters, raw)
244
+ raw.each do |part|
245
+ filter, value = recognise_prefix(part)
246
+ if filters.has_key? filter
247
+ filters[filter] = Array(filters[filter])
248
+ filters[filter] << value
249
+ else
250
+ filters[filter] = value
136
251
  end
137
252
  end
253
+ end
138
254
 
139
- filter_args
255
+ def recognised_prefix?(url_part)
256
+ recognise_prefix(url_part, raise_if_unrecognised: false).present?
140
257
  end
258
+
259
+ def recognise_prefix(url_part, raise_if_unrecognised: true)
260
+ url_to_filter_params.each do |url_prefix, filter|
261
+ pref = "#{url_prefix}#{PREFIX_SEPARATOR}"
262
+ if url_part.start_with? pref
263
+ return [filter, url_part.sub(pref, EMPTY_STRING)]
264
+ end
265
+ end
266
+
267
+ # check if it's the same prefix -> if it is in the indexed parts
268
+ url_part_divided = url_part.split(PREFIX_SEPARATOR)
269
+ param = url_part_divided.first
270
+ if indexed_url_params_order.include?(param.to_s)
271
+ return [param.to_sym, url_part_divided.drop(1).join(PREFIX_SEPARATOR)]
272
+ end
273
+
274
+ if raise_if_unrecognised
275
+ raise UnrecognisedPrefixError, "url part: #{url_part}"
276
+ else
277
+ nil
278
+ end
279
+ end
280
+
281
+
282
+ ##########
283
+ # COMMON #
284
+ ##########
285
+
286
+ def get_separator_from_position_options(options)
287
+ options[:multiple_separator].presence || DEFAULT_MULTIPLE_SEPARATOR_FOR_POSITION
288
+ end
289
+
290
+ def filter_params(filter_args)
291
+ filter_args = filter_args.deep_dup
292
+ convert_to_lists(filter_args)
293
+ filter_params_treatment.call filter_args
294
+ end
295
+
296
+ def convert_to_lists(filter_args)
297
+ always_lists_fields.each do |field, config|
298
+ if filter_args.key? field
299
+ case config
300
+ when String, Regexp
301
+ filter_args[field] = filter_args[field].to_s.split(config)
302
+ else
303
+ filter_args[field] = Array(filter_args[field])
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ def prepare_always_lists_fields(given)
310
+ case given
311
+ when Array
312
+ given.map { |a| [a, true] }.to_h
313
+ when Hash
314
+ given
315
+ else
316
+ { given.to_sym => true }
317
+ end
318
+ end
319
+
141
320
  end
142
321
  end
@@ -1,3 +1,3 @@
1
1
  module UrlParamsManager
2
- VERSION = '0.2.0'
2
+ VERSION = '0.4.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: url_params_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eduardo Turiño
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2015-10-09 00:00:00.000000000 Z
12
+ date: 2015-11-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport