active_manageable 0.1.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +52 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +42 -0
  6. data/.rubocop_rails.yml +201 -0
  7. data/.rubocop_rspec.yml +68 -0
  8. data/.standard.yml +5 -0
  9. data/Appraisals +27 -0
  10. data/CHANGELOG.md +5 -0
  11. data/CODE_OF_CONDUCT.md +84 -0
  12. data/Gemfile +6 -0
  13. data/Gemfile.lock +194 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +758 -0
  16. data/Rakefile +8 -0
  17. data/active_manageable.gemspec +75 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/gemfiles/.bundle/config +2 -0
  21. data/gemfiles/rails_6_0.gemfile +8 -0
  22. data/gemfiles/rails_6_1.gemfile +8 -0
  23. data/gemfiles/rails_7_0.gemfile +8 -0
  24. data/lib/active_manageable/authorization/cancancan.rb +28 -0
  25. data/lib/active_manageable/authorization/pundit.rb +28 -0
  26. data/lib/active_manageable/base.rb +218 -0
  27. data/lib/active_manageable/configuration.rb +58 -0
  28. data/lib/active_manageable/methods/auxiliary/includes.rb +98 -0
  29. data/lib/active_manageable/methods/auxiliary/model_attributes.rb +59 -0
  30. data/lib/active_manageable/methods/auxiliary/order.rb +43 -0
  31. data/lib/active_manageable/methods/auxiliary/scopes.rb +59 -0
  32. data/lib/active_manageable/methods/auxiliary/select.rb +46 -0
  33. data/lib/active_manageable/methods/auxiliary/unique_search.rb +50 -0
  34. data/lib/active_manageable/methods/create.rb +20 -0
  35. data/lib/active_manageable/methods/destroy.rb +23 -0
  36. data/lib/active_manageable/methods/edit.rb +25 -0
  37. data/lib/active_manageable/methods/index.rb +49 -0
  38. data/lib/active_manageable/methods/new.rb +20 -0
  39. data/lib/active_manageable/methods/show.rb +25 -0
  40. data/lib/active_manageable/methods/update.rb +23 -0
  41. data/lib/active_manageable/pagination/kaminari.rb +39 -0
  42. data/lib/active_manageable/search/ransack.rb +38 -0
  43. data/lib/active_manageable/version.rb +5 -0
  44. data/lib/active_manageable.rb +43 -0
  45. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveManageable
4
+ VERSION = "0.1.0"
5
+ 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