uchi 0.1.3 → 0.1.5

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 (283) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +222 -0
  3. data/app/assets/config/uchi_manifest.js +2 -0
  4. data/app/assets/javascripts/uchi/application.js +6095 -0
  5. data/app/assets/javascripts/uchi.js +4 -0
  6. data/app/assets/stylesheets/uchi/application.css +3971 -0
  7. data/app/assets/stylesheets/uchi/uchi.css +17 -0
  8. data/app/assets/tailwind/uchi.css +21 -0
  9. data/app/components/flowbite/breadcrumb.rb +33 -0
  10. data/app/components/flowbite/breadcrumb_home.rb +26 -0
  11. data/app/components/flowbite/breadcrumb_item/current.rb +33 -0
  12. data/app/components/flowbite/breadcrumb_item/first.rb +35 -0
  13. data/app/components/flowbite/breadcrumb_item.rb +48 -0
  14. data/app/components/flowbite/breadcrumb_separator.rb +30 -0
  15. data/app/components/flowbite/button/outline.rb +22 -0
  16. data/app/components/flowbite/button/pill.rb +40 -0
  17. data/app/components/flowbite/button.rb +92 -0
  18. data/app/components/flowbite/card.rb +45 -0
  19. data/app/components/flowbite/input/checkbox.rb +73 -0
  20. data/app/components/flowbite/input/date.rb +11 -0
  21. data/app/components/flowbite/input/date_time.rb +11 -0
  22. data/app/components/flowbite/input/email.rb +12 -0
  23. data/app/components/flowbite/input/field.rb +117 -0
  24. data/app/components/flowbite/input/file.rb +30 -0
  25. data/app/components/flowbite/input/hint.rb +57 -0
  26. data/app/components/flowbite/input/label.rb +82 -0
  27. data/app/components/flowbite/input/number.rb +11 -0
  28. data/app/components/flowbite/input/password.rb +11 -0
  29. data/app/components/flowbite/input/phone.rb +11 -0
  30. data/app/components/flowbite/input/radio_button.rb +50 -0
  31. data/app/components/flowbite/input/select.rb +49 -0
  32. data/app/components/flowbite/input/textarea.rb +42 -0
  33. data/app/components/flowbite/input/url.rb +12 -0
  34. data/app/components/flowbite/input/validation_error.rb +11 -0
  35. data/app/components/flowbite/input_field/checkbox.html.erb +14 -0
  36. data/app/components/flowbite/input_field/checkbox.rb +54 -0
  37. data/app/components/flowbite/input_field/date.rb +13 -0
  38. data/app/components/flowbite/input_field/date_time.rb +13 -0
  39. data/app/components/flowbite/input_field/email.rb +13 -0
  40. data/app/components/flowbite/input_field/file.rb +13 -0
  41. data/app/components/flowbite/input_field/input_field.html.erb +8 -0
  42. data/app/components/flowbite/input_field/number.rb +13 -0
  43. data/app/components/flowbite/input_field/password.rb +13 -0
  44. data/app/components/flowbite/input_field/phone.rb +13 -0
  45. data/app/components/flowbite/input_field/radio_button.html.erb +14 -0
  46. data/app/components/flowbite/input_field/radio_button.rb +86 -0
  47. data/app/components/flowbite/input_field/select.rb +31 -0
  48. data/app/components/flowbite/input_field/text.rb +8 -0
  49. data/app/components/flowbite/input_field/textarea.rb +13 -0
  50. data/app/components/flowbite/input_field/url.rb +13 -0
  51. data/app/components/flowbite/input_field.rb +192 -0
  52. data/app/components/flowbite/link.rb +21 -0
  53. data/app/components/flowbite/style.rb +13 -0
  54. data/app/components/flowbite/toast/icon.html.erb +5 -0
  55. data/app/components/flowbite/toast/icon.rb +57 -0
  56. data/app/components/flowbite/toast/toast.html.erb +11 -0
  57. data/app/components/flowbite/toast.rb +34 -0
  58. data/app/components/uchi/field/base.rb +57 -0
  59. data/app/components/uchi/field/belongs_to/edit.html.erb +1 -0
  60. data/app/components/uchi/field/belongs_to/index.html.erb +1 -0
  61. data/app/components/uchi/field/belongs_to/show.html.erb +5 -0
  62. data/app/components/uchi/field/belongs_to.rb +112 -0
  63. data/app/components/uchi/field/blank/edit.html.erb +1 -0
  64. data/app/components/uchi/field/blank/index.html.erb +1 -0
  65. data/app/components/uchi/field/blank/show.html.erb +1 -0
  66. data/app/components/uchi/field/blank.rb +16 -0
  67. data/app/components/uchi/field/boolean/edit.html.erb +1 -0
  68. data/app/components/uchi/field/boolean/index.html.erb +9 -0
  69. data/app/components/uchi/field/boolean/show.html.erb +9 -0
  70. data/app/components/uchi/field/boolean.rb +27 -0
  71. data/app/components/uchi/field/date/edit.html.erb +1 -0
  72. data/app/components/uchi/field/date/index.html.erb +1 -0
  73. data/app/components/uchi/field/date/show.html.erb +1 -0
  74. data/app/components/uchi/field/date.rb +27 -0
  75. data/app/components/uchi/field/date_time/edit.html.erb +1 -0
  76. data/app/components/uchi/field/date_time/index.html.erb +1 -0
  77. data/app/components/uchi/field/date_time/show.html.erb +1 -0
  78. data/app/components/uchi/field/date_time.rb +27 -0
  79. data/app/components/uchi/field/file/edit.html.erb +1 -0
  80. data/app/components/uchi/field/file/index.html.erb +6 -0
  81. data/app/components/uchi/field/file/show.html.erb +8 -0
  82. data/app/components/uchi/field/file.rb +37 -0
  83. data/app/components/uchi/field/has_and_belongs_to_many/edit.html.erb +9 -0
  84. data/app/components/uchi/field/has_and_belongs_to_many/index.html.erb +1 -0
  85. data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +28 -0
  86. data/app/components/uchi/field/has_and_belongs_to_many.rb +131 -0
  87. data/app/components/uchi/field/has_many/edit.html.erb +1 -0
  88. data/app/components/uchi/field/has_many/index.html.erb +1 -0
  89. data/app/components/uchi/field/has_many/show.html.erb +28 -0
  90. data/app/components/uchi/field/has_many.rb +107 -0
  91. data/app/components/uchi/field/id/index.html.erb +4 -0
  92. data/app/components/uchi/field/id/show.html.erb +4 -0
  93. data/app/components/uchi/field/id.rb +26 -0
  94. data/app/components/uchi/field/image/edit.html.erb +1 -0
  95. data/app/components/uchi/field/image/index.html.erb +6 -0
  96. data/app/components/uchi/field/image/show.html.erb +6 -0
  97. data/app/components/uchi/field/image.rb +38 -0
  98. data/app/components/uchi/field/number/edit.html.erb +1 -0
  99. data/app/components/uchi/field/number/index.html.erb +1 -0
  100. data/app/components/uchi/field/number/show.html.erb +1 -0
  101. data/app/components/uchi/field/number.rb +32 -0
  102. data/app/components/uchi/field/string/edit.html.erb +1 -0
  103. data/app/components/uchi/field/string/index.html.erb +1 -0
  104. data/app/components/uchi/field/string/show.html.erb +1 -0
  105. data/app/components/uchi/field/string.rb +33 -0
  106. data/app/components/uchi/field/text/edit.html.erb +1 -0
  107. data/app/components/uchi/field/text/index.html.erb +1 -0
  108. data/app/components/uchi/field/text/show.html.erb +1 -0
  109. data/app/components/uchi/field/text.rb +38 -0
  110. data/app/components/uchi/ui/breadcrumb/breadcrumb.html.erb +13 -0
  111. data/app/components/uchi/ui/breadcrumb.rb +14 -0
  112. data/app/components/uchi/ui/form/footer/footer.html.erb +5 -0
  113. data/app/components/uchi/ui/form/footer.rb +15 -0
  114. data/app/components/uchi/ui/form/input/collection_checkboxes.html.erb +32 -0
  115. data/app/components/uchi/ui/form/input/collection_checkboxes.rb +125 -0
  116. data/app/components/uchi/ui/frame/frame.html.erb +3 -0
  117. data/app/components/uchi/ui/frame.rb +10 -0
  118. data/app/components/uchi/ui/index/records_table/records_table.html.erb +67 -0
  119. data/app/components/uchi/ui/index/records_table/search_form/search_form.html.erb +21 -0
  120. data/app/components/uchi/ui/index/records_table/search_form.rb +49 -0
  121. data/app/components/uchi/ui/index/records_table.rb +29 -0
  122. data/app/components/uchi/ui/index/turbo_frame.rb +50 -0
  123. data/app/components/uchi/ui/page_header/page_header.html.erb +24 -0
  124. data/app/components/uchi/ui/page_header.rb +18 -0
  125. data/app/components/uchi/ui/pagination/current_link.html.erb +3 -0
  126. data/app/components/uchi/ui/pagination/current_link.rb +10 -0
  127. data/app/components/uchi/ui/pagination/gap.html.erb +3 -0
  128. data/app/components/uchi/ui/pagination/gap.rb +10 -0
  129. data/app/components/uchi/ui/pagination/item.rb +24 -0
  130. data/app/components/uchi/ui/pagination/link.html.erb +3 -0
  131. data/app/components/uchi/ui/pagination/link.rb +10 -0
  132. data/app/components/uchi/ui/pagination/next_link.html.erb +8 -0
  133. data/app/components/uchi/ui/pagination/next_link.rb +22 -0
  134. data/app/components/uchi/ui/pagination/pagination.html.erb +15 -0
  135. data/app/components/uchi/ui/pagination/previous_link.html.erb +8 -0
  136. data/app/components/uchi/ui/pagination/previous_link.rb +22 -0
  137. data/app/components/uchi/ui/pagination.rb +48 -0
  138. data/app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb +14 -0
  139. data/app/components/uchi/ui/show/attribute_fields.rb +18 -0
  140. data/app/components/uchi/ui/spinner/spinner.html.erb +7 -0
  141. data/app/components/uchi/ui/spinner.rb +15 -0
  142. data/app/controllers/uchi/application_controller.rb +4 -0
  143. data/app/controllers/uchi/controller.rb +13 -0
  144. data/app/controllers/uchi/repository_controller.rb +166 -0
  145. data/app/helpers/uchi/application_helper.rb +17 -0
  146. data/app/jobs/uchi/application_job.rb +4 -0
  147. data/app/mailers/uchi/application_mailer.rb +6 -0
  148. data/app/views/layouts/uchi/_flash_messages.html.erb +10 -0
  149. data/app/views/layouts/uchi/application.html.erb +33 -0
  150. data/app/views/uchi/repository/edit.html.erb +40 -0
  151. data/app/views/uchi/repository/index.html.erb +49 -0
  152. data/app/views/uchi/repository/new.html.erb +37 -0
  153. data/app/views/uchi/repository/show.html.erb +41 -0
  154. data/lib/generators/uchi/controller/controller_generator.rb +16 -0
  155. data/lib/generators/uchi/controller/templates/controller.rb.tt +11 -0
  156. data/lib/generators/uchi/install/install_generator.rb +13 -0
  157. data/lib/generators/uchi/repository/repository_generator.rb +16 -0
  158. data/lib/generators/uchi/repository/templates/repository.rb.tt +11 -0
  159. data/lib/tasks/uchi_tasks.rake +4 -0
  160. data/lib/uchi/application_record.rb +5 -0
  161. data/lib/uchi/engine.rb +24 -0
  162. data/lib/uchi/field/configuration.rb +142 -0
  163. data/lib/uchi/field.rb +80 -0
  164. data/lib/uchi/i18n.rb +13 -0
  165. data/lib/uchi/pagination/controller.rb +26 -0
  166. data/lib/uchi/pagination/page.rb +20 -0
  167. data/lib/uchi/pagy/LICENSE.txt +21 -0
  168. data/lib/uchi/pagy/classes/exceptions.rb +35 -0
  169. data/lib/uchi/pagy/classes/offset/offset.rb +56 -0
  170. data/lib/uchi/pagy/classes/request.rb +38 -0
  171. data/lib/uchi/pagy/modules/abilities/configurable.rb +38 -0
  172. data/lib/uchi/pagy/modules/abilities/linkable.rb +62 -0
  173. data/lib/uchi/pagy/modules/abilities/rangeable.rb +17 -0
  174. data/lib/uchi/pagy/modules/abilities/shiftable.rb +14 -0
  175. data/lib/uchi/pagy/modules/console.rb +40 -0
  176. data/lib/uchi/pagy/toolbox/helpers/loader.rb +19 -0
  177. data/lib/uchi/pagy/toolbox/helpers/page_url.rb +25 -0
  178. data/lib/uchi/pagy/toolbox/paginators/method.rb +21 -0
  179. data/lib/uchi/pagy/toolbox/paginators/offset.rb +35 -0
  180. data/lib/uchi/pagy.rb +60 -0
  181. data/lib/uchi/repository/routes.rb +62 -0
  182. data/lib/uchi/repository/translate.rb +368 -0
  183. data/lib/uchi/repository.rb +156 -0
  184. data/lib/uchi/sort_order.rb +35 -0
  185. data/lib/uchi/version.rb +5 -0
  186. data/lib/uchi.rb +18 -0
  187. data/uchi.gemspec +35 -0
  188. metadata +189 -97
  189. data/.github/dependabot.yml +0 -17
  190. data/.github/workflows/build.yml +0 -23
  191. data/.github/workflows/lint.yml +0 -30
  192. data/CHANGELOG.md +0 -29
  193. data/docs/fields.md +0 -82
  194. data/docs/repositories.md +0 -63
  195. data/package.json +0 -31
  196. data/sig/uchi.rbs +0 -4
  197. data/test/components/uchi/field/belongs_to_test.rb +0 -134
  198. data/test/components/uchi/field/blank_test.rb +0 -119
  199. data/test/components/uchi/field/boolean_test.rb +0 -163
  200. data/test/components/uchi/field/date_test.rb +0 -163
  201. data/test/components/uchi/field/date_time_test.rb +0 -152
  202. data/test/components/uchi/field/has_and_belongs_to_many_test.rb +0 -144
  203. data/test/components/uchi/field/has_many_test.rb +0 -138
  204. data/test/components/uchi/field/id_test.rb +0 -113
  205. data/test/components/uchi/field/number_test.rb +0 -163
  206. data/test/components/uchi/field/string_test.rb +0 -159
  207. data/test/components/uchi/field/text_test.rb +0 -160
  208. data/test/components/uchi/ui/form/input/collection_checkboxes_test.rb +0 -171
  209. data/test/controllers/uchi/authors_controller_test.rb +0 -120
  210. data/test/controllers/uchi/repository_controller_test.rb +0 -93
  211. data/test/controllers/uchi/scoped_repository_controller_test.rb +0 -73
  212. data/test/dummy/Rakefile +0 -6
  213. data/test/dummy/app/assets/images/.keep +0 -0
  214. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  215. data/test/dummy/app/controllers/application_controller.rb +0 -4
  216. data/test/dummy/app/controllers/concerns/.keep +0 -0
  217. data/test/dummy/app/controllers/uchi/authors_controller.rb +0 -7
  218. data/test/dummy/app/controllers/uchi/books_controller.rb +0 -7
  219. data/test/dummy/app/controllers/uchi/titles_controller.rb +0 -7
  220. data/test/dummy/app/helpers/application_helper.rb +0 -2
  221. data/test/dummy/app/jobs/application_job.rb +0 -7
  222. data/test/dummy/app/mailers/application_mailer.rb +0 -4
  223. data/test/dummy/app/models/application_record.rb +0 -3
  224. data/test/dummy/app/models/author.rb +0 -5
  225. data/test/dummy/app/models/book.rb +0 -4
  226. data/test/dummy/app/models/concerns/.keep +0 -0
  227. data/test/dummy/app/models/title.rb +0 -3
  228. data/test/dummy/app/uchi/repositories/author.rb +0 -20
  229. data/test/dummy/app/uchi/repositories/book.rb +0 -16
  230. data/test/dummy/app/uchi/repositories/title.rb +0 -17
  231. data/test/dummy/app/views/layouts/application.html.erb +0 -27
  232. data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
  233. data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
  234. data/test/dummy/app/views/pwa/manifest.json.erb +0 -22
  235. data/test/dummy/app/views/pwa/service-worker.js +0 -26
  236. data/test/dummy/bin/dev +0 -2
  237. data/test/dummy/bin/rails +0 -4
  238. data/test/dummy/bin/rake +0 -4
  239. data/test/dummy/bin/setup +0 -34
  240. data/test/dummy/config/application.rb +0 -29
  241. data/test/dummy/config/boot.rb +0 -5
  242. data/test/dummy/config/cable.yml +0 -10
  243. data/test/dummy/config/database.yml +0 -32
  244. data/test/dummy/config/environment.rb +0 -5
  245. data/test/dummy/config/environments/development.rb +0 -69
  246. data/test/dummy/config/environments/production.rb +0 -89
  247. data/test/dummy/config/environments/test.rb +0 -53
  248. data/test/dummy/config/initializers/content_security_policy.rb +0 -25
  249. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -8
  250. data/test/dummy/config/initializers/inflections.rb +0 -16
  251. data/test/dummy/config/locales/da.yml +0 -52
  252. data/test/dummy/config/locales/en.yml +0 -31
  253. data/test/dummy/config/puma.rb +0 -38
  254. data/test/dummy/config/routes.rb +0 -9
  255. data/test/dummy/config/storage.yml +0 -34
  256. data/test/dummy/config.ru +0 -6
  257. data/test/dummy/db/migrate/20251002183635_create_authors.rb +0 -11
  258. data/test/dummy/db/migrate/20251005131726_create_books.rb +0 -9
  259. data/test/dummy/db/migrate/20251005131811_create_titles.rb +0 -11
  260. data/test/dummy/db/migrate/20251031140958_add_author_books_join_table.rb +0 -9
  261. data/test/dummy/db/schema.rb +0 -44
  262. data/test/dummy/log/.keep +0 -0
  263. data/test/dummy/public/400.html +0 -114
  264. data/test/dummy/public/404.html +0 -114
  265. data/test/dummy/public/406-unsupported-browser.html +0 -114
  266. data/test/dummy/public/422.html +0 -114
  267. data/test/dummy/public/500.html +0 -114
  268. data/test/dummy/public/icon.png +0 -0
  269. data/test/dummy/public/icon.svg +0 -3
  270. data/test/dummy/storage/.keep +0 -0
  271. data/test/dummy/test/fixtures/authors.yml +0 -11
  272. data/test/dummy/test/models/author_test.rb +0 -7
  273. data/test/dummy/tmp/.keep +0 -0
  274. data/test/dummy/tmp/pids/.keep +0 -0
  275. data/test/dummy/tmp/storage/.keep +0 -0
  276. data/test/test_helper.rb +0 -15
  277. data/test/uchi/field_test.rb +0 -77
  278. data/test/uchi/i18n_test.rb +0 -18
  279. data/test/uchi/repository/routes_test.rb +0 -49
  280. data/test/uchi/repository/translate_test.rb +0 -272
  281. data/test/uchi/repository_test.rb +0 -137
  282. data/test/uchi/sort_order_test.rb +0 -47
  283. data/test/uchi_test.rb +0 -7
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module Uchi
6
+ class Pagy
7
+ # Provide the helpers to handle the url and anchor
8
+ module Linkable
9
+ module QueryUtils
10
+ module_function
11
+
12
+ # Extracted from Rack::Utils and reformatted for rubocop
13
+ # Add the 'unescaped' param, and use it for simple and safe url-templating.
14
+ # All string keyed hashes
15
+ def build_nested_query(value, prefix = nil, unescaped = [])
16
+ case value
17
+ when Array
18
+ value.map { |v| build_nested_query(v, "#{prefix}[]", unescaped) }.join('&')
19
+ when Hash
20
+ value.map do |k, v|
21
+ new_k = prefix ? "#{prefix}[#{escape(k)}]" : escape(k)
22
+ unescaped[unescaped.find_index(k)] = new_k if unescaped.size.positive? && new_k != k && unescaped.include?(k)
23
+ build_nested_query(v, new_k, unescaped)
24
+ end.delete_if(&:empty?).join('&')
25
+ when nil
26
+ escape(prefix)
27
+ else
28
+ raise ArgumentError, 'value must be a Hash' if prefix.nil?
29
+ return "#{escape(prefix)}=#{value}" if unescaped.include?(prefix)
30
+
31
+ "#{escape(prefix)}=#{escape(value)}"
32
+ end
33
+ end
34
+
35
+ def escape(str)
36
+ URI.encode_www_form_component(str)
37
+ end
38
+ end
39
+
40
+ protected
41
+
42
+ # Return the URL for the page, relying on the Pagy::Request
43
+ def compose_page_url(page, limit_token: nil, **options)
44
+ jsonapi, page_key, limit_key, limit, client_max_limit, querify, absolute, path, fragment =
45
+ @options.merge(options)
46
+ .values_at(:jsonapi, :page_key, :limit_key, :limit, :client_max_limit, :querify, :absolute, :path, :fragment)
47
+ query = @request.query.clone(freeze: false)
48
+ query.delete(jsonapi ? 'page' : page_key)
49
+ factors = {}.tap do |h|
50
+ h[page_key] = countless? ? "#{page || 1}+#{@last}" : page
51
+ h[limit_key] = limit_token || limit if client_max_limit
52
+ end.compact # No empty params
53
+ query.merge!(jsonapi ? { 'page' => factors } : factors) if factors.size.positive?
54
+ querify&.(query) # Must modify the query: the returned value is ignored
55
+ query_string = QueryUtils.build_nested_query(query, nil, [page_key, limit_key])
56
+ query_string = "?#{query_string}" unless query_string.empty?
57
+ fragment &&= %(##{fragment}) unless fragment&.start_with?('#')
58
+ "#{@request.base_url if absolute}#{path || @request.path}#{query_string}#{fragment}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Pagy
5
+ # Add method supporting range checking, range error and rescue
6
+ module Rangeable
7
+ # Check if in range
8
+ def in_range?
9
+ return @in_range if defined?(@in_range) || (@in_range = yield)
10
+ raise RangeError.new(self, :page, "in 1..#{@last}", @page) if @options[:raise_range_error]
11
+
12
+ assign_empty_page_variables
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Pagy
5
+ module Shiftable
6
+ protected
7
+
8
+ def assign_previous_and_next
9
+ @previous = @page - 1 unless @page == 1
10
+ @next = @page + 1 unless @page == @last
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Pagy
5
+ # Provide a ready to use pagy environment when included in irb/rails console
6
+ module Console
7
+ class Request
8
+ attr_accessor :base_url, :path, :params
9
+
10
+ def initialize
11
+ @base_url = 'http://www.example.com'
12
+ @path = '/path'
13
+ @params = { example: '123' }
14
+ end
15
+
16
+ def GET = @params # rubocop:disable Naming/MethodName
17
+
18
+ def cookies = {}
19
+ end
20
+
21
+ class Collection < Array
22
+ def initialize(arr = Array(1..1000))
23
+ super
24
+ @collection = clone
25
+ end
26
+
27
+ def offset(value) = tap { @collection = self[value..] }
28
+ def limit(value) = @collection[0, value]
29
+ def count(*) = size
30
+ end
31
+
32
+ include Method
33
+
34
+ # Direct reference to request.params via a method
35
+ def params = request.params
36
+ def request = @request ||= Request.new
37
+ def collection = Collection
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Pagy
5
+ module Loader
6
+ paths = { public: { page_url: 'page_url' } }.freeze
7
+
8
+ paths.each do |visibility, methods|
9
+ send(visibility)
10
+ # Load the method, overriding its own alias. Next requests will call the method directly.
11
+ define_method(:"load_#{visibility}") do |*args, **kwargs|
12
+ require_relative methods[__callee__]
13
+ send(__callee__, *args, **kwargs)
14
+ end
15
+ methods.each_key { |method| alias_method method, :"load_#{visibility}" }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Pagy
5
+ # Return the page url for any page
6
+ # :nocov:
7
+ def page_url(page, **)
8
+ case page
9
+ when :first
10
+ compose_page_url(nil, **)
11
+ when :current
12
+ compose_page_url(@page, **) if @page
13
+ when :previous
14
+ compose_page_url(@previous, **) if @previous
15
+ when :next
16
+ compose_page_url(@next, **) if @next
17
+ when :last
18
+ compose_page_url(@last, **) if @last
19
+ when Integer, String
20
+ compose_page_url(page, **)
21
+ end
22
+ end
23
+ # :nocov:
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../classes/request'
4
+
5
+ module Uchi
6
+ class Pagy
7
+ paginators = { offset: :OffsetPaginator }.freeze
8
+
9
+ path = Pathname.new(__dir__)
10
+ paginators.each { |symbol, name| autoload name, path.join(symbol.to_s) }
11
+
12
+ # Defines the pagy method. Include in the app controller/view.
13
+ Method = Module.new do
14
+ protected
15
+
16
+ define_method :pagy do |paginator = :offset, collection, **options|
17
+ Pagy.const_get(paginators[paginator]).paginate(self, collection, **Pagy.options, **options)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Pagy
5
+ module OffsetPaginator
6
+ module_function
7
+
8
+ # Return instance and page of results
9
+ def paginate(context, collection, **options)
10
+ context.instance_eval do
11
+ request = Request.new(options[:request] || self.request, options)
12
+ options[:page] ||= request.resolve_page(options)
13
+ options[:limit] = request.resolve_limit(options)
14
+ options[:count] ||= collection.instance_of?(Array) ? collection.size : OffsetPaginator.get_count(collection, options)
15
+ pagy = Offset.new(**options, request:)
16
+ [pagy, collection.instance_of?(Array) ? collection[pagy.offset, pagy.limit] : pagy.records(collection)]
17
+ end
18
+ end
19
+
20
+ # Get the collection count
21
+ def get_count(collection, options)
22
+ return collection.count unless defined?(::ActiveRecord) && collection.is_a?(::ActiveRecord::Relation)
23
+
24
+ count = if options[:count_over] && !collection.group_values.empty?
25
+ # COUNT(*) OVER ()
26
+ sql = Arel.star.count.over(Arel::Nodes::Grouping.new([]))
27
+ collection.unscope(:order).pick(sql).to_i
28
+ else
29
+ collection.count(:all)
30
+ end
31
+ count.is_a?(Hash) ? count.size : count
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/uchi/pagy.rb ADDED
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require_relative 'pagy/classes/exceptions'
5
+ require_relative 'pagy/modules/abilities/linkable'
6
+ require_relative 'pagy/modules/abilities/configurable'
7
+ require_relative 'pagy/toolbox/helpers/loader'
8
+
9
+ module Uchi
10
+ # Top superclass: it defines only what's common to all the subclasses
11
+ class Pagy
12
+ VERSION = '43.0.0.rc4'
13
+ ROOT = Pathname.new(__dir__).parent.freeze
14
+ DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
15
+ PAGE_TOKEN = 'P '
16
+ LIMIT_TOKEN = 'L '
17
+ LABEL_TOKEN = 'L'
18
+ A_TAG = '<a style="display: none;">#</a>'
19
+
20
+ path = Pathname.new(__FILE__).sub_ext('')
21
+ autoload :Method, path.join('toolbox/paginators/method')
22
+ autoload :I18n, path.join('modules/i18n/i18n')
23
+ autoload :Console, path.join('modules/console')
24
+ autoload :Offset, path.join('classes/offset/offset')
25
+
26
+ def self.options = @options ||= {}
27
+
28
+ extend Configurable
29
+ include Linkable
30
+ include Loader
31
+
32
+ attr_reader :page, :next, :in, :limit, :options
33
+
34
+ protected
35
+
36
+ # Define the hierarchical identity methods, overridden by the respective classes
37
+ def offset? = false
38
+ def countless? = false
39
+
40
+ # Validates and assign the passed options: var must be present and value.to_i must be >= to min
41
+ def assign_and_check(name_min)
42
+ name_min.each do |name, min|
43
+ raise OptionError.new(self, name, ">= #{min}", @options[name]) \
44
+ unless @options[name].respond_to?(:to_i) && instance_variable_set(:"@#{name}", @options[name].to_i) >= min
45
+ end
46
+ end
47
+
48
+ # Merge all the DEFAULT constants of the class hierarchy with the options
49
+ def assign_options(**options)
50
+ @request = options.delete(:request) # internal object
51
+ default = {}
52
+ current = self.class
53
+ begin
54
+ default = current::DEFAULT.merge(default)
55
+ current = current.superclass
56
+ end until current == Object # rubocop:disable Lint/Loop -- see https://github.com/rubocop/rubocop-performance/issues/362
57
+ @options = default.merge!(options.delete_if { |k, v| default.key?(k) && (v.nil? || v == '') }).freeze
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Repository
5
+ class Routes
6
+ attr_reader :plural, :repository, :singular
7
+
8
+ def initialize(repository)
9
+ @repository = repository
10
+ @singular = repository.controller_name.singularize
11
+ @plural = repository.controller_name.pluralize
12
+ end
13
+
14
+ def path_for(action, **options)
15
+ action = action.to_sym
16
+ case action
17
+ when :destroy, :edit, :new, :show, :update
18
+ singular_path_for(action, **options)
19
+ else
20
+ plural_path_for(action, **options)
21
+ end
22
+ end
23
+
24
+ def root_path
25
+ "/#{uchi_namespace}/"
26
+ end
27
+
28
+ private
29
+
30
+ def call_url_helper_in_main_app(parts, **options)
31
+ url_helper_name = parts.join("_")
32
+ Rails.application.routes.url_helpers.send(url_helper_name, **options)
33
+ end
34
+
35
+ def plural_path_for(_action, **options)
36
+ parts = [
37
+ uchi_namespace,
38
+ plural,
39
+ "path"
40
+ ].compact
41
+ call_url_helper_in_main_app(parts, **options)
42
+ end
43
+
44
+ def singular_path_for(action, **options)
45
+ action = nil if action == :show
46
+ action = nil if action == :update
47
+ action = nil if action == :destroy
48
+ parts = [
49
+ action,
50
+ uchi_namespace,
51
+ singular,
52
+ "path"
53
+ ].compact
54
+ call_url_helper_in_main_app(parts, **options)
55
+ end
56
+
57
+ def uchi_namespace
58
+ :uchi
59
+ end
60
+ end
61
+ end
62
+ end