kadmin 0.1.7 → 0.2.2

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