url_params_manager 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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