kadmin 0.1.7 → 0.2.2

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -1
  3. data/app/assets/stylesheets/kadmin/application.scss +9 -0
  4. data/app/assets/stylesheets/kadmin/{finder.scss → typeahead-select.scss} +0 -0
  5. data/app/decorators/kadmin/finder_decorator.rb +33 -0
  6. data/app/decorators/kadmin/pager_decorator.rb +33 -0
  7. data/app/views/kadmin/components/_finder.html.erb +18 -0
  8. data/app/views/kadmin/components/finder/_empty.html.erb +3 -0
  9. data/app/views/kadmin/components/finder/_form.erb +10 -0
  10. data/app/views/kadmin/components/finder/_header.html.erb +7 -0
  11. data/app/views/kadmin/{_alerts.html.erb → helpers/_alerts.html.erb} +0 -0
  12. data/app/views/kadmin/helpers/_form_errors.html.erb +10 -0
  13. data/app/views/layouts/kadmin/application.html.erb +2 -2
  14. data/config/locales/de.yml +5 -0
  15. data/config/locales/en.yml +5 -0
  16. data/lib/kadmin/finder.rb +15 -6
  17. data/lib/kadmin/form.rb +94 -44
  18. data/lib/kadmin/pager.rb +14 -3
  19. data/lib/kadmin/version.rb +1 -1
  20. data/test/dummy/app/controllers/admin/application_controller.rb +1 -1
  21. data/test/dummy/app/controllers/admin/{persons_controller.rb → people_controller.rb} +23 -20
  22. data/test/dummy/app/models/person.rb +6 -1
  23. data/test/dummy/app/views/admin/people/_form.html.erb +34 -0
  24. data/test/dummy/app/views/admin/people/_table.html.erb +33 -0
  25. data/test/dummy/app/views/admin/people/edit.html.erb +4 -0
  26. data/test/dummy/app/views/admin/people/index.html.erb +3 -0
  27. data/test/dummy/app/views/admin/people/new.html.erb +5 -0
  28. data/test/dummy/app/views/admin/people/show.html.erb +3 -0
  29. data/test/dummy/config/application.rb +6 -0
  30. data/test/dummy/config/locales/en.yml +13 -25
  31. data/test/dummy/config/routes.rb +2 -3
  32. data/test/dummy/db/dummy_development.sqlite3 +0 -0
  33. data/test/dummy/db/dummy_test.sqlite3 +0 -0
  34. data/test/dummy/db/migrate/20161006114509_create_people.rb +1 -1
  35. data/test/dummy/db/schema.rb +1 -1
  36. data/test/dummy/lib/forms/group_form.rb +1 -13
  37. data/test/dummy/lib/forms/person_form.rb +4 -13
  38. data/test/dummy/log/development.log +12819 -0
  39. data/test/dummy/log/test.log +38 -0
  40. data/test/dummy/tmp/cache/assets/sprockets/v3.0/-F/-FsNbFK52v0pNBxKy1HNNm6PvilpJ-x7Wnv9h6tmyB0.cache +1 -0
  41. data/test/dummy/tmp/cache/assets/sprockets/v3.0/0n/0n6SGYdqNeEUJSsu-imL90L-yIUKPi_NDSkmMySPdWQ.cache +1 -0
  42. data/test/dummy/tmp/cache/assets/sprockets/v3.0/0t/0tn2q8esWnF5fEcbJLYg62c_FP9JanA9PSXdq_tYLYY.cache +0 -0
  43. data/test/dummy/tmp/cache/assets/sprockets/v3.0/1I/1IRm2UzZL2EDGDrmidisFycmYvgjxsm88Qhn2Xgpob8.cache +0 -0
  44. data/test/dummy/tmp/cache/assets/sprockets/v3.0/1M/1MqoHGqxGd5cjs2GFOFxmpuvnUUEqu7VgvlhIQiJyOg.cache +1 -0
  45. data/test/dummy/tmp/cache/assets/sprockets/v3.0/55/55XufN8FnqyYo8_Q9Fjekgd9NNgLIf51DyqmHCunvXE.cache +1 -0
  46. data/test/dummy/tmp/cache/assets/sprockets/v3.0/5i/5i0sweEEf0_vvcK_Zu6WD6pyCfLIOQE-861C6dFShUY.cache +0 -0
  47. data/test/dummy/tmp/cache/assets/sprockets/v3.0/63/637_sfmF7zODk6Dzv4BT1sf91iXjjlcxAvnToFqUlDI.cache +0 -0
  48. data/test/dummy/tmp/cache/assets/sprockets/v3.0/6M/6MU_r-ycVrZjzTQTJGMi6ufWq8z7emfSF41_aIN-KB8.cache +0 -0
  49. data/test/dummy/tmp/cache/assets/sprockets/v3.0/7q/7qsE29MVuPYwZNAgNwNeH8xVhpOsc_b6ieDbhbhvqpw.cache +1 -0
  50. data/test/dummy/tmp/cache/assets/sprockets/v3.0/8P/8PiD2jpkOTKwCqmTucDpZfcaYIoDaLpRmCTDikmBAMk.cache +0 -0
  51. data/test/dummy/tmp/cache/assets/sprockets/v3.0/8T/8TAB-jrsQAvLWhG3fwfbx6ZVwjI7j6sAWWQTfPn91iU.cache +1 -0
  52. data/test/dummy/tmp/cache/assets/sprockets/v3.0/8g/8gcjIdXkz5dMqwDP6vmyotAcXH7cn0PUj3_5Wmp3CtI.cache +0 -0
  53. data/test/dummy/tmp/cache/assets/sprockets/v3.0/8m/8m_OIflQITiNEo__Te4xgHNCGjqG1584BRqm-cjmjcs.cache +0 -0
  54. data/test/dummy/tmp/cache/assets/sprockets/v3.0/9L/9LEx970_JauYhUWfuivS75eV0Iyfbt9ubccTHIsAfYw.cache +0 -0
  55. data/test/dummy/tmp/cache/assets/sprockets/v3.0/9_/9_pVK00xoiNV1Rlusq__AhBd-RI2wkk8CYsmetStEr0.cache +0 -0
  56. data/test/dummy/tmp/cache/assets/sprockets/v3.0/9y/9yz1HSty42fFgm9G7oOB99fA470iGKKnSXO5t5PEHXw.cache +0 -0
  57. data/test/dummy/tmp/cache/assets/sprockets/v3.0/C_/C_apQOWNpEW8SnzKgrc57IADlEkNTHj8h_oDFtY9064.cache +0 -0
  58. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Cy/Cy_UGoqxMmEq4sj5RxYjcR-aIdYvnjZvP_Pnrz1_9e8.cache +0 -0
  59. data/test/dummy/tmp/cache/assets/sprockets/v3.0/DP/DPzrQSMXLpVyt0hbMLGC5DJZyW9kFE4CP0vt6WNOp48.cache +1 -0
  60. data/test/dummy/tmp/cache/assets/sprockets/v3.0/GW/GW4OJ-JDKmC4uwvYKzjrIhVq-P4t_9zaLGKSydO1f-U.cache +1 -0
  61. data/test/dummy/tmp/cache/assets/sprockets/v3.0/H8/H8slRzi1H2WqciDo1jBGxJw6DZeRgEuv5MXdQ-y6mWg.cache +1 -0
  62. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Iy/IyL_kOqVN1xwoPTt5LY6WW15l0RdXMbqH4eG9BIy9eo.cache +2 -0
  63. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Iy/IyfLjIElmUEnBz0fjO4ofGF04F_HqLxAbuft8mp0yBU.cache +1 -0
  64. data/test/dummy/tmp/cache/assets/sprockets/v3.0/JR/JRJaUThDzGQo0YF40Lfk5goZBKJKxJJBtxXLmxwRx2w.cache +0 -0
  65. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Lf/Lf6bmdizyOM18a9qBS2SAloyVi3kwlh5LDQ9rf09AVs.cache +1 -0
  66. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Lf/lFitMap7CRaoT13khB0t9biKi45L1Gx053Qqb0V7xZ0.cache +2 -0
  67. data/test/dummy/tmp/cache/assets/sprockets/v3.0/NL/NLORenk-WvjF2ETu3Vd8oPQSkm56nqI0w4FNB4PlOhw.cache +0 -0
  68. data/test/dummy/tmp/cache/assets/sprockets/v3.0/O3/o3x01lJRlMuBMc1c5TIFuEysbQ5hJ-u8oDEu3O8LYpM.cache +1 -0
  69. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Oe/Oe-3DQv4rb1wBwCSKfPxsFWLQEG2uwzsN3r1NlhUtGg.cache +0 -0
  70. data/test/dummy/tmp/cache/assets/sprockets/v3.0/QR/qrn7imioXU-GzX_YDKunySyxOQQPdYnm8cFRc9_0Bbg.cache +1 -0
  71. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Qk/qK1FqgxCmejbunaDgFtqgzegZ-MWYa9sGmkdsy2hXbg.cache +0 -0
  72. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Sj/Sj9P9v_5nRDm_FbYyYHC7B00vqxjZxYc27y9Cm2vpb4.cache +2 -0
  73. data/test/dummy/tmp/cache/assets/sprockets/v3.0/UG/UGW_N3c5WIGHIsGBAlRBI-sD2QLfW6LMGFiCsdfC2xE.cache +1 -0
  74. data/test/dummy/tmp/cache/assets/sprockets/v3.0/VZ/VZjlQNeP2hr_AciJNPbb7z4G6K4AKe3Z0gn4WdC7-Tc.cache +3 -0
  75. data/test/dummy/tmp/cache/assets/sprockets/v3.0/WK/WKjVrAg3EXJ83bQHhANNBB8gXiSSQwcWJfzQUOrfly4.cache +0 -0
  76. data/test/dummy/tmp/cache/assets/sprockets/v3.0/WS/WSKgaB4x1F6sDxk0fM4Fk8nA4EVyHSSJesu1-X82yjQ.cache +1 -0
  77. data/test/dummy/tmp/cache/assets/sprockets/v3.0/YY/YyJCDg1eiYs1CZSWGv3k7E8PC2u8ujMHnHruKAZB_ns.cache +0 -0
  78. data/test/dummy/tmp/cache/assets/sprockets/v3.0/Yt/ytqIuGGRQKNExye5HUeNNpLiTb2bz_DUdCNF-PNgG5s.cache +1 -0
  79. data/test/dummy/tmp/cache/assets/sprockets/v3.0/ZN/ZNsrQk4C9XhLrV8_ccxeh7iUpUxCU8p6HzJnLQ1JzTs.cache +2 -0
  80. data/test/dummy/tmp/cache/assets/sprockets/v3.0/_c/_chctmRf-pIheCJNY7hl-Sx59opmqdxFL2PevfZwq2o.cache +0 -0
  81. data/test/dummy/tmp/cache/assets/sprockets/v3.0/a2/a2cuBhgUfDqIn_cESf3bZCYgOfUL1pr90zUxIqJ9kKk.cache +2 -0
  82. data/test/dummy/tmp/cache/assets/sprockets/v3.0/aP/aPmiEIWGz1eNfYaCxOihmOj8gH_FiMuy2vyfa2dSm74.cache +1 -0
  83. data/test/dummy/tmp/cache/assets/sprockets/v3.0/br/BRgaKx5w_y5SAfZ01RZMdp6Z7-ggaICbwPGgGGYnMvo.cache +4 -0
  84. data/test/dummy/tmp/cache/assets/sprockets/v3.0/cc/cc_H3Ys29GBr0Mwo8mhwVpU1ZD29Orm_zrrlsK04ZxY.cache +0 -0
  85. data/test/dummy/tmp/cache/assets/sprockets/v3.0/cp/cpltkoEnlCxGELgo5pzYraQtUFtncB66ULMh61y9UmU.cache +0 -0
  86. data/test/dummy/tmp/cache/assets/sprockets/v3.0/d2/d2pyKhpApMC_82fcSpBr3rJiQi0GXmd5nAxW4gjjbqI.cache +1 -0
  87. data/test/dummy/tmp/cache/assets/sprockets/v3.0/dG/DgWTCx-Rb2jbSHoEnFqjHWsBClV-3r80EPU7mB2IOlg.cache +0 -0
  88. data/test/dummy/tmp/cache/assets/sprockets/v3.0/fb/fbnO4L8jq7mKY_872G6-R_1g55VkHGsFLV5_L9N_8JY.cache +0 -0
  89. data/test/dummy/tmp/cache/assets/sprockets/v3.0/gK/gKggrFUEs3kvWMzHpuEICvH6zL86Giqv50EMpi91rUk.cache +1 -0
  90. data/test/dummy/tmp/cache/assets/sprockets/v3.0/n1/n1pvlbASbj39sCgVSMFGCy33gQAhAGlLscNk8Q_hGA0.cache +0 -0
  91. data/test/dummy/tmp/cache/assets/sprockets/v3.0/r8/r8Tej1MuIktw1pbDz86gRXteeQymjEsgGYzC6nBpwxI.cache +1 -0
  92. data/test/dummy/tmp/cache/assets/sprockets/v3.0/tK/tKjzj5lmM8TAi-WoE8QAQEBI5vJv7bmYYxuEH1UXb9c.cache +1 -0
  93. data/test/dummy/tmp/cache/assets/sprockets/v3.0/tb/TbBwthojLrWKVI_yFccgzGN9cXRY9iHGWVV3lr8uXTM.cache +1 -0
  94. data/test/dummy/tmp/cache/assets/sprockets/v3.0/tq/TQpnYfdZO_10UMuNVcEY29LaqvuW8gtzhA6qvSt4iQM.cache +0 -0
  95. data/test/dummy/tmp/cache/assets/sprockets/v3.0/u7/u75Pn-UKc5QtTrPD6IPz43HZYUDrevf8iMkv7_LabOA.cache +0 -0
  96. data/test/dummy/tmp/cache/assets/sprockets/v3.0/zE/zEE95VsN7pfJ2w6HuECxe6KtA0vg0I7liX2HuPK92Tk.cache +1 -0
  97. metadata +137 -14
  98. data/test/dummy/app/views/admin/persons/_form.html.erb +0 -0
  99. data/test/dummy/app/views/admin/persons/edit.html.erb +0 -0
  100. data/test/dummy/app/views/admin/persons/index.html.erb +0 -0
  101. data/test/dummy/app/views/admin/persons/new.html.erb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e9d50b4cc493eab605ce1ba63e1652c57f4cebda
4
- data.tar.gz: 916d45a69e5a1e2a751a8bae2e87237e42ea2e15
3
+ metadata.gz: 6ab1df6f025d446075d7ea0ff8e6f5e6267e5048
4
+ data.tar.gz: e01b485202b100b31014c57680fe99ded5017518
5
5
  SHA512:
6
- metadata.gz: 8ce32466d84c80136a4bc37543bb5d34c1b73792a02a8d7b6cfc2022b924aff030d90a7f0d2126658f8166b7d5a8264f145907dd94effac247404f33ca5a4dac
7
- data.tar.gz: 861b60839df2cf82d6e338869d4157d35a641eab80d05680aa7a8672ed0968ecc1ed277bdbe22667309207cb49a0c2189c25da1ff7c31940934ab4a37300f6e3
6
+ metadata.gz: 221c1f51318638636bbafbd2c7dd135cd21f69f0cf4df6edbd97be4eda02ee07e2b3013a041aa9138b4c106eaf296747f0139ee7df80d6ff1e7392260cbd944e
7
+ data.tar.gz: cbc7767909e0b85ff93f7fa6ee6a08d4d03d57ac4ad0db975b35f9d8b79fd17bc487f290313c25f8e3f712aadb0fc4f588583f572631cb081984632b6f7926ef
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kadmin
2
2
 
3
- [![GitHub release](https://img.shields.io/badge/release-0.1.7-blue.png)](https://github.com/barcoo/kadmin/releases/tag/0.1.7)
3
+ [![GitHub release](https://img.shields.io/badge/release-0.2.2-blue.png)](https://github.com/barcoo/kadmin/releases/tag/0.2.2)
4
4
 
5
5
  Collection of utility, configuration, etc., for admin areas in different projects.
6
6
 
@@ -33,3 +33,11 @@ When you want to create a new release, use the rake task ```cim:release``` (in t
33
33
  ```shell
34
34
  bundle exec rake cim:release
35
35
  ```
36
+
37
+ ## Roadmap
38
+
39
+ TODOs:
40
+
41
+ * [ ] Finish form objects (destruction and creation) + tests + examples
42
+ * [ ] Make a generic typehead-select form object
43
+ * [x] Wrap Finder objects + view helpers
@@ -2,6 +2,15 @@
2
2
  @import "bootstrap-sprockets";
3
3
  @import "bootstrap";
4
4
 
5
+ /**
6
+ * Highlights fields properly when they have errors
7
+ * Rails automatically appends .field_with_errors on any form element whose model
8
+ * attributes has an error (as marked in model.errors)
9
+ */
10
+ .field_with_errors {
11
+ @extend .has-error;
12
+ }
13
+
5
14
  a.thumbnail {
6
15
  max-width: 100%;
7
16
  overflow: hidden;
@@ -0,0 +1,33 @@
1
+ module Kadmin
2
+ class FinderDecorator
3
+ # @return [Kadmin::Finder] underlying finder model
4
+ attr_reader :finder
5
+
6
+ delegate :filters, :scope, :results, to: :finder
7
+
8
+ def initialize(finder)
9
+ @finder = finder
10
+ end
11
+
12
+ # @return [String] human readable, singular/plural form of the finder's model
13
+ def resource_name
14
+ return @finder.scope.model_name.human(count: pager.displayed_items)
15
+ end
16
+
17
+ # @return [String] a description of the current applied filters
18
+ def applied_filters
19
+ filters = @finder.filters.reduce([]) do |acc, (name, filter)|
20
+ next(acc) if filter.value.blank?
21
+ acc << %(<strong>#{filter.value}</strong> on <em>#{name}</em>).html_safe
22
+ end
23
+ applied_filters = "(filtering: #{filters.join('; ')})" unless filters.empty?
24
+
25
+ return applied_filters
26
+ end
27
+
28
+ # @return [Kadmin::PagerDecorator] decorated pager of the underlying finder
29
+ def pager
30
+ return @pager ||= Kadmin::PagerDecorator.new(@finder.pager)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Kadmin
2
+ class PagerDecorator
3
+ # @return [Kadmin::Pager] underlying pager model
4
+ attr_reader :pager
5
+
6
+ delegate :total, :size, :offset, :pages, :current_page, :contains?, :next_page?,
7
+ :previous_page?, :offset_at, :current_page?,
8
+ to: :pager
9
+
10
+ def initialize(pager)
11
+ @pager = pager
12
+ end
13
+
14
+ # @return [Integer] the current number of items displayed for this page
15
+ def displayed_items
16
+ return page_end - offset
17
+ end
18
+
19
+ # @return [Integer] the index number of the last item for this page
20
+ def page_end
21
+ return [next_page_offset, total].min
22
+ end
23
+
24
+ # @return [Integer] the index number of the start item for this page
25
+ def page_start
26
+ return offset + 1
27
+ end
28
+
29
+ def next_page_offset
30
+ return @pager.offset_at(current_page + 1)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ <%= render partial: 'kadmin/components/finder/header', locals: { finder: finder } %>
2
+
3
+ <div class='container-fluid'>
4
+ <% if finder.filters.present? %>
5
+ <div class='row'>
6
+ <%= render partial: 'kadmin/components/finder/form', locals: { finder: finder } %>
7
+ </div>
8
+ <% end %>
9
+
10
+ <div class='row' style='padding-top: 15px'>
11
+ <% if finder.results.empty? %>
12
+ <%= render partial: 'kadmin/components/finder/empty', locals: { finder: finder } %>
13
+ <% else %>
14
+ <%= yield %>
15
+ <%= paginate(finder.pager, [15, 30, 50, 100]) %>
16
+ <% end %>
17
+ </div>
18
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class='jumbotron'>
2
+ <p><%= t('kadmin.components.finder.empty') %></p>
3
+ </div>
@@ -0,0 +1,10 @@
1
+ <%= form_tag(request.path, method: 'get', class: 'form-inline') do %>
2
+ <div class='form-group'>
3
+ <%= link_to('Create', url_for(action: :new), class: 'btn btn-primary') %>
4
+ <% finder.filters.each do |name, filter| %>
5
+ <%= text_field_tag("filter_#{name}", filter&.value, class: 'form-control', placeholder: "Filter by #{name}...") %>
6
+ <% end %>
7
+ <%= submit_tag('Filter', class: 'btn btn-default form-control') %>
8
+ <%= link_to('Clear', request.path, class: 'btn btn-danger form-control') %>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <div class="page-header">
2
+ <h2>
3
+ Showing <%= finder.resource_name.downcase %>
4
+ <%= finder.pager.page_start %> - <%= finder.pager.page_end %>
5
+ <small>out of <%= finder.pager.total %> <%= finder.applied_filters %></small>
6
+ </h2>
7
+ </div>
@@ -0,0 +1,10 @@
1
+ <% if model.errors.present? %>
2
+ <%= alert('danger') do %>
3
+ <p><%= glyphicon('exclamation-sign') %> <%= t('kadmin.forms.please_correct') %></p>
4
+ <ul>
5
+ <% model.errors.full_messages.each do |message| %>
6
+ <li><%= message.html_safe %></li>
7
+ <% end %>
8
+ </ul>
9
+ <% end %>
10
+ <% end %>
@@ -46,7 +46,7 @@
46
46
  <% if content_for?(:sidebar) %>
47
47
  <!-- main -->
48
48
  <div class="col-sm-8 col-md-8 col-lg-9 main">
49
- <%= render partial: 'kadmin/alerts' %>
49
+ <%= render partial: 'kadmin/helpers/alerts' %>
50
50
  <%= yield %>
51
51
  </div>
52
52
  <!-- sidebar -->
@@ -55,7 +55,7 @@
55
55
  </div>
56
56
  <% else %>
57
57
  <div class="col-sm-12 col-md-12 main">
58
- <%= render partial: 'kadmin/alerts' %>
58
+ <%= render partial: 'kadmin/helpers/alerts' %>
59
59
  <%= yield %>
60
60
  </div>
61
61
  <% end %>
@@ -6,9 +6,14 @@ de:
6
6
  please_login: Bitte melden Sie sich an
7
7
  unauthorized: Unberechtigte Zugriff
8
8
  unauthorized_message: Sie haben für die gewünschte Seite leider keine Zugriffsrechte; falls Sie haben sollten, bitte das Apps & Services Team kontaktieren
9
+ components:
10
+ finder:
11
+ empty: Nichts zu zeigen
9
12
  dash_message: Schaue Dir mal die Bereiche in der Navigationsleiste oben. Wenn es irgenwelche Probleme gibt, oder es fehlt Dir Zugriffsrechte, bitte das Apps & Services Team kontaktieren.
10
13
  error: Fehler
11
14
  errors:
12
15
  not_found: Objekt nicht gefunden
13
16
  params_missing: Fehlenden erforderlichen Parameter
17
+ forms:
18
+ please_correct: "Bitte die folgende Felde korrigieren:"
14
19
  welcome: Willkommen zurück, %{user}!
@@ -6,9 +6,14 @@ en:
6
6
  please_login: Please authenticate yourself
7
7
  unauthorized: Unauthorized access
8
8
  unauthorized_message: You are not authorized to access this resource; if you think you should be, contact the Apps & Services team
9
+ components:
10
+ finder:
11
+ empty: Nothing to show
9
12
  dash_message: See the top navigation bar for the different admin sections. If you are missing authorizations, or if there is any issue at all, contact the Apps & Services team!
10
13
  error: Error
11
14
  errors:
12
15
  not_found: Requested object not found
13
16
  params_missing: Missing required parameters
17
+ forms:
18
+ please_correct: "Please correct the following:"
14
19
  welcome: Welcome %{user}!
data/lib/kadmin/finder.rb CHANGED
@@ -17,16 +17,19 @@ module Kadmin
17
17
  @scope = scope
18
18
  @pager = nil
19
19
  @filters = {}
20
+ @results = nil
20
21
  end
21
22
 
22
23
  # @param [String] name the filter name (should be unique)
23
24
  # @param [String, Array<String>] column the column(s) name to filter on
24
25
  # @param [String, Array<String>] value the value or values to look for (OR'd)
25
26
  def filter(name:, column:, value:)
26
- if column.present? && value.present? && !@filters.key?(name)
27
+ if column.present? && !@filters.key?(name)
27
28
  @filters[name] = Kadmin::Finder::Filter.new(column, value)
28
- @scope = @scope.where("#{@scope.table_name}.`#{column}` LIKE ?", value.tr('*', '%'))
29
- @pager&.total = @scope.count
29
+ if value.present?
30
+ @scope = @scope.where("#{@scope.table_name}.`#{column}` LIKE ?", value.tr('*', '%'))
31
+ @pager&.total = @scope.count
32
+ end
30
33
  end
31
34
 
32
35
  return self
@@ -47,10 +50,16 @@ module Kadmin
47
50
  end
48
51
 
49
52
  # @return [ActiveRecord::Relation] the filtered (and optionally paginated) results
50
- def find
51
- results = @scope
52
- results = @pager.page(results) unless @pager.nil?
53
+ def results
54
+ return @results ||= begin
55
+ results = @scope
56
+ results = @pager.page(results) unless @pager.nil?
57
+ results
58
+ end
59
+ end
53
60
 
61
+ def find!
62
+ @results = nil
54
63
  return results
55
64
  end
56
65
  end
data/lib/kadmin/form.rb CHANGED
@@ -36,7 +36,7 @@ module Kadmin
36
36
  # @return [ActiveModel::Model] underlying model to populate
37
37
  attr_reader :model
38
38
 
39
- delegate :id, :persisted?, to: :model
39
+ delegate :id, :persisted?, :to_key, :to_query, :to_param, :type_for_attribute, to: :model
40
40
 
41
41
  def initialize(model)
42
42
  @errors = ActiveModel::Errors.new(self)
@@ -44,52 +44,23 @@ module Kadmin
44
44
  @form_input = {}
45
45
  end
46
46
 
47
- # @!group Parsing/Deserialization
48
-
49
- # Populates the model based on the form input. The input is typically obtained
50
- # from the controller's params method, but can be any hash which conforms to
51
- # whatever the form object is expecting.
52
- # If some input was previously parsed, there is no "rollback" on the state
53
- # of the model; it should be done prior to reparsing if it is necessary.
54
- # @param [Hash<String, Object>] form_input a hash representing the raw form input
55
- def assign_attributes(form_input)
56
- @errors.clear
57
- form_input.each do |attr, value|
58
- setter = "#{attr}="
59
- send(setter, value) if respond_to?(setter)
60
- end
47
+ def to_model
48
+ return @model
61
49
  end
62
50
 
63
- # @!endgroup
51
+ # @!group Attributes assignment/manipulation
64
52
 
65
- # @!group Validation
53
+ # Allows parsing of multi parameter attributes, such as those returned by
54
+ # the form helpers date_select, datetime_select, etc.
55
+ # Also allows nested attributes, but this is not currently in use.
56
+ include ActiveRecord::AttributeAssignment
66
57
 
67
- validate :model_valid?
68
-
69
- # Validates the models and merge errors back into our own errors if they
70
- # are invalid.
71
- # Overload if you need to validate associations.
72
- # @example
73
- # class PersonForm < Form
74
- # def model_valid?
75
- # super
76
- # if @model&.child&.changed? && !@model.child.valid?
77
- # @errors.add(:base, :invalid, message: 'child model is invalid')
78
- # end
79
- # end
80
- # end
81
- def model_valid?
82
- unless @model.valid?
83
- @model.errors.each do |attribute, error|
84
- @errors.add(attribute, error)
85
- end
86
- end
58
+ # For now, we overload the method to accept all attributes.
59
+ # This is removed in Rails 5, so once we upgrade we can remove the overload.
60
+ def sanitize_for_mass_assignment(attributes)
61
+ return attributes
87
62
  end
88
63
 
89
- # @!endgroup
90
-
91
- # @!group Helper methods
92
-
93
64
  class << self
94
65
  # Delegates the list of attributes to the model, both readers and writers.
95
66
  # If the attribute value passed is a hash and not a symbol, assumes it is
@@ -98,23 +69,102 @@ module Kadmin
98
69
  # delegate_attributes :first_name, { last_name: [:reader] }
99
70
  # @param [Array<Symbol, Hash<Symbol, Array<Symbol>>>] attributes list of attributes to delegate to the model
100
71
  def delegate_attributes(*attributes)
101
- delegates = attributes.reduce([]) do |acc, attribute|
72
+ delegates = attributes.each_with_object([]) do |attribute, acc|
102
73
  case attribute
103
74
  when Hash
104
75
  key, value = attribute.first
105
76
  acc << key if value.include?(:reader)
106
77
  acc << "#{key}=" if value.include?(:writer)
107
78
  when Symbol, String
108
- acc << attribute
79
+ acc.push(attribute, "#{attribute}=")
109
80
  else
110
81
  raise(ArgumentError, 'Attribute must be one of: Hash, Symbol, String')
111
82
  end
83
+ end
112
84
 
113
- delegate(*delegates, to: model)
85
+ delegate(*delegates, to: :model)
86
+ end
87
+
88
+ # Delegates a specified associations to other another form object
89
+ # @example
90
+ # delegate_associations :child, :parent, to: 'Forms::PersonForm'
91
+ cattr_accessor(:associations) { {} }
92
+ def delegate_association(association, to:)
93
+ self.associations[association] = to
94
+
95
+ # add a reader attribute
96
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
97
+ def #{association}
98
+ return self.associated_forms['#{association}']
99
+ end
100
+ METHOD
101
+ end
102
+ end
103
+
104
+ def associated_forms
105
+ return @associated_forms ||= begin
106
+ self.class.associations.map do |name, form_class_name|
107
+ form_class = form_class_name.constantize
108
+ form_class.new(@model.public_send(name))
114
109
  end
115
110
  end
116
111
  end
117
112
 
118
113
  # @!endgroup
114
+
115
+ # @!group Validation
116
+
117
+ validate :validate_model
118
+ def validate_model
119
+ unless @model.valid?
120
+ @model.errors.each do |attribute, error|
121
+ @errors.add(attribute, error)
122
+ end
123
+ end
124
+ end
125
+ protected :validate_model
126
+
127
+ validate :validate_associated_forms
128
+ def validate_associated_forms
129
+ self.associated_forms.each do |_name, form|
130
+ next if form.valid?
131
+ form.errors.each do |_attribute, _error|
132
+ @errors.add(:base, :association_error, "associated #{form.model_name.human} form has some errors")
133
+ end
134
+ end
135
+ end
136
+ protected :validate_associated_forms
137
+
138
+ # @!endgroup
139
+
140
+ # @!group Persistence
141
+
142
+ def save
143
+ saved = false
144
+ @model.class.transaction do
145
+ saved = @model.save
146
+ self.associated_forms.each do |_name, form|
147
+ saved &&= form.save
148
+ end
149
+
150
+ raise ActiveRecord::Rollback unless saved
151
+ end
152
+
153
+ return saved
154
+ end
155
+
156
+ def save!
157
+ saved = false
158
+ @model.class.transaction do
159
+ saved = @model.save!
160
+ self.associated_forms.each do |_name, form|
161
+ saved &&= form.save! # no need to raise anything, save! will do so
162
+ end
163
+ end
164
+
165
+ return saved
166
+ end
167
+
168
+ # @!endgroup
119
169
  end
120
170
  end