recourse 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f9e1d88463aa9fa76e6ef3eece1bac89c07dc243bbc9686152a618c27b8ec34
4
- data.tar.gz: 3b6b069221e79bc5858576178f9e057ddf85cde0969bb866f84a4fbf4d64b1fd
3
+ metadata.gz: c8ac6749b83bd677e0066b47d0e3137dc3c21d320bfef56e1df5857bb49fa425
4
+ data.tar.gz: 1bedace6c0dbfc04d6a7bb741e4f21999dd932b8e0d510e4057c75c394ddd36d
5
5
  SHA512:
6
- metadata.gz: 314ecfe4d38bdb02c7116cae414180fc6f6bf9e6a33d5262e941939f5cfbc375836e3e3b58e68a15b8d82e5129d5f6642c6c5c9c654d9916c3a96556eed85fde
7
- data.tar.gz: e58d715cf54a9ce6ee19c8eb6a1ef20508fd2f5920d92167bd3de6751ded5c9e08daf9371fa36838c3a31fd9a7d3201a3ec6c551a87b9f668c9a2ded30803553
6
+ metadata.gz: 9a834ed2627af083af2702471e8b2455c04b9750ef90e4bdd879283900ad7c2dc8a438237311e99768c9ddeee8f49024c17daf029e12aa137cd08423a0650efb
7
+ data.tar.gz: de580547ed1f66cc50fa6d4214ef665417bbf603989c75b045eb16a4499e8a66967d09e0ed5a1eefbfadd4ef89745e6f088992627f05dcbc1fc852a74f0739a5
data/README.md CHANGED
@@ -13,7 +13,7 @@ Provides a new `recourses` method that can be invoked in a Rails app inside conf
13
13
  How to install
14
14
  ==============
15
15
 
16
- 1. Add `gem 'resource'` to the `Gemfile` file of your Rails app.
16
+ 1. Add `gem 'recourse'` to the `Gemfile` file of your Rails app.
17
17
 
18
18
  Available methods
19
19
  =================
@@ -26,7 +26,6 @@ In a resourceful Active Record model:
26
26
 
27
27
  - `recourse_includes`: the associations to include when fetching the resources
28
28
  - `recourse_order`: the SQL to sort the resources by
29
- - `recourse_positionable?`: whether the model has a position field to drag'n'drop sort
30
29
 
31
30
  Anywhere:
32
31
 
@@ -0,0 +1,26 @@
1
+ # Base class for recoursive controllers.
2
+ class RecoursesController < ApplicationController
3
+ helper RecoursiveHelper, SearchableHelper
4
+ include Pagy::Method
5
+
6
+ def index
7
+ model = controller_name.classify.constantize
8
+ @where = request.path_parameters.except(:controller, :action)
9
+ @order = model.recourse_order
10
+ @includes = model.recourse_includes
11
+
12
+ @pagy, @resources = paginate model, where: @where, order: model.recourse_order, includes: model.recourse_includes
13
+ end
14
+
15
+ private
16
+
17
+ def paginate(model, where: nil, includes: [], order: nil)
18
+ if model.recourse_searchable? || model.recourse_sortable?
19
+ @q = model.where(where).ransack params[:q]
20
+ @q.sorts = (order || 'created_at desc') if @q.sorts.empty?
21
+ pagy includes.present? ? @q.result.includes(includes) : @q.result.distinct(true)
22
+ else
23
+ pagy model.where(where).includes(includes).order(order)
24
+ end
25
+ end
26
+ end
@@ -1,17 +1,10 @@
1
1
  # A collection of helper methods used for navigation link in the admin-only section
2
2
  module RecoursiveHelper
3
- # Adds a link next to the title to add a new resource.
4
- def button_link_to_add(name, path)
5
- content_for(:edit) { button_link_to "Add #{name}", path, class: 'btn-sm btn-outline ms-3' }
6
- end
7
-
8
- private
9
-
10
- def button_link_to(name, path, options = {})
11
- link_to name, path, with_class('btn theme-primary', options)
12
- end
13
-
14
- def with_class(classes, options = {})
15
- options.merge class: [options[:class], classes].compact.join(' ')
3
+ def column(header:, **options, &block)
4
+ if @recourse_headers
5
+ tag.th header, **options.merge(scope: :col)
6
+ else
7
+ tag.td **options, &block
8
+ end
16
9
  end
17
10
  end
@@ -1,4 +1,4 @@
1
- # A collection of helper methods used for navigation link in the admin-only section
1
+ # A collection of helper methods used to search resources with Ransack.
2
2
  module SearchableHelper
3
3
  # Renders a form with a field to search within +model+ that uses +placeholder+.
4
4
  # @param [String] q the search query.
@@ -0,0 +1,10 @@
1
+ <%# locals: (recourse:) -%>
2
+ <% recourse.attributes.each do |name, value| %>
3
+ <%= column header: (recourse.class.ransortable_attributes.include?(name) ? sort_link(@q, name, name.upcase_first) : name) do %>
4
+ <% if recourse.class.ransackable_attributes.include? name %>
5
+ <%= search_highlight value, model: recourse.class %>
6
+ <% else %>
7
+ <%= value %>
8
+ <% end %>
9
+ <% end unless recourse.encrypted_attribute?(name) %>
10
+ <% end %>
@@ -0,0 +1,35 @@
1
+ <%# locals: (recourses:, pagy: nil, cached: true) -%>
2
+ <% name = @lookup_context.find_template('row', @lookup_context.prefixes, true).short_identifier.split('/')[-2] %>
3
+ <% key = name.singularize.to_sym %>
4
+
5
+ <%= turbo_frame_tag :results, data: { turbo_action: :advance }, class: 'w-100' do %>
6
+ <% if recourses.empty? %>
7
+ <p>No <%= controller_path.split('/').last %>.</p>
8
+ <% else %>
9
+ <% cache_if cached, [recourses, (recourses.unscoped.size if cached)].compact do %>
10
+ <table class='table caption-top table-hover table-responsive'>
11
+ <thead>
12
+ <tr>
13
+ <% @recourse_headers = true %>
14
+ <%= render 'row', key => recourses.first %>
15
+ </tr>
16
+ </thead>
17
+ <tbody class='table-group-divider'>
18
+ <% @recourse_headers = false %>
19
+ <% recourses.each do |recourse| %>
20
+ <% cache_if (cached && !params.key?(:q)), recourse do %>
21
+ <tr><%= render 'row', key => recourse %></tr>
22
+ <% end %>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+
27
+ <% if pagy %>
28
+ <div class='d-flex align-items-center'>
29
+ <div class='ps-2 flex-grow-1'><%== pagy.info_tag %></div>
30
+ <div class='pe-2'><%== pagy.series_nav(:bootstrap, classes: 'pagination mb-0') if pagy.last > 1 %></div>
31
+ </div>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <% resources = instance_variable_get("@#{controller_path.split('/').last}") || @resources %>
2
+
3
+ <% content_for :title, controller_path.split('/').last.humanize if @where&.empty? %>
4
+
5
+ <% model = controller_name.classify.constantize %>
6
+
7
+ <% content_for :search do %>
8
+ <script>
9
+ function debounce(method) {
10
+ let timer
11
+ return (...args) => {
12
+ clearTimeout(timer)
13
+ timer = setTimeout(() => { method.apply(this, args) }, 300);
14
+ }
15
+ }
16
+ const debouncedSubmit = debounce((form) => form.requestSubmit())
17
+ </script>
18
+ <%= search_form q: @q, model: model, url: request.path %>
19
+ <% end if model.recourse_searchable? %>
20
+
21
+ <% content_for :actions do %>
22
+ <% begin %>
23
+ <%= link_to "Add #{controller_path.split('/').last.singularize}", url_for(action: :new), class: 'btn theme-primary btn-sm btn-outline ms-3'%>
24
+ <% rescue ActionController::UrlGenerationError %>
25
+ <% end %>
26
+ <% end %>
27
+
28
+ <% locals = { @lookup_context.find_template('table', @lookup_context.prefixes, true).short_identifier.split('/')[-2].to_sym => resources } %>
29
+
30
+ <%= render 'table', locals.merge(pagy: @pagy, cached: model.recourse_cachable?) %>
@@ -4,7 +4,8 @@ module Recourse
4
4
  class Engine < ::Rails::Engine
5
5
  config.to_prepare do
6
6
  require 'recourse/routing'
7
- require 'recourse/model'
7
+ require 'recourse/recoursive'
8
+ require 'recourse/searchable'
8
9
  end
9
10
  end
10
11
  end
@@ -7,13 +7,6 @@ module Recourse
7
7
  %i[]
8
8
  end
9
9
 
10
- # Return the actions available for the model (among :new, :edit, :destroy)
11
- def recourse_actions
12
- default = %i[new edit destroy]
13
- options = Recourse.resources[name.underscore.pluralize.to_sym]
14
- (Array(options.fetch :only, default) & default) - Array(options.fetch :except, [])
15
- end
16
-
17
10
  # Return the fields to .order when loading all the models.
18
11
  def recourse_order
19
12
  end
@@ -23,19 +16,18 @@ module Recourse
23
16
  true
24
17
  end
25
18
 
26
- # Return whether the model has a position field to use for drag'n'drop sorting.
27
- def recourse_positionable?
28
- false
29
- end
30
-
31
19
  # @return [Boolean] whether Ransack search attributes are defined on the resource.
32
20
  def recourse_searchable?
33
- ransackable_attributes.any?
21
+ ransackable_attributes.any? || ransackable_associations.any?
34
22
  end
35
23
 
36
24
  # @return [Boolean] whether Ransack sort attributes are defined on the resource.
37
25
  def recourse_sortable?
38
- ransortable_attributes.any?
26
+ ransortable_attributes.any?
39
27
  end
40
28
  end
41
29
  end
30
+
31
+ ActiveSupport.on_load(:active_record) do
32
+ extend Recourse::Recoursive
33
+ end
@@ -4,20 +4,32 @@ module Recourse
4
4
  module Routing
5
5
  # This method is equivalent to Rails `resources` with the added bonus that we store
6
6
  # the name of these administered resources so we can display them to admins in the navbar.
7
- def recourses(*administered_resources, concerns: nil, **options, &block)
8
- administered_resources.each { |resource| Recourse.resources[resource] = options }
7
+ def recourses(*args, **kwargs, &block)
8
+ store_recourses args, kwargs
9
+ resources(*args, **kwargs, &scoped(args, &block))
10
+ end
11
+
12
+ private
9
13
 
10
- if block_given?
11
- error = 'recourses accepts only one resource if a block is given'
12
- raise ArgumentError, error unless administered_resources.one?
13
- administered_resource = administered_resources.sole
14
- resources administered_resource, concerns: concerns, **options do
15
- scope module: administered_resource, &block
16
- end
14
+ def store_recourses(args, kwargs)
15
+ if @scope[:scope_level_resource]
16
+ Recourse.resources[@scope[:scope_level_resource].name.to_sym][:nested] = args
17
17
  else
18
- resources *administered_resources, concerns: concerns, **options, &block
18
+ routes = Array(kwargs.fetch :only, self.class::Resource.default_actions(false))
19
+ routes-= Array(kwargs.fetch :except, [])
20
+
21
+ Recourse.resources.merge! args.to_h { |arg| [arg, { module: @scope[:module], routes: routes }] }
19
22
  end
20
23
  end
24
+
25
+ def scoped(args, &block)
26
+ return unless block_given?
27
+
28
+ parent_module = args.sole
29
+ Proc.new { scope module: parent_module, &block }
30
+ rescue Enumerable::SoleItemExpectedError
31
+ raise ArgumentError, 'recourses accepts only one resource if a block is given'
32
+ end
21
33
  end
22
34
  end
23
35
 
@@ -38,3 +38,7 @@ module Recourse
38
38
  end
39
39
  end
40
40
  end
41
+
42
+ ActiveSupport.on_load(:active_record) do
43
+ extend Recourse::Searchable
44
+ end
@@ -1,3 +1,3 @@
1
1
  module Recourse
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
data/lib/recourse.rb CHANGED
@@ -1,11 +1,9 @@
1
- require 'bh'
2
1
  require 'pagy'
3
2
  require 'ransack'
4
3
  require 'recourse/engine'
5
4
 
6
5
  # @see https://ddnexus.github.io/pagy/resources/initializer/
7
6
  Pagy::OPTIONS[:limit] = 15
8
- # Pagy.options[:limit] = 15
9
7
 
10
8
  # A module to manage administered resources.
11
9
  module Recourse
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recourse
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: bh
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: pagy
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -103,18 +89,18 @@ files:
103
89
  - MIT-LICENSE
104
90
  - README.md
105
91
  - Rakefile
106
- - app/controllers/recourse_controller.rb
92
+ - app/controllers/recourses_controller.rb
107
93
  - app/helpers/recoursive_helper.rb
108
94
  - app/helpers/searchable_helper.rb
109
- - app/views/recourse/_row.html.erb
110
- - app/views/recourse/index.html.erb
95
+ - app/views/recourses/_row.html.erb
96
+ - app/views/recourses/_table.html.erb
97
+ - app/views/recourses/index.html.erb
111
98
  - db/migrate/20260323234318_add_baby_to_posts.rb
112
99
  - lib/recourse.rb
113
100
  - lib/recourse/engine.rb
114
- - lib/recourse/model.rb
115
- - lib/recourse/model/recoursive.rb
116
- - lib/recourse/model/searchable.rb
101
+ - lib/recourse/recoursive.rb
117
102
  - lib/recourse/routing.rb
103
+ - lib/recourse/searchable.rb
118
104
  - lib/recourse/version.rb
119
105
  homepage: https://github.com/claudiob/recourse
120
106
  licenses:
@@ -1,23 +0,0 @@
1
- # Base class for controllers that require admin access
2
- class RecourseController < ApplicationController
3
- helper RecoursiveHelper, SearchableHelper
4
- include Pagy::Method
5
-
6
- def index
7
- @model = controller_name.classify.constantize
8
- @pagy, @resources = paginate @model, order: @model.recourse_order, includes: @model.recourse_includes
9
- render 'recourse/index'
10
- end
11
-
12
- private
13
-
14
- def paginate(model, includes: [], order: nil)
15
- if model.recourse_searchable? || model.recourse_sortable?
16
- @q = model.ransack params[:q]
17
- @q.sorts = (order || 'created_at desc') if @q.sorts.empty?
18
- pagy includes.present? ? @q.result.includes(includes) : @q.result.distinct(true)
19
- else
20
- pagy @model.includes(includes).order(order)
21
- end
22
- end
23
- end
@@ -1,4 +0,0 @@
1
- <%# locals: (resource:, section:) -%>
2
- <%= column section: section, header: 'ID' do %>
3
- <%= resource.id %>
4
- <% end %>
@@ -1,21 +0,0 @@
1
- <% content_for :title, @model.name.pluralize %>
2
-
3
- <% content_for :search do %>
4
- <script>
5
- function debounce(method) {
6
- let timer
7
- return (...args) => {
8
- clearTimeout(timer)
9
- timer = setTimeout(() => { method.apply(this, args) }, 300);
10
- }
11
- }
12
- const debouncedSubmit = debounce((form) => form.requestSubmit())
13
- </script>
14
- <%= search_form q: @q, model: @model %>
15
- <% end if @model.recourse_searchable? %>
16
-
17
- <% button_link_to_add @model.name.underscore, url_for(action: :new) if @model.recourse_actions.include? :new %>
18
-
19
- <%= table items: @resources, cached: @model.recourse_cachable?, positioned: @model.recourse_positionable?, pagy: @pagy, empty_label: "No #{@model.name.underscore.pluralize}" do |resource, section| %>
20
- <%= render 'row', resource: resource, section: section %>
21
- <% end %>
@@ -1,8 +0,0 @@
1
- require 'recourse/model/recoursive'
2
- require 'recourse/model/searchable'
3
-
4
-
5
- ActiveSupport.on_load(:active_record) do
6
- extend Recourse::Recoursive
7
- extend Recourse::Searchable
8
- end