gestart-admino 1.0.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +29 -0
  3. data/.gitignore +20 -0
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +91 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +716 -0
  9. data/Rakefile +6 -0
  10. data/gestart-admino.gemspec +36 -0
  11. data/lib/admino/action_view_extension.rb +35 -0
  12. data/lib/admino/query/base.rb +124 -0
  13. data/lib/admino/query/base_presenter.rb +27 -0
  14. data/lib/admino/query/builder.rb +24 -0
  15. data/lib/admino/query/configuration.rb +100 -0
  16. data/lib/admino/query/dsl.rb +38 -0
  17. data/lib/admino/query/filter_group.rb +73 -0
  18. data/lib/admino/query/filter_group_presenter.rb +85 -0
  19. data/lib/admino/query/scope_presenter.rb +17 -0
  20. data/lib/admino/query/search_field.rb +43 -0
  21. data/lib/admino/query/sorting.rb +74 -0
  22. data/lib/admino/query/sorting_presenter.rb +60 -0
  23. data/lib/admino/query.rb +15 -0
  24. data/lib/admino/table/head_row.rb +86 -0
  25. data/lib/admino/table/presenter.rb +122 -0
  26. data/lib/admino/table/resource_row.rb +145 -0
  27. data/lib/admino/table/row.rb +56 -0
  28. data/lib/admino/table.rb +7 -0
  29. data/lib/admino/version.rb +3 -0
  30. data/lib/admino.rb +14 -0
  31. data/lib/gestart-admino.rb +1 -0
  32. data/spec/admino/integration_spec.rb +63 -0
  33. data/spec/admino/query/base_presenter_spec.rb +59 -0
  34. data/spec/admino/query/base_spec.rb +192 -0
  35. data/spec/admino/query/dsl_spec.rb +52 -0
  36. data/spec/admino/query/filter_group_presenter_spec.rb +182 -0
  37. data/spec/admino/query/filter_group_spec.rb +133 -0
  38. data/spec/admino/query/search_field_spec.rb +66 -0
  39. data/spec/admino/query/sorting_presenter_spec.rb +206 -0
  40. data/spec/admino/query/sorting_spec.rb +137 -0
  41. data/spec/admino/table/head_row_spec.rb +158 -0
  42. data/spec/admino/table/presenter_spec.rb +180 -0
  43. data/spec/admino/table/resource_row_spec.rb +225 -0
  44. data/spec/admino/table/row_spec.rb +76 -0
  45. data/spec/spec_helper.rb +93 -0
  46. metadata +267 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
6
+
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'admino/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'gestart-admino'
8
+ spec.version = Admino::VERSION
9
+ spec.authors = ['Gestart Cloud S.r.l.', 'Stefano Verna']
10
+ spec.email = ['dev@gestart.net']
11
+ spec.description = %q{Fork Gestart di admino 0.0.22, potato di ciò che Gestart non usa}
12
+ spec.summary = %q{Fork Gestart di admino: query object e tabelle delle pagine indice}
13
+ spec.homepage = 'https://github.com/gestartcloudsrl/gestart-admino'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 3.1'
22
+
23
+ spec.add_dependency 'showcase', '~> 0.2.5'
24
+ spec.add_dependency 'activesupport'
25
+ spec.add_dependency 'activemodel'
26
+
27
+ spec.add_development_dependency 'bundler'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'i18n'
31
+ spec.add_development_dependency 'rspec-html-matchers'
32
+ spec.add_development_dependency 'actionpack'
33
+ spec.add_development_dependency 'railties'
34
+ spec.add_development_dependency 'simple_form'
35
+ spec.add_development_dependency 'ostruct'
36
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/core_ext/hash'
2
+ require 'admino/table/presenter'
3
+ require 'admino/query/base_presenter'
4
+
5
+ module Admino
6
+ module ActionViewExtension
7
+ module Internals
8
+ def self.present_query(query, context, options, key = :presenter)
9
+ presenter_klass = options.fetch(key, Admino::Query::BasePresenter)
10
+ presenter_klass.new(query, context)
11
+ end
12
+ end
13
+
14
+ def table_for(collection, options = {}, &block)
15
+ options.symbolize_keys!
16
+ options.assert_valid_keys(:presenter, :class, :query, :html)
17
+ presenter_klass = options.fetch(:presenter, Admino::Table::Presenter)
18
+ query = if options[:query]
19
+ Internals.present_query(options[:query], self, options, :query_presenter)
20
+ else
21
+ nil
22
+ end
23
+ presenter = presenter_klass.new(collection, options[:class], query, self)
24
+ html_options = options.fetch(:html, {})
25
+ presenter.to_html(html_options, &block)
26
+ end
27
+
28
+ def simple_search_form_for(query, options = {}, &block)
29
+ options.symbolize_keys!
30
+ Internals.present_query(query, self, options.slice(:presenter)).
31
+ simple_form(options, &block)
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,124 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/naming'
3
+ require 'active_model/translation'
4
+ require 'active_model/conversion'
5
+ require 'active_support/hash_with_indifferent_access'
6
+ require 'active_support/core_ext/hash'
7
+ require 'active_support/core_ext/class/attribute'
8
+
9
+ require 'admino/query/dsl'
10
+ require 'admino/query/builder'
11
+
12
+ module Admino
13
+ module Query
14
+ class Base
15
+ extend ActiveModel::Naming
16
+ extend ActiveModel::Translation
17
+ include ActiveModel::Conversion
18
+ extend Dsl
19
+
20
+ attr_reader :params
21
+ attr_reader :context
22
+ attr_reader :filter_groups
23
+ attr_reader :search_fields
24
+ attr_reader :sorting
25
+
26
+ def self.i18n_scope
27
+ :query
28
+ end
29
+
30
+ def initialize(params = nil, context = {}, config = nil)
31
+ @params =
32
+ if params.respond_to?(:to_unsafe_h)
33
+ ActiveSupport::HashWithIndifferentAccess.new(params.to_unsafe_h)
34
+ else
35
+ ActiveSupport::HashWithIndifferentAccess.new(params)
36
+ end
37
+ @config = config
38
+ @context = context
39
+
40
+ init_filter_groups
41
+ init_search_fields
42
+ init_sorting
43
+ end
44
+
45
+ def scope(starting_scope = nil)
46
+ starting_scope ||= if config.starting_scope_callable
47
+ config.starting_scope_callable.call(self)
48
+ else
49
+ raise ArgumentError, 'no starting scope provided'
50
+ end
51
+
52
+ scope = augment_scope(Builder.new(self, starting_scope)).scope
53
+
54
+ if config.ending_scope_callable
55
+ scope.instance_exec(self, &config.ending_scope_callable)
56
+ else
57
+ scope
58
+ end
59
+ end
60
+
61
+ def persisted?
62
+ false
63
+ end
64
+
65
+ def config
66
+ @config || self.class.config
67
+ end
68
+
69
+ def filter_groups
70
+ @filter_groups.values
71
+ end
72
+
73
+ def filter_group_by_name(name)
74
+ @filter_groups[name]
75
+ end
76
+
77
+ def search_fields
78
+ @search_fields.values
79
+ end
80
+
81
+ def search_field_by_name(name)
82
+ @search_fields[name]
83
+ end
84
+
85
+ private
86
+
87
+ def augment_scope(query_builder)
88
+ scope_augmenters.each do |augmenter|
89
+ query_builder = augmenter.augment_scope(query_builder)
90
+ end
91
+
92
+ query_builder
93
+ end
94
+
95
+ def scope_augmenters
96
+ scope_augmenters = search_fields + filter_groups
97
+ scope_augmenters << sorting if sorting
98
+ scope_augmenters
99
+ end
100
+
101
+ def init_filter_groups
102
+ @filter_groups = {}
103
+ i18n_key = self.class.model_name.i18n_key
104
+ config.filter_groups.each do |config|
105
+ @filter_groups[config.name] = FilterGroup.new(config, params, i18n_key)
106
+ end
107
+ end
108
+
109
+ def init_search_fields
110
+ @search_fields = {}
111
+ config.search_fields.each do |config|
112
+ @search_fields[config.name] = SearchField.new(config, params)
113
+ end
114
+ end
115
+
116
+ def init_sorting
117
+ if config.sorting
118
+ i18n_key = self.class.model_name.i18n_key
119
+ @sorting = Sorting.new(config.sorting, params, i18n_key)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,27 @@
1
+ require 'showcase'
2
+
3
+ module Admino
4
+ module Query
5
+ class BasePresenter < Showcase::Presenter
6
+ presents_collection :filter_groups
7
+ presents :sorting
8
+
9
+ def simple_form(options = {}, &block)
10
+ h.simple_form_for(
11
+ self,
12
+ options.reverse_merge(default_form_options),
13
+ &block
14
+ )
15
+ end
16
+
17
+ def default_form_options
18
+ {
19
+ as: :query,
20
+ method: :get,
21
+ url: h.request.fullpath
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,24 @@
1
+ module Admino
2
+ module Query
3
+ class Builder
4
+ attr_accessor :scope
5
+ attr_reader :context
6
+
7
+ def initialize(context, scope)
8
+ @context = context
9
+ @scope = scope
10
+ end
11
+
12
+ private
13
+
14
+ def method_missing(method, *args)
15
+ context_method = "#{method}_scope"
16
+ if context.respond_to?(context_method)
17
+ Builder.new(context, context.send(context_method, scope, *args))
18
+ else
19
+ Builder.new(context, scope.send(method, *args))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,100 @@
1
+ module Admino
2
+ module Query
3
+ class Configuration
4
+ class SearchField
5
+ attr_reader :name
6
+ attr_reader :options
7
+
8
+ def initialize(name, options = {})
9
+ options.symbolize_keys!
10
+ options.assert_valid_keys(
11
+ :default
12
+ )
13
+
14
+ @name = name.to_sym
15
+ @options = options
16
+ end
17
+
18
+ def default_value
19
+ options[:default]
20
+ end
21
+ end
22
+
23
+ class FilterGroup
24
+ attr_reader :name
25
+ attr_reader :scopes
26
+ attr_reader :options
27
+
28
+ def initialize(name, scopes, options = {})
29
+ options.symbolize_keys!
30
+ options.assert_valid_keys(
31
+ :include_empty_scope,
32
+ :default
33
+ )
34
+
35
+ @name = name.to_sym
36
+ @scopes = scopes.map(&:to_sym)
37
+ @options = options
38
+ end
39
+
40
+ def include_empty_scope?
41
+ @options.fetch(:include_empty_scope) { false }
42
+ end
43
+
44
+ def default_scope
45
+ if options[:default]
46
+ options[:default].to_sym
47
+ end
48
+ end
49
+ end
50
+
51
+ class Sorting
52
+ attr_reader :scopes
53
+ attr_reader :default_scope
54
+ attr_reader :default_direction
55
+
56
+ def initialize(scopes, options = {})
57
+ options.symbolize_keys!
58
+ options.assert_valid_keys(:default_scope, :default_direction)
59
+
60
+ @scopes = scopes.map(&:to_sym)
61
+ @default_scope = if options[:default_scope]
62
+ options[:default_scope].to_sym
63
+ end
64
+
65
+ @default_direction = if options[:default_direction]
66
+ options[:default_direction].to_sym
67
+ end
68
+ end
69
+ end
70
+
71
+ attr_reader :search_fields
72
+ attr_reader :filter_groups
73
+ attr_reader :sorting
74
+ attr_accessor :starting_scope_callable
75
+ attr_accessor :ending_scope_callable
76
+
77
+ def initialize
78
+ @search_fields = []
79
+ @filter_groups = []
80
+ end
81
+
82
+ def add_search_field(name, options = {})
83
+ SearchField.new(name, options).tap do |search_field|
84
+ self.search_fields << search_field
85
+ end
86
+ end
87
+
88
+ def add_filter_group(name, scopes, options = {})
89
+ FilterGroup.new(name, scopes, options).tap do |filter_group|
90
+ self.filter_groups << filter_group
91
+ end
92
+ end
93
+
94
+ def add_sorting_scopes(scopes, options = {})
95
+ @sorting = Sorting.new(scopes, options)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
@@ -0,0 +1,38 @@
1
+ module Admino
2
+ module Query
3
+ module Dsl
4
+ def config
5
+ @config ||= Admino::Query::Configuration.new
6
+ end
7
+
8
+ def search_field(name, options = {})
9
+ config.add_search_field(name, options)
10
+
11
+ define_method name do
12
+ search_field_by_name(name).value
13
+ end
14
+ end
15
+
16
+ def filter_by(name, scopes, options = {})
17
+ config.add_filter_group(name, scopes, options)
18
+
19
+ define_method name do
20
+ filter_group_by_name(name).value.to_s
21
+ end
22
+ end
23
+
24
+ def sorting(*args)
25
+ options = args.extract_options!
26
+ config.add_sorting_scopes(args, options)
27
+ end
28
+
29
+ def starting_scope(&block)
30
+ config.starting_scope_callable = block
31
+ end
32
+
33
+ def ending_scope(&block)
34
+ config.ending_scope_callable = block
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ module Admino
5
+ module Query
6
+ class FilterGroup
7
+ attr_reader :params
8
+ attr_reader :config
9
+ attr_reader :query_i18n_key
10
+
11
+ def initialize(config, params, query_i18n_key = nil)
12
+ @config = config
13
+ @params = ActiveSupport::HashWithIndifferentAccess.new(params)
14
+ @query_i18n_key = query_i18n_key
15
+ end
16
+
17
+ def augment_scope(scope)
18
+ if active_scope && active_scope != :empty
19
+ scope.send(active_scope)
20
+ else
21
+ scope
22
+ end
23
+ end
24
+
25
+ def active_scope
26
+ if value && scopes.include?(value)
27
+ value
28
+ else
29
+ nil
30
+ end
31
+ end
32
+
33
+ def is_scope_active?(scope)
34
+ active_scope == scope.to_sym
35
+ end
36
+
37
+ def value
38
+ value = params.fetch(:query, {}).fetch(param_name, default_value)
39
+ if value
40
+ value.to_sym
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def default_value
47
+ if config.default_scope
48
+ config.default_scope
49
+ elsif config.include_empty_scope?
50
+ :empty
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def param_name
57
+ config.name
58
+ end
59
+
60
+ def scopes
61
+ @scopes ||= config.scopes.dup.tap do |scopes|
62
+ if config.include_empty_scope?
63
+ scopes.unshift :empty
64
+ end
65
+ end
66
+ end
67
+
68
+ def i18n_key
69
+ config.name
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,85 @@
1
+ require 'i18n'
2
+ require 'showcase'
3
+ require 'showcase/helpers/html_options'
4
+ require 'active_support/core_ext/object/deep_dup'
5
+ require 'active_support/core_ext/array/extract_options'
6
+ require 'active_support/hash_with_indifferent_access'
7
+ require 'active_support/core_ext/hash'
8
+
9
+ module Admino
10
+ module Query
11
+ class FilterGroupPresenter < Showcase::Presenter
12
+ def scope_link(scope, *args)
13
+ options = args.extract_options!
14
+
15
+ label = args.first || scope_name(scope)
16
+
17
+ active_class = options.delete(:active_class) { 'is-active' }
18
+ options = Showcase::Helpers::HtmlOptions.new(options)
19
+
20
+ if is_scope_active?(scope)
21
+ options.add_class!(active_class)
22
+ end
23
+
24
+ h.link_to label, scope_path(scope), options.to_h
25
+ end
26
+
27
+ def scopes
28
+ @scopes ||= super.map do |scope|
29
+ ScopePresenter.new(scope, self, view_context)
30
+ end
31
+ end
32
+
33
+ def each_scope(&block)
34
+ scopes.each(&block)
35
+ end
36
+
37
+ def scope_path(scope)
38
+ h.request.path + "?" + scope_params(scope).to_query
39
+ end
40
+
41
+ def scope_params(scope)
42
+ params = ActiveSupport::HashWithIndifferentAccess.new(
43
+ h.request.query_parameters.deep_dup
44
+ )
45
+
46
+ params[:query] ||= {}
47
+
48
+ if is_scope_active?(scope)
49
+ params[:query].delete(param_name)
50
+ else
51
+ params[:query].merge!(param_name => scope.to_s)
52
+ end
53
+
54
+ if params[:query].empty?
55
+ params.delete(:query)
56
+ end
57
+
58
+ params
59
+ end
60
+
61
+ def scope_name(scope)
62
+ I18n.t(
63
+ :"#{query_i18n_key}.#{i18n_key}.scopes.#{scope}",
64
+ scope: 'query.filter_groups',
65
+ default: [
66
+ :"#{i18n_key}.scopes.#{scope}",
67
+ scope.to_s.titleize
68
+ ]
69
+ )
70
+ end
71
+
72
+ def name
73
+ I18n.t(
74
+ :"#{query_i18n_key}.#{i18n_key}.name",
75
+ scope: 'query.filter_groups',
76
+ default: [
77
+ :"#{i18n_key}.name",
78
+ i18n_key.to_s.titleize.capitalize
79
+ ]
80
+ )
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,17 @@
1
+ require 'showcase'
2
+
3
+ module Admino
4
+ module Query
5
+ class ScopePresenter < Showcase::Presenter
6
+ def initialize(object, parent, view_context)
7
+ super(object, view_context)
8
+ @parent = parent
9
+ end
10
+
11
+ def link(*args)
12
+ @parent.scope_link(object, *args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,43 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ module Admino
5
+ module Query
6
+ class SearchField
7
+ attr_reader :params
8
+ attr_reader :config
9
+
10
+ def initialize(config, params)
11
+ @config = config
12
+ @params = ActiveSupport::HashWithIndifferentAccess.new(params)
13
+ end
14
+
15
+ def augment_scope(scope)
16
+ if present?
17
+ scope.send(scope_name, value)
18
+ else
19
+ scope
20
+ end
21
+ end
22
+
23
+ def value
24
+ value = params.fetch(:query, {}).fetch(param_name, nil)
25
+
26
+ value || config.default_value
27
+ end
28
+
29
+ def present?
30
+ value.present?
31
+ end
32
+
33
+ def param_name
34
+ config.name
35
+ end
36
+
37
+ def scope_name
38
+ config.name
39
+ end
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,74 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ module Admino
5
+ module Query
6
+ class Sorting
7
+ attr_reader :params
8
+ attr_reader :config
9
+ attr_reader :query_i18n_key
10
+
11
+ def initialize(config, params, query_i18n_key = nil)
12
+ @config = config
13
+ @params = ActiveSupport::HashWithIndifferentAccess.new(params)
14
+ end
15
+
16
+ def augment_scope(scope)
17
+ if active_scope
18
+ scope.send(active_scope, ascending? ? :asc : :desc)
19
+ else
20
+ scope
21
+ end
22
+ end
23
+
24
+ def is_scope_active?(scope)
25
+ active_scope == scope.to_sym
26
+ end
27
+
28
+ def ascending?
29
+ if params[:sort_order] == 'desc'
30
+ false
31
+ elsif params[:sort_order].blank? && active_scope == default_scope
32
+ default_direction_is_ascending?
33
+ else
34
+ true
35
+ end
36
+ end
37
+
38
+ def active_scope
39
+ if param_value && scopes.include?(param_value.to_sym)
40
+ param_value.to_sym
41
+ elsif default_scope
42
+ default_scope
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ def default_scope
49
+ config.default_scope
50
+ end
51
+
52
+ def default_direction
53
+ config.default_direction
54
+ end
55
+
56
+ def default_direction_is_ascending?
57
+ default_direction != :desc
58
+ end
59
+
60
+ def param_value
61
+ params.fetch(param_name, nil)
62
+ end
63
+
64
+ def param_name
65
+ :sorting
66
+ end
67
+
68
+ def scopes
69
+ config.scopes
70
+ end
71
+ end
72
+ end
73
+ end
74
+