ibrain-core 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/MIT-LICENSE +20 -0
- data/README.md +75 -0
- data/Rakefile +7 -0
- data/app/controllers/concerns/ibrain_errors.rb +23 -0
- data/app/controllers/concerns/ibrain_handler.rb +42 -0
- data/app/controllers/ibrain/base_controller.rb +28 -0
- data/app/controllers/ibrain/graphql_controller.rb +55 -0
- data/app/graphql/ibrain/base_schema.rb +52 -0
- data/app/graphql/ibrain/extentions/default_value.rb +15 -0
- data/app/graphql/ibrain/interfaces/base_interface.rb +5 -0
- data/app/graphql/ibrain/interfaces/person_interface.rb +15 -0
- data/app/graphql/ibrain/interfaces/record_interface.rb +10 -0
- data/app/graphql/ibrain/lazy/base.rb +8 -0
- data/app/graphql/ibrain/loaders/association_loader.rb +61 -0
- data/app/graphql/ibrain/mutations/base_mutation.rb +35 -0
- data/app/graphql/ibrain/policies/base_policy.rb +45 -0
- data/app/graphql/ibrain/policies/graphql_policy.rb +8 -0
- data/app/graphql/ibrain/resolvers/base_aggregate.rb +9 -0
- data/app/graphql/ibrain/resolvers/base_resolver.rb +15 -0
- data/app/graphql/ibrain/types/aggregate_type.rb +9 -0
- data/app/graphql/ibrain/types/base_argument.rb +11 -0
- data/app/graphql/ibrain/types/base_connection.rb +16 -0
- data/app/graphql/ibrain/types/base_edge.rb +10 -0
- data/app/graphql/ibrain/types/base_enum.rb +8 -0
- data/app/graphql/ibrain/types/base_field.rb +15 -0
- data/app/graphql/ibrain/types/base_input_object.rb +9 -0
- data/app/graphql/ibrain/types/base_interface.rb +14 -0
- data/app/graphql/ibrain/types/base_node.rb +13 -0
- data/app/graphql/ibrain/types/base_object.rb +14 -0
- data/app/graphql/ibrain/types/base_query_type.rb +14 -0
- data/app/graphql/ibrain/types/base_scalar.rb +8 -0
- data/app/graphql/ibrain/types/base_union.rb +10 -0
- data/app/graphql/ibrain/types/filter_type.rb +8 -0
- data/app/graphql/ibrain/types/node_type.rb +11 -0
- data/app/graphql/ibrain/util/field_combiner.rb +13 -0
- data/app/graphql/ibrain/util/query_combiner.rb +13 -0
- data/app/graphql/mutations/insert_user.rb +18 -0
- data/app/models/ibrain/ability.rb +51 -0
- data/app/models/ibrain/application_record.rb +7 -0
- data/app/models/ibrain/base.rb +47 -0
- data/app/models/ibrain/concerns/ransackable_attributes.rb +22 -0
- data/app/models/ibrain/concerns/soft_deletable.rb +16 -0
- data/app/models/ibrain/concerns/user_api_authentication.rb +23 -0
- data/app/models/ibrain/concerns/user_methods.rb +25 -0
- data/app/models/ibrain/legacy_user.rb +19 -0
- data/app/models/ibrain/role.rb +14 -0
- data/app/models/ibrain/role_user.rb +18 -0
- data/config/initializers/friendly_id.rb +87 -0
- data/config/locales/en.yml +10 -0
- data/config/locales/jp.yml +10 -0
- data/config/locales/vi.yml +10 -0
- data/config/routes.rb +9 -0
- data/lib/generators/ibrain/graphql/core.rb +75 -0
- data/lib/generators/ibrain/graphql/mutation_generator.rb +58 -0
- data/lib/generators/ibrain/graphql/object_generator.rb +80 -0
- data/lib/generators/ibrain/graphql/resolver_generator.rb +33 -0
- data/lib/generators/ibrain/graphql/resolvers_generator.rb +59 -0
- data/lib/generators/ibrain/graphql/templates/aggregate.erb +10 -0
- data/lib/generators/ibrain/graphql/templates/mutation.erb +16 -0
- data/lib/generators/ibrain/graphql/templates/object.erb +11 -0
- data/lib/generators/ibrain/graphql/templates/resolver.erb +15 -0
- data/lib/generators/ibrain/graphql/templates/resolvers.erb +13 -0
- data/lib/generators/ibrain/graphql/type_generator.rb +101 -0
- data/lib/generators/ibrain/install/install_generator.rb +189 -0
- data/lib/generators/ibrain/install/templates/config/database.tt +23 -0
- data/lib/generators/ibrain/install/templates/config/initializers/cors.tt +25 -0
- data/lib/generators/ibrain/install/templates/config/initializers/ibrain.rb.tt +55 -0
- data/lib/generators/ibrain/install/templates/config/puma.tt +43 -0
- data/lib/generators/ibrain/install/templates/graphql/app_schema.rb.tt +4 -0
- data/lib/generators/ibrain/install/templates/graphql/types/mutation_type.rb.tt +10 -0
- data/lib/generators/ibrain/install/templates/graphql/types/query_type.rb.tt +13 -0
- data/lib/ibrain/app_configuration.rb +66 -0
- data/lib/ibrain/config.rb +5 -0
- data/lib/ibrain/core/class_constantizer.rb +41 -0
- data/lib/ibrain/core/controller_helpers/auth.rb +64 -0
- data/lib/ibrain/core/controller_helpers/current_host.rb +17 -0
- data/lib/ibrain/core/controller_helpers/response.rb +52 -0
- data/lib/ibrain/core/controller_helpers/strong_parameters.rb +21 -0
- data/lib/ibrain/core/engine.rb +16 -0
- data/lib/ibrain/core/environment.rb +17 -0
- data/lib/ibrain/core/environment_extension.rb +27 -0
- data/lib/ibrain/core/role_configuration.rb +72 -0
- data/lib/ibrain/core/validators/email.rb +23 -0
- data/lib/ibrain/core/version.rb +17 -0
- data/lib/ibrain/core/versioned_value.rb +73 -0
- data/lib/ibrain/core.rb +86 -0
- data/lib/ibrain/encryptor.rb +27 -0
- data/lib/ibrain/i18n.rb +17 -0
- data/lib/ibrain/logger.rb +23 -0
- data/lib/ibrain/permission_sets/base.rb +33 -0
- data/lib/ibrain/permission_sets/super_user.rb +11 -0
- data/lib/ibrain/permission_sets.rb +4 -0
- data/lib/ibrain/permitted_attributes.rb +26 -0
- data/lib/ibrain/preferences/configuration.rb +170 -0
- data/lib/ibrain/preferences/preferable.rb +183 -0
- data/lib/ibrain/preferences/preferable_class_methods.rb +140 -0
- data/lib/ibrain/user_class_handle.rb +29 -0
- data/lib/ibrain_core.rb +3 -0
- data/lib/tasks/ibrain/auto_annotate_models.rake +61 -0
- data/lib/tasks/ibrain/core_tasks.rake +5 -0
- data/lib/tasks/ibrain/ridgepole.rake +37 -0
- 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
|
data/lib/ibrain/i18n.rb
ADDED
@@ -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,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
|
data/lib/ibrain_core.rb
ADDED