active_manageable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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