active_manageable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +52 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +42 -0
- data/.rubocop_rails.yml +201 -0
- data/.rubocop_rspec.yml +68 -0
- data/.standard.yml +5 -0
- data/Appraisals +27 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +194 -0
- data/LICENSE.txt +21 -0
- data/README.md +758 -0
- data/Rakefile +8 -0
- data/active_manageable.gemspec +75 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_6_0.gemfile +8 -0
- data/gemfiles/rails_6_1.gemfile +8 -0
- data/gemfiles/rails_7_0.gemfile +8 -0
- data/lib/active_manageable/authorization/cancancan.rb +28 -0
- data/lib/active_manageable/authorization/pundit.rb +28 -0
- data/lib/active_manageable/base.rb +218 -0
- data/lib/active_manageable/configuration.rb +58 -0
- data/lib/active_manageable/methods/auxiliary/includes.rb +98 -0
- data/lib/active_manageable/methods/auxiliary/model_attributes.rb +59 -0
- data/lib/active_manageable/methods/auxiliary/order.rb +43 -0
- data/lib/active_manageable/methods/auxiliary/scopes.rb +59 -0
- data/lib/active_manageable/methods/auxiliary/select.rb +46 -0
- data/lib/active_manageable/methods/auxiliary/unique_search.rb +50 -0
- data/lib/active_manageable/methods/create.rb +20 -0
- data/lib/active_manageable/methods/destroy.rb +23 -0
- data/lib/active_manageable/methods/edit.rb +25 -0
- data/lib/active_manageable/methods/index.rb +49 -0
- data/lib/active_manageable/methods/new.rb +20 -0
- data/lib/active_manageable/methods/show.rb +25 -0
- data/lib/active_manageable/methods/update.rb +23 -0
- data/lib/active_manageable/pagination/kaminari.rb +39 -0
- data/lib/active_manageable/search/ransack.rb +38 -0
- data/lib/active_manageable/version.rb +5 -0
- data/lib/active_manageable.rb +43 -0
- metadata +373 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Auxiliary
|
4
|
+
module Scopes
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
# Sets the default scope(s) to use when fetching records in the index method
|
9
|
+
# if the index :options argument does not contain a :scopes key;
|
10
|
+
# accepting a scope name, a hash containing scope name and argument, or an array of names/hashes;
|
11
|
+
# also accepting a lambda/proc to execute to return a scope name or hash or array.
|
12
|
+
#
|
13
|
+
# For example:-
|
14
|
+
# default_scopes :electronic
|
15
|
+
# default_scopes {released_in_year: "1980"}
|
16
|
+
# default_scopes :rock, :electronic, {released_in_year: "1980"}
|
17
|
+
# default_scopes -> { index_scopes }
|
18
|
+
def default_scopes(*args)
|
19
|
+
defaults[:scopes] = args.first.is_a?(Proc) ? args.first : args
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
included do
|
24
|
+
private
|
25
|
+
|
26
|
+
def scopes(scopes)
|
27
|
+
get_scopes(scopes).each { |name, args| @target = @target.send(name, *args) }
|
28
|
+
@target
|
29
|
+
end
|
30
|
+
|
31
|
+
# Accepts a symbol or string scope name, hash containing scope name and argument,
|
32
|
+
# lambda/proc to execute to return scope(s) or an array of those types
|
33
|
+
# and when the argument is blank it uses the class default scopes.
|
34
|
+
# Converts the scopes to a hash of hashes with the key containing the scope name
|
35
|
+
# and value containing an array of scope arguments.
|
36
|
+
def get_scopes(scopes = nil)
|
37
|
+
scopes ||= defaults[:scopes]
|
38
|
+
scopes = scopes.is_a?(Hash) ? [scopes] : Array(scopes)
|
39
|
+
|
40
|
+
scopes.map do |scope|
|
41
|
+
case scope
|
42
|
+
when Symbol, String
|
43
|
+
{scope => []}
|
44
|
+
when Hash
|
45
|
+
# ensure values are an array so they can be passed to the scope using splat operator
|
46
|
+
scope.transform_values! { |v| Array(v) }
|
47
|
+
when Proc
|
48
|
+
# if the class default contains a lambda/proc that returns nil
|
49
|
+
# don't call get_scopes as we don't want to end up in an infinite loop
|
50
|
+
p_scopes = instance_exec(&scope)
|
51
|
+
get_scopes(p_scopes) if p_scopes.present?
|
52
|
+
end
|
53
|
+
end.compact.reduce({}, :merge)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Auxiliary
|
4
|
+
module Select
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
# Sets the default attributes to return in the SELECT statement used
|
9
|
+
# when fetching records in the index, show and edit methods
|
10
|
+
# if the methods :options argument does not contain a :select key;
|
11
|
+
# accepting either an array of attribute names or a lambda/proc
|
12
|
+
# to execute to return an array of attribute names;
|
13
|
+
# and optional :methods in which to use the attributes.
|
14
|
+
#
|
15
|
+
# For example:-
|
16
|
+
# default_select :name
|
17
|
+
# default_select :id, :name, methods: :show
|
18
|
+
# default_select -> { select_attributes }
|
19
|
+
# default_select -> { select_attributes }, methods: [:index, :edit]
|
20
|
+
def default_select(*attributes)
|
21
|
+
options = attributes.extract_options!.dup
|
22
|
+
attrs = attributes.first.is_a?(Proc) ? attributes.first : attributes
|
23
|
+
add_method_defaults(key: :select, value: attrs, methods: options[:methods])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
included do
|
28
|
+
private
|
29
|
+
|
30
|
+
def select(attributes)
|
31
|
+
@target = @target.select(attributes || get_default_select_attributes)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the default select attributes for the method
|
35
|
+
# from the class attribute that can contain an array of attribute names
|
36
|
+
# or a lambdas/procs to execute to return an array of attribute names
|
37
|
+
def get_default_select_attributes
|
38
|
+
default_selects = defaults[:select] || {}
|
39
|
+
attributes = default_selects[@current_method] || default_selects[:all] || []
|
40
|
+
attributes.is_a?(Proc) ? instance_exec(&attributes) : attributes
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Auxiliary
|
4
|
+
module UniqueSearch
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
# Specifies whether to use the distinct method when fetching records in the index method;
|
9
|
+
# accepting no argument to always return unique records or a hash with :if or :unless keyword
|
10
|
+
# and method name or lambda/proc to execute each time the index method is called.
|
11
|
+
#
|
12
|
+
# For example:-
|
13
|
+
# has_unique_search
|
14
|
+
# has_unique_search if: :method_name
|
15
|
+
# has_unique_search unless: -> { lambda }
|
16
|
+
def has_unique_search(**args)
|
17
|
+
self.unique_search = args.present? ? args.assert_valid_keys(:if, :unless) : true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
included do
|
22
|
+
class_attribute :unique_search, instance_writer: false, instance_predicate: false
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def unique_search?
|
27
|
+
case unique_search
|
28
|
+
when nil
|
29
|
+
false
|
30
|
+
when TrueClass, FalseClass
|
31
|
+
unique_search
|
32
|
+
when Hash
|
33
|
+
evaluate_condition(*unique_search.first)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate_condition(condition, method)
|
38
|
+
result = case method
|
39
|
+
when Symbol
|
40
|
+
method(method).call
|
41
|
+
when Proc
|
42
|
+
instance_exec(&method)
|
43
|
+
end
|
44
|
+
condition == :if ? result : !result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Create
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::ModelAttributes
|
8
|
+
|
9
|
+
def create(attributes:)
|
10
|
+
initialize_state(attributes: attributes)
|
11
|
+
|
12
|
+
@target = model_class.new(attribute_values)
|
13
|
+
authorize(record: @target)
|
14
|
+
|
15
|
+
@target.save
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Destroy
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::Includes
|
8
|
+
|
9
|
+
def destroy(id:, options: {})
|
10
|
+
initialize_state(options: options)
|
11
|
+
|
12
|
+
@target = model_class
|
13
|
+
includes(@options[:includes])
|
14
|
+
|
15
|
+
@target = @target.find(id)
|
16
|
+
authorize(record: @target)
|
17
|
+
|
18
|
+
@target.destroy
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Edit
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::Includes
|
8
|
+
include ActiveManageable::Methods::Auxiliary::Select
|
9
|
+
|
10
|
+
def edit(id:, options: {})
|
11
|
+
initialize_state(options: options)
|
12
|
+
|
13
|
+
@target = model_class
|
14
|
+
includes(@options[:includes])
|
15
|
+
select(@options[:select])
|
16
|
+
|
17
|
+
@target = @target.find(id)
|
18
|
+
authorize(record: @target)
|
19
|
+
|
20
|
+
@target
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Index
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::Order
|
8
|
+
include ActiveManageable::Methods::Auxiliary::Scopes
|
9
|
+
include ActiveManageable::Methods::Auxiliary::Includes
|
10
|
+
include ActiveManageable::Methods::Auxiliary::Select
|
11
|
+
include ActiveManageable::Methods::Auxiliary::UniqueSearch
|
12
|
+
|
13
|
+
def index(options: {})
|
14
|
+
initialize_state(options: options)
|
15
|
+
|
16
|
+
@target = scoped_class
|
17
|
+
authorize(record: model_class)
|
18
|
+
search(@options[:search])
|
19
|
+
order(@options[:order])
|
20
|
+
scopes(@options[:scopes])
|
21
|
+
page(@options[:page])
|
22
|
+
includes(@options[:includes])
|
23
|
+
select(@options[:select])
|
24
|
+
distinct(unique_search?)
|
25
|
+
|
26
|
+
@target
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def scoped_class
|
32
|
+
model_class
|
33
|
+
end
|
34
|
+
|
35
|
+
def search(opts)
|
36
|
+
@target
|
37
|
+
end
|
38
|
+
|
39
|
+
def page(opts)
|
40
|
+
@target
|
41
|
+
end
|
42
|
+
|
43
|
+
def distinct(value)
|
44
|
+
@target = target.distinct(value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module New
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::ModelAttributes
|
8
|
+
|
9
|
+
def new(attributes: {})
|
10
|
+
initialize_state(attributes: attributes)
|
11
|
+
|
12
|
+
@target = model_class.new(attribute_values)
|
13
|
+
authorize(record: @target)
|
14
|
+
|
15
|
+
@target
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Show
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::Includes
|
8
|
+
include ActiveManageable::Methods::Auxiliary::Select
|
9
|
+
|
10
|
+
def show(id:, options: {})
|
11
|
+
initialize_state(options: options)
|
12
|
+
|
13
|
+
@target = model_class
|
14
|
+
includes(@options[:includes])
|
15
|
+
select(@options[:select])
|
16
|
+
|
17
|
+
@target = @target.find(id)
|
18
|
+
authorize(record: @target)
|
19
|
+
|
20
|
+
@target
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveManageable
|
2
|
+
module Methods
|
3
|
+
module Update
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveManageable::Methods::Auxiliary::Includes
|
8
|
+
|
9
|
+
def update(id:, attributes:, options: {})
|
10
|
+
initialize_state(attributes: attributes, options: options)
|
11
|
+
|
12
|
+
@target = model_class
|
13
|
+
includes(@options[:includes])
|
14
|
+
|
15
|
+
@target = @target.find(id)
|
16
|
+
authorize(record: @target)
|
17
|
+
|
18
|
+
@target.update(@attributes)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
# Pagination methods for Kaminari
|
3
|
+
# https://github.com/kaminari/kaminari
|
4
|
+
#
|
5
|
+
module ActiveManageable
|
6
|
+
module Pagination
|
7
|
+
module Kaminari
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
# Sets the default page size to use when fetching records in the index method
|
12
|
+
# if the index :options argument does not contain a :page hash with :size key;
|
13
|
+
# accepting an integer.
|
14
|
+
#
|
15
|
+
# For example:-
|
16
|
+
# default_page_size 5
|
17
|
+
def default_page_size(page_size)
|
18
|
+
defaults[:page] = {size: page_size.to_i}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
included do
|
23
|
+
private
|
24
|
+
|
25
|
+
def page(opts)
|
26
|
+
@target = @target.page(page_number(opts)).per(page_size(opts))
|
27
|
+
end
|
28
|
+
|
29
|
+
def page_number(opts)
|
30
|
+
opts.try(:[], :number)
|
31
|
+
end
|
32
|
+
|
33
|
+
def page_size(opts)
|
34
|
+
opts.try(:[], :size) || defaults.dig(:page, :size)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# Search methods for Ransack
|
3
|
+
# https://github.com/activerecord-hackery/ransack
|
4
|
+
#
|
5
|
+
module ActiveManageable
|
6
|
+
module Search
|
7
|
+
module Ransack
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
attr_reader :ransack
|
12
|
+
|
13
|
+
initialize_state_methods :initialize_ransack_state
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def initialize_ransack_state
|
18
|
+
@ransack = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def search(opts)
|
22
|
+
@ransack = @target.ransack(opts)
|
23
|
+
@target = @ransack.result
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform standard index module ordering when no ransack search params provided
|
27
|
+
# or no ransack sorts params provided
|
28
|
+
def order(attributes)
|
29
|
+
if @ransack.blank? || @ransack.sorts.empty?
|
30
|
+
@target = @target.order(get_order_attributes(attributes))
|
31
|
+
else
|
32
|
+
@target
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Active Support Core Extensions
|
4
|
+
# https://guides.rubyonrails.org/active_support_core_extensions.html
|
5
|
+
require "active_support/core_ext"
|
6
|
+
# rails-i18n required for "number.format.separator"
|
7
|
+
require "rails-i18n"
|
8
|
+
# flexitime required to parse date & datetime attribute values
|
9
|
+
require "flexitime"
|
10
|
+
|
11
|
+
require_relative "active_manageable/version"
|
12
|
+
require_relative "active_manageable/configuration"
|
13
|
+
require_relative "active_manageable/base"
|
14
|
+
require_relative "active_manageable/methods/index"
|
15
|
+
require_relative "active_manageable/methods/show"
|
16
|
+
require_relative "active_manageable/methods/new"
|
17
|
+
require_relative "active_manageable/methods/create"
|
18
|
+
require_relative "active_manageable/methods/edit"
|
19
|
+
require_relative "active_manageable/methods/update"
|
20
|
+
require_relative "active_manageable/methods/destroy"
|
21
|
+
require_relative "active_manageable/methods/auxiliary/includes"
|
22
|
+
require_relative "active_manageable/methods/auxiliary/model_attributes"
|
23
|
+
require_relative "active_manageable/methods/auxiliary/order"
|
24
|
+
require_relative "active_manageable/methods/auxiliary/scopes"
|
25
|
+
require_relative "active_manageable/methods/auxiliary/select"
|
26
|
+
require_relative "active_manageable/methods/auxiliary/unique_search"
|
27
|
+
require_relative "active_manageable/authorization/pundit"
|
28
|
+
require_relative "active_manageable/authorization/cancancan"
|
29
|
+
require_relative "active_manageable/search/ransack"
|
30
|
+
require_relative "active_manageable/pagination/kaminari"
|
31
|
+
|
32
|
+
module ActiveManageable
|
33
|
+
ALL_METHODS = "*"
|
34
|
+
|
35
|
+
thread_mattr_accessor :current_user
|
36
|
+
|
37
|
+
mattr_accessor :configuration
|
38
|
+
@@configuration = Configuration.new
|
39
|
+
|
40
|
+
def self.config
|
41
|
+
yield configuration
|
42
|
+
end
|
43
|
+
end
|