recourse 1.0.2 → 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: 238c0c227f1cd34c3aa991972792acbcdaf26ec64149f833e072fb106cae16e7
4
- data.tar.gz: 70769ca47575f9320d2a7acdbe1ec0edcb7db009afcaad594ca60319dfccc1ca
3
+ metadata.gz: c8ac6749b83bd677e0066b47d0e3137dc3c21d320bfef56e1df5857bb49fa425
4
+ data.tar.gz: 1bedace6c0dbfc04d6a7bb741e4f21999dd932b8e0d510e4057c75c394ddd36d
5
5
  SHA512:
6
- metadata.gz: 33d0deb2d2f1300d0cbdf0fa90337564a4325d5bd2a22a28ba8a2683de167f1847d8f2e32650e47943ebdd2ec385947a78abb2ceb25450d206c6eb0f10667514
7
- data.tar.gz: 84fb9a4fc6e3daf58b28c99f106a8520ce17a18cc76ae3daeedc14b61f9533806dbc2a82b0f9beb7250826f5d9406a28f00ff7cfbc9549ee77b468e0583c3e78
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?) %>
@@ -0,0 +1,5 @@
1
+ class AddBabyToPosts < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_reference :posts, :baby, null: true, foreign_key: true
4
+ end
5
+ end
@@ -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,10 +4,31 @@ 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
- # NOTE: only is not enough, I need the except as well
9
- administered_resources.each { |resource| Recourse.resources[resource] = options }
10
- resources *administered_resources, concerns: concerns, **options, &block
7
+ def recourses(*args, **kwargs, &block)
8
+ store_recourses args, kwargs
9
+ resources(*args, **kwargs, &scoped(args, &block))
10
+ end
11
+
12
+ private
13
+
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
+ else
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 }] }
22
+ end
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'
11
32
  end
12
33
  end
13
34
  end
@@ -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.0.2'
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.0.2
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,17 +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
98
+ - db/migrate/20260323234318_add_baby_to_posts.rb
111
99
  - lib/recourse.rb
112
100
  - lib/recourse/engine.rb
113
- - lib/recourse/model.rb
114
- - lib/recourse/model/recoursive.rb
115
- - lib/recourse/model/searchable.rb
101
+ - lib/recourse/recoursive.rb
116
102
  - lib/recourse/routing.rb
103
+ - lib/recourse/searchable.rb
117
104
  - lib/recourse/version.rb
118
105
  homepage: https://github.com/claudiob/recourse
119
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