ibrain-core 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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +75 -0
  4. data/Rakefile +7 -0
  5. data/app/controllers/concerns/ibrain_errors.rb +23 -0
  6. data/app/controllers/concerns/ibrain_handler.rb +42 -0
  7. data/app/controllers/ibrain/base_controller.rb +28 -0
  8. data/app/controllers/ibrain/graphql_controller.rb +55 -0
  9. data/app/graphql/ibrain/base_schema.rb +52 -0
  10. data/app/graphql/ibrain/extentions/default_value.rb +15 -0
  11. data/app/graphql/ibrain/interfaces/base_interface.rb +5 -0
  12. data/app/graphql/ibrain/interfaces/person_interface.rb +15 -0
  13. data/app/graphql/ibrain/interfaces/record_interface.rb +10 -0
  14. data/app/graphql/ibrain/lazy/base.rb +8 -0
  15. data/app/graphql/ibrain/loaders/association_loader.rb +61 -0
  16. data/app/graphql/ibrain/mutations/base_mutation.rb +35 -0
  17. data/app/graphql/ibrain/policies/base_policy.rb +45 -0
  18. data/app/graphql/ibrain/policies/graphql_policy.rb +8 -0
  19. data/app/graphql/ibrain/resolvers/base_aggregate.rb +9 -0
  20. data/app/graphql/ibrain/resolvers/base_resolver.rb +15 -0
  21. data/app/graphql/ibrain/types/aggregate_type.rb +9 -0
  22. data/app/graphql/ibrain/types/base_argument.rb +11 -0
  23. data/app/graphql/ibrain/types/base_connection.rb +16 -0
  24. data/app/graphql/ibrain/types/base_edge.rb +10 -0
  25. data/app/graphql/ibrain/types/base_enum.rb +8 -0
  26. data/app/graphql/ibrain/types/base_field.rb +15 -0
  27. data/app/graphql/ibrain/types/base_input_object.rb +9 -0
  28. data/app/graphql/ibrain/types/base_interface.rb +14 -0
  29. data/app/graphql/ibrain/types/base_node.rb +13 -0
  30. data/app/graphql/ibrain/types/base_object.rb +14 -0
  31. data/app/graphql/ibrain/types/base_query_type.rb +14 -0
  32. data/app/graphql/ibrain/types/base_scalar.rb +8 -0
  33. data/app/graphql/ibrain/types/base_union.rb +10 -0
  34. data/app/graphql/ibrain/types/filter_type.rb +8 -0
  35. data/app/graphql/ibrain/types/node_type.rb +11 -0
  36. data/app/graphql/ibrain/util/field_combiner.rb +13 -0
  37. data/app/graphql/ibrain/util/query_combiner.rb +13 -0
  38. data/app/graphql/mutations/insert_user.rb +18 -0
  39. data/app/models/ibrain/ability.rb +51 -0
  40. data/app/models/ibrain/application_record.rb +7 -0
  41. data/app/models/ibrain/base.rb +47 -0
  42. data/app/models/ibrain/concerns/ransackable_attributes.rb +22 -0
  43. data/app/models/ibrain/concerns/soft_deletable.rb +16 -0
  44. data/app/models/ibrain/concerns/user_api_authentication.rb +23 -0
  45. data/app/models/ibrain/concerns/user_methods.rb +25 -0
  46. data/app/models/ibrain/legacy_user.rb +19 -0
  47. data/app/models/ibrain/role.rb +14 -0
  48. data/app/models/ibrain/role_user.rb +18 -0
  49. data/config/initializers/friendly_id.rb +87 -0
  50. data/config/locales/en.yml +10 -0
  51. data/config/locales/jp.yml +10 -0
  52. data/config/locales/vi.yml +10 -0
  53. data/config/routes.rb +9 -0
  54. data/lib/generators/ibrain/graphql/core.rb +75 -0
  55. data/lib/generators/ibrain/graphql/mutation_generator.rb +58 -0
  56. data/lib/generators/ibrain/graphql/object_generator.rb +80 -0
  57. data/lib/generators/ibrain/graphql/resolver_generator.rb +33 -0
  58. data/lib/generators/ibrain/graphql/resolvers_generator.rb +59 -0
  59. data/lib/generators/ibrain/graphql/templates/aggregate.erb +10 -0
  60. data/lib/generators/ibrain/graphql/templates/mutation.erb +16 -0
  61. data/lib/generators/ibrain/graphql/templates/object.erb +11 -0
  62. data/lib/generators/ibrain/graphql/templates/resolver.erb +15 -0
  63. data/lib/generators/ibrain/graphql/templates/resolvers.erb +13 -0
  64. data/lib/generators/ibrain/graphql/type_generator.rb +101 -0
  65. data/lib/generators/ibrain/install/install_generator.rb +189 -0
  66. data/lib/generators/ibrain/install/templates/config/database.tt +23 -0
  67. data/lib/generators/ibrain/install/templates/config/initializers/cors.tt +25 -0
  68. data/lib/generators/ibrain/install/templates/config/initializers/ibrain.rb.tt +55 -0
  69. data/lib/generators/ibrain/install/templates/config/puma.tt +43 -0
  70. data/lib/generators/ibrain/install/templates/graphql/app_schema.rb.tt +4 -0
  71. data/lib/generators/ibrain/install/templates/graphql/types/mutation_type.rb.tt +10 -0
  72. data/lib/generators/ibrain/install/templates/graphql/types/query_type.rb.tt +13 -0
  73. data/lib/ibrain/app_configuration.rb +66 -0
  74. data/lib/ibrain/config.rb +5 -0
  75. data/lib/ibrain/core/class_constantizer.rb +41 -0
  76. data/lib/ibrain/core/controller_helpers/auth.rb +64 -0
  77. data/lib/ibrain/core/controller_helpers/current_host.rb +17 -0
  78. data/lib/ibrain/core/controller_helpers/response.rb +52 -0
  79. data/lib/ibrain/core/controller_helpers/strong_parameters.rb +21 -0
  80. data/lib/ibrain/core/engine.rb +16 -0
  81. data/lib/ibrain/core/environment.rb +17 -0
  82. data/lib/ibrain/core/environment_extension.rb +27 -0
  83. data/lib/ibrain/core/role_configuration.rb +72 -0
  84. data/lib/ibrain/core/validators/email.rb +23 -0
  85. data/lib/ibrain/core/version.rb +17 -0
  86. data/lib/ibrain/core/versioned_value.rb +73 -0
  87. data/lib/ibrain/core.rb +86 -0
  88. data/lib/ibrain/encryptor.rb +27 -0
  89. data/lib/ibrain/i18n.rb +17 -0
  90. data/lib/ibrain/logger.rb +23 -0
  91. data/lib/ibrain/permission_sets/base.rb +33 -0
  92. data/lib/ibrain/permission_sets/super_user.rb +11 -0
  93. data/lib/ibrain/permission_sets.rb +4 -0
  94. data/lib/ibrain/permitted_attributes.rb +26 -0
  95. data/lib/ibrain/preferences/configuration.rb +170 -0
  96. data/lib/ibrain/preferences/preferable.rb +183 -0
  97. data/lib/ibrain/preferences/preferable_class_methods.rb +140 -0
  98. data/lib/ibrain/user_class_handle.rb +29 -0
  99. data/lib/ibrain_core.rb +3 -0
  100. data/lib/tasks/ibrain/auto_annotate_models.rake +61 -0
  101. data/lib/tasks/ibrain/core_tasks.rake +5 -0
  102. data/lib/tasks/ibrain/ridgepole.rake +37 -0
  103. metadata +293 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibrain
4
+ # Ibrain::Encryptor is a thin wrapper around ActiveSupport::MessageEncryptor.
5
+ class Encryptor
6
+ # @param key [String] the 256 bits signature key
7
+ def initialize(key = Ibrain::Config.ibrain_encryptor_key)
8
+ key = Rails.application.secrets.secret_key_base.byteslice(0..31) if key.blank?
9
+
10
+ @crypt = ActiveSupport::MessageEncryptor.new(key)
11
+ end
12
+
13
+ # Encrypt a value
14
+ # @param value [String] the value to encrypt
15
+ # @return [String] the encrypted value
16
+ def encrypt(value)
17
+ @crypt.encrypt_and_sign(value)
18
+ end
19
+
20
+ # Decrypt an encrypted value
21
+ # @param encrypted_value [String] the value to decrypt
22
+ # @return [String] the decrypted value
23
+ def decrypt(encrypted_value)
24
+ @crypt.decrypt_and_verify(encrypted_value)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n'
4
+
5
+ module Ibrain
6
+ def self.i18n_available_locales
7
+ I18n.available_locales.select do |locale|
8
+ I18n.t('ibrain.i18n.this_file_language', locale: locale, fallback: false, default: nil)
9
+ end
10
+ end
11
+
12
+ # This value is used as a count for the pluralization helpers related to I18n
13
+ # ex: Ibrain::Order.model_name.human(count: Ibrain::I18N_GENERIC_PLURAL)
14
+ # Related to Ibrain issue #1164, this is needed to avoid problems with
15
+ # some pluralization calculators
16
+ I18N_GENERIC_PLURAL = 2.1
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibrain
4
+ class Logger
5
+ class << self
6
+ def info(message)
7
+ Rails.logger.info("[Ibrain] #{message}")
8
+ end
9
+
10
+ def warn(message)
11
+ Rails.logger.warn("[Ibrain] #{message}")
12
+ end
13
+
14
+ def debug(message)
15
+ Rails.logger.debug { "[Ibrain] #{message}" }
16
+ end
17
+
18
+ def error(message)
19
+ Rails.logger.error("[Ibrain] #{message}")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibrain
4
+ module PermissionSets
5
+ # This is the base class used for crafting permission sets.
6
+ #
7
+ # This is used by {Ibrain::RoleConfiguration} when adding custom behavior to {Ibrain::Ability}.
8
+ # See one of the subclasses for example structure such as {Ibrain::PermissionSets::UserDisplay}
9
+ #
10
+ # @see Ibrain::RoleConfiguration
11
+ # @see Ibrain::PermissionSets
12
+ class Base
13
+ # @param ability [CanCan::Ability]
14
+ # The ability that will be extended with the current permission set.
15
+ # The ability passed in must respond to #user
16
+ def initialize(ability)
17
+ @ability = ability
18
+ end
19
+
20
+ # Activate permissions on the ability. Put your can and cannot statements here.
21
+ # Must be overriden by subclasses
22
+ def activate!
23
+ raise NotImplementedError.new
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :ability
29
+
30
+ delegate :can, :cannot, :user, to: :ability
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibrain
4
+ module PermissionSets
5
+ class SuperUser < PermissionSets::Base
6
+ def activate!
7
+ can :manage, :all
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ibrain/permission_sets/base'
4
+ require 'ibrain/permission_sets/super_user'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibrain
4
+ # Ibrain::PermittedAttributes contains the attributes permitted through strong
5
+ # params in various controllers in the frontend. Extensions and stores that
6
+ # need additional params to be accepted can mutate these arrays to add them.
7
+ module PermittedAttributes
8
+ ATTRIBUTES = [
9
+ :address_attributes,
10
+ :user_attributes
11
+ ]
12
+
13
+ mattr_reader(*ATTRIBUTES)
14
+
15
+ @@address_attributes = [
16
+ :id, :name, :address1, :address2, :city, :country_id, :state_id,
17
+ :zipcode, :phone, :state_name, :province_id, :ward_id, :district_id
18
+ ]
19
+
20
+ # Intentionally leaving off email here to prevent privilege escalation
21
+ # by changing a user with higher priveleges' email to one a lower-priveleged
22
+ # admin owns. Creating a user with an email is handled separate at the
23
+ # controller level.
24
+ @@user_attributes = [:name, :email, :provider, :uid, :first_name, :last_name, :password, :password_confirmation]
25
+ end
26
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ibrain/core/versioned_value'
4
+ require 'ibrain/preferences/preferable'
5
+
6
+ module Ibrain::Preferences
7
+ # This takes the preferrable methods and adds some
8
+ # syntatic sugar to access the preferences
9
+ #
10
+ # class App < Configuration
11
+ # preference :color, :string
12
+ # end
13
+ #
14
+ # a = App.new
15
+ #
16
+ # Provides the following setters:
17
+ #
18
+ # a.color = :blue
19
+ # a[:color] = :blue
20
+ # a.set color: :blue
21
+ # a.preferred_color = :blue
22
+ #
23
+ # and the following getters:
24
+ #
25
+ # a.color
26
+ # a[:color]
27
+ # a.get :color
28
+ # a.preferred_color
29
+ #
30
+ class Configuration
31
+ include Ibrain::Preferences::Preferable
32
+
33
+ # @!attribute [r] loaded_defaults
34
+ # @return [String]
35
+ # Some configuration defaults can be added or changed when a new Ibrain
36
+ # version is released. Setting this to an older Ibrain version allows keeping
37
+ # backward compatibility until the application code is updated to the new
38
+ # defaults. Set via {#load_defaults}
39
+ attr_reader :loaded_defaults
40
+
41
+ attr_reader :load_defaults_called
42
+
43
+ def initialize
44
+ @loaded_defaults = Ibrain.ibrain_version
45
+ @load_defaults_called = false
46
+ end
47
+
48
+ # @param [String] Ibrain version from which take defaults when not
49
+ # overriden.
50
+ # @see #load_defaults
51
+ def load_defaults(version)
52
+ @loaded_defaults = version
53
+ @load_defaults_called = true
54
+ reset
55
+ end
56
+
57
+ def check_load_defaults_called(instance_constant_name = nil)
58
+ return if load_defaults_called || !Ibrain::Core.has_install_generator_been_run?
59
+
60
+ target_name = instance_constant_name || "#{self.class.name}.new"
61
+ Ibrain::Deprecation.warn <<~MSG
62
+ It's recommended that you explicitly load the default configuration for
63
+ your current Ibrain version. You can do it by adding the following call
64
+ to your Ibrain initializer within the #{target_name} block:
65
+
66
+ config.load_defaults('#{Ibrain.ibrain_version}')
67
+
68
+ MSG
69
+ end
70
+
71
+ # @yield [config] Yields this configuration object to a block
72
+ def configure
73
+ yield(self)
74
+ end
75
+
76
+ # @!attribute preference_store
77
+ # Storage method for preferences.
78
+ attr_writer :preference_store
79
+
80
+ def preference_store
81
+ @preference_store ||= default_preferences
82
+ end
83
+
84
+ # Replace the default legacy preference store, which stores preferences in
85
+ # the Ibrain_preferences table, with a plain in memory hash. This is faster
86
+ # and less error prone.
87
+ #
88
+ # This will set all preferences to their default values.
89
+ #
90
+ # These won't be loaded from or persisted to the database, so any desired
91
+ # changes must be made each time the application is started, such as in an
92
+ # initializer.
93
+ def use_static_preferences!
94
+ @preference_store = default_preferences
95
+ end
96
+
97
+ # Replace the new static preference store with the legacy store which
98
+ # fetches preferences from the DB.
99
+ def use_legacy_db_preferences!
100
+ @preference_store = ScopedStore.new(self.class.name.underscore)
101
+ end
102
+
103
+ alias_method :preferences, :preference_store
104
+
105
+ # Reset all preferences to their default values.
106
+ def reset
107
+ set(default_preferences)
108
+ end
109
+
110
+ alias :[] :get_preference
111
+ alias :[]= :set_preference
112
+
113
+ alias :get :get_preference
114
+
115
+ # @param preferences [Hash] a hash of preferences to set
116
+ def set(preferences)
117
+ preferences.each do |name, value|
118
+ set_preference name, value
119
+ end
120
+ end
121
+
122
+ # Generates a different preference default depending on {#version_defaults}
123
+ #
124
+ # This method is meant to be used in the `default:` keyword argument for
125
+ # {.preference}. For instance, in the example, `foo`'s default was `true`
126
+ # until version 3.0.0.alpha, when it became `false`:
127
+ #
128
+ # @example
129
+ # preference :foo, :boolean, default: by_version(true, "3.0.0.alpha" => false)
130
+ #
131
+ # @see #loaded_defaults
132
+ # @see Ibrain::Core::VersionedValue
133
+ def self.by_version(*args)
134
+ proc do |loaded_defaults|
135
+ Ibrain::Core::VersionedValue.new(*args).call(loaded_defaults)
136
+ end
137
+ end
138
+
139
+ def self.preference(name, type, options = {})
140
+ super
141
+ alias_method name.to_s, "preferred_#{name}"
142
+ alias_method "#{name}=", "preferred_#{name}="
143
+ end
144
+
145
+ def self.class_name_attribute(name, default:)
146
+ ivar = :"@#{name}"
147
+
148
+ define_method("#{name}=") do |class_name|
149
+ # If this is a named class constant, we should store it as a string to
150
+ # allow code reloading.
151
+ class_name = class_name.name if class_name.is_a?(Class) && class_name.name
152
+
153
+ instance_variable_set(ivar, class_name)
154
+ end
155
+
156
+ define_method(name) do
157
+ class_name = instance_variable_get(ivar)
158
+ class_name ||= default
159
+ class_name = class_name.constantize if class_name.is_a?(String)
160
+ class_name
161
+ end
162
+ end
163
+
164
+ private
165
+
166
+ def context_for_default
167
+ [loaded_defaults]
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ibrain/preferences/preferable_class_methods'
4
+ require 'active_support/concern'
5
+ require 'active_support/core_ext/hash/keys'
6
+
7
+ module Ibrain
8
+ module Preferences
9
+ # Preferable allows defining preference accessor methods.
10
+ #
11
+ # A class including Preferable must implement #preferences which should return
12
+ # an object responding to .fetch(key), []=(key, val), and .delete(key).
13
+ #
14
+ # It may also define a `#context_for_default` method. It should return an
15
+ # array with the arguments to be provided to a proc used as the `default:`
16
+ # keyword for a preference.
17
+ #
18
+ # The generated writer method performs typecasting before assignment into the
19
+ # preferences object.
20
+ #
21
+ # Examples:
22
+ #
23
+ # # Ibrain::Base includes Preferable and defines preferences as a serialized
24
+ # # column.
25
+ # class Settings < Ibrain::Base
26
+ # preference :color, :string, default: 'red'
27
+ # preference :temperature, :integer, default: 21
28
+ # end
29
+ #
30
+ # s = Settings.new
31
+ # s.preferred_color # => 'red'
32
+ # s.preferred_temperature # => 21
33
+ #
34
+ # s.preferred_color = 'blue'
35
+ # s.preferred_color # => 'blue'
36
+ #
37
+ # # Typecasting is performed on assignment
38
+ # s.preferred_temperature = '24'
39
+ # s.preferred_color # => 24
40
+ #
41
+ # # Modifications have been made to the .preferences hash
42
+ # s.preferences #=> {color: 'blue', temperature: 24}
43
+ #
44
+ # # Save the changes. All handled by activerecord
45
+ # s.save!
46
+ #
47
+ # Each preference gets rendered as a form field in Ibrain backend.
48
+ #
49
+ # As not all supported preference types are representable as a form field, only
50
+ # some of them get rendered per default. Arrays and Hashes for instance are
51
+ # supported preference field types, but do not represent well as a form field.
52
+ #
53
+ # Overwrite +allowed_admin_form_preference_types+ in your class if you want to
54
+ # provide more fields. If you do so, you also need to provide a preference field
55
+ # partial that lives in:
56
+ #
57
+ # +app/views/ibrain/admin/shared/preference_fields/+
58
+ #
59
+ module Preferable
60
+ extend ActiveSupport::Concern
61
+
62
+ included do
63
+ extend Ibrain::Preferences::PreferableClassMethods
64
+ end
65
+
66
+ # Get a preference
67
+ # @param name [#to_sym] name of preference
68
+ # @return [Object] The value of preference +name+
69
+ def get_preference(name)
70
+ has_preference! name
71
+ send self.class.preference_getter_method(name)
72
+ end
73
+
74
+ # Set a preference
75
+ # @param name [#to_sym] name of preference
76
+ # @param value [Object] new value for preference +name+
77
+ def set_preference(name, value)
78
+ has_preference! name
79
+ send self.class.preference_setter_method(name), value
80
+ end
81
+
82
+ # @param name [#to_sym] name of preference
83
+ # @return [Symbol] The type of preference +name+
84
+ def preference_type(name)
85
+ has_preference! name
86
+ send self.class.preference_type_getter_method(name)
87
+ end
88
+
89
+ # @param name [#to_sym] name of preference
90
+ # @return [Object] The default for preference +name+
91
+ def preference_default(name)
92
+ has_preference! name
93
+ send self.class.preference_default_getter_method(name)
94
+ end
95
+
96
+ # Raises an exception if the +name+ preference is not defined on this class
97
+ # @param name [#to_sym] name of preference
98
+ def has_preference!(name)
99
+ raise NoMethodError.new "#{name} preference not defined" unless has_preference? name
100
+ end
101
+
102
+ # @param name [#to_sym] name of preference
103
+ # @return [Boolean] if preference exists on this class
104
+ def has_preference?(name)
105
+ defined_preferences.include?(name.to_sym)
106
+ end
107
+
108
+ # @return [Array<Symbol>] All preferences defined on this class
109
+ def defined_preferences
110
+ self.class.defined_preferences
111
+ end
112
+
113
+ # @return [Hash{Symbol => Object}] Default for all preferences defined on this class
114
+ def default_preferences
115
+ defined_preferences.index_with do |preference|
116
+ preference_default(preference)
117
+ end
118
+ end
119
+
120
+ # Preference names representable as form fields in Ibrain backend
121
+ #
122
+ # Not all preferences are representable as a form field.
123
+ #
124
+ # Arrays and Hashes for instance are supported preference field types,
125
+ # but do not represent well as a form field.
126
+ #
127
+ # As these kind of preferences are mostly developer facing
128
+ # and not admin facing we should not render them.
129
+ #
130
+ # Overwrite +allowed_admin_form_preference_types+ in your class that
131
+ # includes +Ibrain::Preferable+ if you want to provide more fields.
132
+ # If you do so, you also need to provide a preference field partial
133
+ # that lives in:
134
+ #
135
+ # +app/views/ibrain/admin/shared/preference_fields/+
136
+ #
137
+ # @return [Array]
138
+
139
+ private
140
+
141
+ def convert_preference_value(value, type, preference_encryptor = nil)
142
+ return nil if value.nil?
143
+
144
+ case type
145
+ when :string, :text, :password
146
+ value.to_s
147
+ when :encrypted_string
148
+ preference_encryptor.encrypt(value.to_s)
149
+ when :decimal
150
+ begin
151
+ value.to_s.to_d
152
+ rescue ArgumentError
153
+ BigDecimal('0')
154
+ end
155
+ when :integer
156
+ value.to_i
157
+ when :boolean
158
+ if !value ||
159
+ value.to_s =~ /\A(f|false|0|^)\Z/i ||
160
+ (value.respond_to?(:empty?) && value.empty?)
161
+ false
162
+ else
163
+ true
164
+ end
165
+ when :array
166
+ raise TypeError, "Array expected got #{value.inspect}" unless value.is_a?(Array)
167
+
168
+ value
169
+ when :hash
170
+ raise TypeError, "Hash expected got #{value.inspect}" unless value.is_a?(Hash)
171
+
172
+ value
173
+ else
174
+ value
175
+ end
176
+ end
177
+
178
+ def context_for_default
179
+ [].freeze
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'ibrain/encryptor'
5
+
6
+ module Ibrain::Preferences
7
+ module PreferableClassMethods
8
+ DEFAULT_ADMIN_FORM_PREFERENCE_TYPES = %i(
9
+ boolean
10
+ decimal
11
+ integer
12
+ password
13
+ string
14
+ text
15
+ encrypted_string
16
+ )
17
+
18
+ def defined_preferences
19
+ []
20
+ end
21
+
22
+ def preference(name, type, options = {})
23
+ options.assert_valid_keys(:default, :encryption_key)
24
+
25
+ if type == :encrypted_string
26
+ preference_encryptor = preference_encryptor(options)
27
+ options[:default] = preference_encryptor.encrypt(options[:default])
28
+ end
29
+
30
+ default = begin
31
+ given = options[:default]
32
+ if self <= Ibrain::Preferences::Configuration &&
33
+ given.is_a?(Proc) &&
34
+ given.lambda? &&
35
+ given.arity.zero?
36
+ Ibrain::Logger.warn <<~MSG
37
+ The arity of a proc given as the default for a preference
38
+ has changed from 0 to 1 on Ibrain 3.1. The Ibrain
39
+ version for the loaded preference defaults is given as the
40
+ proc's argument from this point on.
41
+
42
+ If you don't need to return a different default value
43
+ depending on the loaded Ibrain version, you can change
44
+ the proc so that it doesn't have lambda semantics (lambdas
45
+ raise when extra arguments are supplied, while raw procs
46
+ don't). E.g.:
47
+
48
+ preference :foo, :string, default: proc { true }
49
+
50
+ If you want to branch on the provided Ibrain version, you can do like the following:
51
+
52
+ preference :foo, :string, default: by_version(true, "3.2.0" => false)
53
+
54
+ MSG
55
+ ->(_default_context) { given.call }
56
+ elsif given.is_a?(Proc)
57
+ given
58
+ else
59
+ proc { given }
60
+ end
61
+ end
62
+
63
+ # The defined preferences on a class are all those defined directly on
64
+ # that class as well as those defined on ancestors.
65
+ # We store these as a class instance variable on each class which has a
66
+ # preference. super() collects preferences defined on ancestors.
67
+ singleton_preferences = (@defined_singleton_preferences ||= [])
68
+ singleton_preferences << name.to_sym
69
+
70
+ define_singleton_method :defined_preferences do
71
+ super() + singleton_preferences
72
+ end
73
+
74
+ # cache_key will be nil for new objects, then if we check if there
75
+ # is a pending preference before going to default
76
+ define_method preference_getter_method(name) do
77
+ value = preferences.fetch(name) do
78
+ default.call(*context_for_default)
79
+ end
80
+ value = preference_encryptor.decrypt(value) if preference_encryptor.present?
81
+ value
82
+ end
83
+
84
+ define_method preference_setter_method(name) do |value|
85
+ value = convert_preference_value(value, type, preference_encryptor)
86
+ preferences[name] = value
87
+
88
+ # If this is an activerecord object, we need to inform
89
+ # ActiveRecord::Dirty that this value has changed, since this is an
90
+ # in-place update to the preferences hash.
91
+ preferences_will_change! if respond_to?(:preferences_will_change!)
92
+ end
93
+
94
+ define_method preference_default_getter_method(name) do
95
+ default.call(*context_for_default)
96
+ end
97
+
98
+ define_method preference_type_getter_method(name) do
99
+ type
100
+ end
101
+ end
102
+
103
+ def preference_getter_method(name)
104
+ "preferred_#{name}".to_sym
105
+ end
106
+
107
+ def preference_setter_method(name)
108
+ "preferred_#{name}=".to_sym
109
+ end
110
+
111
+ def preference_default_getter_method(name)
112
+ "preferred_#{name}_default".to_sym
113
+ end
114
+
115
+ def preference_type_getter_method(name)
116
+ "preferred_#{name}_type".to_sym
117
+ end
118
+
119
+ def preference_encryptor(options)
120
+ key = options[:encryption_key] ||
121
+ ENV['IBRAIN_PREFERENCES_MASTER_KEY'] ||
122
+ Rails.application.credentials.secret_key_base
123
+
124
+ Ibrain::Encryptor.new(key)
125
+ end
126
+
127
+ # List of preference types allowed as form fields in the Ibrain admin
128
+ #
129
+ # Overwrite this method in your class that includes +Ibrain::Preferable+
130
+ # if you want to provide more fields. If you do so, you also need to provide
131
+ # a preference field partial that lives in:
132
+ #
133
+ # +app/views/Ibrain/admin/shared/preference_fields/+
134
+ #
135
+ # @return [Array]
136
+ def allowed_admin_form_preference_types
137
+ DEFAULT_ADMIN_FORM_PREFERENCE_TYPES
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibrain
4
+ # Configuration point for User model implementation.
5
+ #
6
+ # `Ibrain::UserClassHandle` allows you to configure your own implementation of a
7
+ # User class or use an extension like `ibrain-auth`.
8
+ #
9
+ # @note Placeholder for name of Ibrain.user_class to ensure later evaluation at
10
+ # runtime.
11
+ #
12
+ # Unfortunately, it is possible for classes to get loaded before
13
+ # Ibrain.user_class has been set in the initializer. As a result, they end up
14
+ # with class_name: "" in their association definitions. For obvious reasons,
15
+ # that doesn't work.
16
+ #
17
+ # For now, Rails does not call to_s on the instance passed in until runtime.
18
+ # So this little hack provides a wrapper around Ibrain.user_class so that we
19
+ # can basically lazy-evaluate it. Yay! Problem solved forever.
20
+ class UserClassHandle
21
+ # @return [String] the name of the user class as a string.
22
+ # @raise [RuntimeError] if Ibrain.user_class is nil
23
+ def to_s
24
+ fail "'Ibrain.user_class' has not been set yet." unless Ibrain.user_class
25
+
26
+ "::#{Ibrain.user_class}"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ibrain/core'