bp3-core 0.1.5 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8689f08e5e4d4d81be12c82c89e11c04482361cbb291e1f246e3feb428e70499
4
- data.tar.gz: 2567df95f61b1c3328b76808fe6ccf269ba304a89c2da79c84295351e512ef9e
3
+ metadata.gz: 76165d42f4b28714655519eeddedc6d08dd0b5bc9a5e919b389f173aa0bf20a0
4
+ data.tar.gz: dba935fa44898891607c50b377cbb4d8636300c7285592838826331cc47d5da6
5
5
  SHA512:
6
- metadata.gz: 0f85dd0d3ad073b01fc56ba4ca7d7f4e22cef238e8eac6f6070bd43794de1efa38793d10fb5ca78433e62f5f22e249b18b3ca10b008b2e0a135d8966d99a7a15
7
- data.tar.gz: a790eb51f45b1c593b2ebc447dc009836870458a2b4a871c14efba3c3513747741da0b22b9a4af1060f5405ab3052e84c77463e5bf8d8b44405d26c297007619
6
+ metadata.gz: 548dd80c8000de996d2abe85c4e158b979e9c809957f1403f15519280ee2dcb3c1fa865bf4355bc6fa25d9a6cf19791bc09cf3d6d0a84b7c0a7888cdd4960c21
7
+ data.tar.gz: 6dbc07299dcc22c84643250fd8fe018dc2b8c94de6a30fe303431d61f38438ffa32c897f43132a49d4fa9d75491ceb33cf11e988c9c2a88e21406a7dd33e6abc
data/.rubocop.yml CHANGED
@@ -1,4 +1,4 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rake
3
3
  - rubocop-rspec
4
4
 
data/.ruby-version CHANGED
@@ -1,2 +1 @@
1
- ruby-3.3.5
2
-
1
+ ruby-3.4.6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-10-26
4
+
5
+ ### Breaking
6
+ - Scope Tenantable by the primary login
7
+ Tenantable default scopes reach as far as the primary login, the request state's
8
+ `current_login` (the least privileged one signed in), rather than as far as any root or admin signed
9
+ in. A root signed in beside a user or an admin no longer reaches every site, and an admin signed in
10
+ beside a user no longer reaches every tenant; with one login signed in, nothing changes
11
+ - Read request state through `Bp3::Core::Rqid.global_request_state_class`
12
+ Tenantable reads the request state through `Bp3::Core::Rqid.global_request_state_class`
13
+ rather than a `GlobalRequestState` constant, so `Bp3::Core::Rqid.global_request_state_class_name`
14
+ must be configured wherever Tenantable is used
15
+ - Add `Bp3::Core::Tenantable.reaches_every_site?`, `.reaches_every_tenant?` and `.global_request_state`
16
+
17
+ ### Bug fixes
18
+
19
+ - Fix efon? and cfon?
20
+ Fix `efon?` and `cfon?` returning the default when the flag is explicitly off; `cfon` returns the
21
+ default when the config is not set
22
+
23
+ ## [0.2.0] - 2026-01-09
24
+
25
+ - Add bp3-request_state dependency
26
+ - Add SystemLogs concern
27
+ - Update gems
28
+ - Update ruby version to 3.4.6
29
+
3
30
  ## [0.1.5] - 2024-11-18
4
31
 
5
32
  - Use ruby 3.3.5 and relax gem constraints
data/CLAUDE.md ADDED
@@ -0,0 +1,119 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ bp3-core is a Ruby gem providing core ActiveSupport concerns for BP3 (persuavis/black_phoebe_3), a
8
+ multi-site multi-tenant Rails application. The concerns handle tenancy, request tracking, sequence
9
+ ordering, ransack, feature flags, cookies and system logging, for the application's models and controllers
10
+ and for the other bp3 gems, which mix them into third-party classes (noticed, friendly_id, pg_search,
11
+ ActionText, ActiveStorage, paper_trail, ActiveAdmin, Rails' mailer previews).
12
+
13
+ ## Commands
14
+
15
+ The gems run on Ruby 3.4.6 (`.ruby-version`). Prefix commands with `~/.rvm/bin/rvm 3.4.6 do` when the
16
+ shell is not already on it; a missing `rspec` means the wrong Ruby, not a missing bundle.
17
+
18
+ - `bundle exec rake` - specs and RuboCop (the default task)
19
+ - `bundle exec rspec spec/path/to/file_spec.rb[:LINE]` - one file or example
20
+ - `bundle exec rubocop` / `bundle exec rubocop -a`
21
+ - `bin/setup`, `bin/console`, `rake install`, `yard doc`
22
+
23
+ The specs have no database or Rails app: test a concern's decisions through plain Ruby (e.g.
24
+ `Tenantable.reaches_every_site?` with a struct standing in for the request state), and try a change against
25
+ the application by putting this repo's `lib` ahead of the bundled gem. `lib/bp3/core/test.rb` is a plain
26
+ class that stubs `before_action`, `belongs_to` and the like and includes every concern; the gem does not
27
+ require it.
28
+
29
+ ## Architecture
30
+
31
+ ### Model concerns
32
+
33
+ **Tenantable** (`tenantable.rb`)
34
+ - `configure_tenancy` looks at which of `sites_site_id`, `tenant_id` and `workspaces_workspace_id` the
35
+ table has and only sets up those: `belongs_to` (optional when the column is nullable), `site`/`workspace`
36
+ aliases, `before_validation` callbacks that fill the column from the request state's `either_*_id`, and
37
+ validations that tenant and workspace belong to the record's site
38
+ - Site and tenant get default scopes from the request state's `either_site`/`either_tenant`. How far they
39
+ reach is decided by the primary login, `current_login`, the least privileged one signed in: a root reaches
40
+ every site and an admin every tenant of theirs, but not when a less privileged login is signed in beside
41
+ them -- see `Tenantable.reaches_every_site?` and `.reaches_every_tenant?`
42
+ - Including it calls `connection` to load column metadata, unless `IGNORE_NOTIFICATIONS` is set
43
+ - The gem does not know the application's `GlobalRequestState`, only its parent `Bp3::RequestState::Base`
44
+ (gem bp3-request_state): use that API -- `current_login`, `current_root`, `current_admin`, `either_site`
45
+ and the like -- through `Bp3::Core::Tenantable.global_request_state`, never the constant
46
+
47
+ **Rqid** (`rqid.rb`)
48
+ - `before_create` sets `rqid` from the configured request state class and method
49
+ - optional `belongs_to` `original_request`, `original_response` and `original_visit` on `rqid` (more than
50
+ one record may share an rqid)
51
+ - `Bp3::Core::Rqid.global_request_state_class` is where every bp3 gem gets the request state class
52
+
53
+ **Sqnr** (`sqnr.rb`): `sqnr` (ascending) and `rnqs` (descending) scopes; `use_sqnr_for_ordering` sets
54
+ `implicit_order_column`
55
+
56
+ **Ransackable** (`ransackable.rb`): `ransackable_attributes` (all columns), `ransackable_associations` (all
57
+ associations), `ransackable_scopes` (none), `ransackable_fields` (all three), `ransortable_attributes`,
58
+ minus the global `attribute_exceptions` and `association_exceptions`
59
+
60
+ **Displayable** (`displayable.rb`): `to_dom_id`, `display_name`/`admin_display_name` ("ClassName id"),
61
+ `i18n_key`, and `filtered_attributes`, which masks `Rails.application.config.filter_parameters` (also inside
62
+ `object`/`object_changes` for versions) with `version_filter_mask`; classes override the mask to tell
63
+ sources apart
64
+
65
+ ### Controller concerns
66
+
67
+ **Actions** (`actions.rb`): the request pipeline. Prepended before actions, in order: `set_rqid`,
68
+ `check_site`, `check_workspace`, `check_tenant` (each finds or creates the record and stores it in the
69
+ request state); then paper_trail whodunnit, user agent and `Inbound::Request` records (skipped under do not
70
+ track), the maintenance-mode check, and `set_global_request_state` for the logins, locale and view context.
71
+ Also `info_for_paper_trail`, `user_for_paper_trail`, `pundit_user` and `default_url_options` with the
72
+ locale. The order of the prepends matters; do not change it
73
+
74
+ **Cookies** (`cookies.rb`): the `_bp3_visitor` signed cookie that finds or creates the `Users::Visitor`,
75
+ the do-not-track cookie, and migration from the old per-site/tenant cookie name
76
+
77
+ **Settings** (`settings.rb`): `create_request_record_is_on?`, `multi_tenant_is_on?`
78
+
79
+ **FeatureFlags** (`feature_flags.rb`): `efon?` (a `Feature::Enabler`), `cfon?` (a record's `configs`),
80
+ `efcfon?` (either, the config winning when both are set)
81
+
82
+ ### Anywhere
83
+
84
+ **SystemLogs** (`system_logs.rb`): `log`, `log_debug`, `log_info`, `log_warn`, `log_error`,
85
+ `log_exception`, as instance methods and on the module itself. Each writes to the Rails log and to the
86
+ configured log and exception classes (`log_to_io:` and `log_to_db:` switch either off); a failing database
87
+ write is logged, not raised
88
+
89
+ ### Configuration
90
+
91
+ Set in the application's `config/initializers/bp3-core.rb`; the class names are constantized and cached on
92
+ first use:
93
+ - `Bp3::Core::Rqid.global_request_state_class_name` (`'GlobalRequestState'`) and
94
+ `.global_request_state_method` (`:request_id`)
95
+ - `Bp3::Core::SystemLogs.system_log_name` (`'System::Log'`) and `.system_exception_name`
96
+ (`'System::Exception'`)
97
+ - `Bp3::Core::Ransackable.attribute_exceptions` and `.association_exceptions`
98
+
99
+ The concerns also name application classes directly: `Sites::Site`, `Tenant`, `Workspaces::Workspace`,
100
+ `Users::Visitor`, `Inbound::Request`, `Inbound::UserAgent`, `Feature::Enabler`, `UserContext`,
101
+ `DomainExtractor`.
102
+
103
+ ### Dependencies
104
+
105
+ - `activesupport ~> 8.1`, `actionview ~> 8.1`
106
+ - `bp3-request_state ~> 0.1` - `Bp3::RequestState::Base`, the parent of the application's request state
107
+
108
+ ## Code Style
109
+
110
+ - RuboCop (`.rubocop.yml`): target Ruby 3.2.2, NewCops, rubocop-rake and rubocop-rspec; AbcSize 26,
111
+ MethodLength 15, ModuleLength 150, BlockLength 66, Cyclomatic/PerceivedComplexity 10; RSpec ExampleLength
112
+ 10, MultipleExpectations 4; Style/Documentation off
113
+ - Every file starts with `# frozen_string_literal: true`
114
+ - American English; no trailing whitespace on any added or modified line
115
+
116
+ ## Release
117
+
118
+ Bump `lib/bp3/core/version.rb` and add a `CHANGELOG.md` entry. `rake release` tags, pushes and publishes to
119
+ rubygems.org; leave releasing and committing to the user.
data/Gemfile.lock CHANGED
@@ -1,109 +1,144 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bp3-core (0.1.5)
5
- actionview (>= 7.1.2)
6
- activesupport (>= 7.1.2)
4
+ bp3-core (0.3.0)
5
+ actionview (~> 8.1)
6
+ activesupport (~> 8.1)
7
+ bp3-request_state (~> 0.1)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
- actionview (8.0.0)
12
- activesupport (= 8.0.0)
12
+ actionpack (8.1.4)
13
+ actionview (= 8.1.4)
14
+ activesupport (= 8.1.4)
15
+ nokogiri (>= 1.8.5)
16
+ rack (>= 2.2.4)
17
+ rack-session (>= 1.0.1)
18
+ rack-test (>= 0.6.3)
19
+ rails-dom-testing (~> 2.2)
20
+ rails-html-sanitizer (~> 1.6)
21
+ useragent (~> 0.16)
22
+ actionview (8.1.4)
23
+ activesupport (= 8.1.4)
13
24
  builder (~> 3.1)
14
25
  erubi (~> 1.11)
15
26
  rails-dom-testing (~> 2.2)
16
27
  rails-html-sanitizer (~> 1.6)
17
- activesupport (8.0.0)
28
+ activesupport (8.1.4)
18
29
  base64
19
- benchmark (>= 0.3)
20
30
  bigdecimal
21
31
  concurrent-ruby (~> 1.0, >= 1.3.1)
22
32
  connection_pool (>= 2.2.5)
23
33
  drb
24
34
  i18n (>= 1.6, < 2)
35
+ json
25
36
  logger (>= 1.4.2)
26
37
  minitest (>= 5.1)
27
38
  securerandom (>= 0.3)
28
39
  tzinfo (~> 2.0, >= 2.0.5)
29
40
  uri (>= 0.13.1)
30
- ast (2.4.2)
31
- base64 (0.2.0)
32
- benchmark (0.4.0)
33
- bigdecimal (3.1.8)
41
+ ast (2.4.3)
42
+ base64 (0.3.0)
43
+ bigdecimal (4.1.3)
44
+ bp3-request_state (0.1.3)
45
+ actionpack (~> 8.1)
46
+ activesupport (~> 8.1)
47
+ request_store (>= 1.5.1)
34
48
  builder (3.3.0)
35
- concurrent-ruby (1.3.4)
36
- connection_pool (2.4.1)
37
- crass (1.0.6)
38
- diff-lcs (1.5.1)
39
- drb (2.2.1)
40
- erubi (1.13.0)
41
- i18n (1.14.6)
49
+ concurrent-ruby (1.3.8)
50
+ connection_pool (3.0.2)
51
+ crass (1.0.8)
52
+ diff-lcs (1.6.2)
53
+ drb (2.2.3)
54
+ erubi (1.13.1)
55
+ i18n (1.15.2)
42
56
  concurrent-ruby (~> 1.0)
43
- json (2.8.2)
44
- language_server-protocol (3.17.0.3)
45
- logger (1.6.1)
46
- loofah (2.23.1)
57
+ json (3.1.0)
58
+ language_server-protocol (3.17.0.6)
59
+ lint_roller (1.1.0)
60
+ logger (1.7.0)
61
+ loofah (2.25.2)
47
62
  crass (~> 1.0.2)
48
63
  nokogiri (>= 1.12.0)
49
- minitest (5.25.1)
50
- nokogiri (1.16.7-x86_64-darwin)
64
+ minitest (6.0.6)
65
+ drb (~> 2.0)
66
+ prism (~> 1.5)
67
+ nokogiri (1.19.4-x86_64-darwin)
51
68
  racc (~> 1.4)
52
- parallel (1.26.3)
53
- parser (3.3.6.0)
69
+ parallel (2.3.0)
70
+ parser (3.3.13.0)
54
71
  ast (~> 2.4.1)
55
72
  racc
73
+ prism (1.9.0)
56
74
  racc (1.8.1)
57
- rails-dom-testing (2.2.0)
75
+ rack (3.2.7)
76
+ rack-session (2.1.2)
77
+ base64 (>= 0.1.0)
78
+ rack (>= 3.0.0)
79
+ rack-test (2.3.0)
80
+ rack (>= 1.3)
81
+ rails-dom-testing (2.3.0)
58
82
  activesupport (>= 5.0.0)
59
83
  minitest
60
84
  nokogiri (>= 1.6)
61
- rails-html-sanitizer (1.6.0)
62
- loofah (~> 2.21)
63
- nokogiri (~> 1.14)
85
+ rails-html-sanitizer (1.7.1)
86
+ loofah (~> 2.25, >= 2.25.2)
87
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
64
88
  rainbow (3.1.1)
65
- rake (13.2.1)
66
- regexp_parser (2.9.2)
67
- rspec (3.13.0)
89
+ rake (13.4.2)
90
+ regexp_parser (2.13.1)
91
+ request_store (1.7.0)
92
+ rack (>= 1.4)
93
+ rspec (3.13.2)
68
94
  rspec-core (~> 3.13.0)
69
95
  rspec-expectations (~> 3.13.0)
70
96
  rspec-mocks (~> 3.13.0)
71
- rspec-core (3.13.2)
97
+ rspec-core (3.13.6)
72
98
  rspec-support (~> 3.13.0)
73
- rspec-expectations (3.13.3)
99
+ rspec-expectations (3.13.5)
74
100
  diff-lcs (>= 1.2.0, < 2.0)
75
101
  rspec-support (~> 3.13.0)
76
- rspec-mocks (3.13.2)
102
+ rspec-mocks (3.13.8)
77
103
  diff-lcs (>= 1.2.0, < 2.0)
78
104
  rspec-support (~> 3.13.0)
79
- rspec-support (3.13.1)
80
- rubocop (1.68.0)
81
- json (~> 2.3)
82
- language_server-protocol (>= 3.17.0)
83
- parallel (~> 1.10)
105
+ rspec-support (3.13.7)
106
+ rubocop (1.92.1)
107
+ json (>= 2.3)
108
+ language_server-protocol (~> 3.17.0.2)
109
+ lint_roller (~> 1.1.0)
110
+ parallel (>= 1.10)
84
111
  parser (>= 3.3.0.2)
85
112
  rainbow (>= 2.2.2, < 4.0)
86
- regexp_parser (>= 2.4, < 3.0)
87
- rubocop-ast (>= 1.32.2, < 2.0)
113
+ regexp_parser (>= 2.11.3, < 3.0)
114
+ rubocop-ast (>= 1.49.0, < 2.0)
88
115
  ruby-progressbar (~> 1.7)
89
- unicode-display_width (>= 2.4.0, < 3.0)
90
- rubocop-ast (1.36.1)
91
- parser (>= 3.3.1.0)
92
- rubocop-rake (0.6.0)
93
- rubocop (~> 1.0)
94
- rubocop-rspec (3.2.0)
95
- rubocop (~> 1.61)
116
+ unicode-display_width (>= 2.4.0, < 4.0)
117
+ rubocop-ast (1.50.0)
118
+ parser (>= 3.3.7.2)
119
+ prism (~> 1.7)
120
+ rubocop-rake (0.7.1)
121
+ lint_roller (~> 1.1)
122
+ rubocop (>= 1.72.1)
123
+ rubocop-rspec (3.10.2)
124
+ lint_roller (~> 1.1)
125
+ regexp_parser (>= 2.0)
126
+ rubocop (~> 1.86, >= 1.86.2)
96
127
  ruby-progressbar (1.13.0)
97
- securerandom (0.3.2)
128
+ securerandom (0.4.1)
98
129
  tzinfo (2.0.6)
99
130
  concurrent-ruby (~> 1.0)
100
- unicode-display_width (2.6.0)
101
- uri (1.0.2)
131
+ unicode-display_width (3.3.0)
132
+ unicode-emoji (~> 4.3)
133
+ unicode-emoji (4.3.0)
134
+ uri (1.1.2)
135
+ useragent (0.16.11)
102
136
 
103
137
  PLATFORMS
104
138
  x86_64-darwin-21
105
139
  x86_64-darwin-22
106
140
  x86_64-darwin-23
141
+ x86_64-darwin-24
107
142
 
108
143
  DEPENDENCIES
109
144
  bp3-core!
@@ -114,4 +149,4 @@ DEPENDENCIES
114
149
  rubocop-rspec (>= 2.25)
115
150
 
116
151
  BUNDLED WITH
117
- 2.5.17
152
+ 2.7.2
data/README.md CHANGED
@@ -61,6 +61,15 @@ To use :sqnr for record ordering for a particular model, use the class macro:
61
61
  ```ruby
62
62
  use_sqnr_for_ordering
63
63
  ```
64
+ In all models that need logging of messages and exceptions to the db, add:
65
+ ```ruby
66
+ include Bp3::Core::SystemLogs
67
+ ```
68
+ Then in your application's `config/initializers/bp3-core`, add:
69
+ ```ruby
70
+ Bp3::Core::SystemLogs.system_log_name = 'System::Log' # any class that exposes a .log_message method
71
+ Bp3::Core::SystemLogs.system_exception_name = 'System::Exception' # any class that exposes a .log_exception method
72
+ ```
64
73
  In all ActiveRecord models (or their base class) that use site, tenant and/or workspace
65
74
  attributes that need to be populated from global state, add:
66
75
  ```ruby
@@ -71,7 +80,11 @@ The specific columns expected by `Tenantable` are:
71
80
  - site: `sites_site_id`
72
81
  - tenant: `tenant_id`
73
82
  - workspace: `workspaces_workspace_id`
74
- Tenantable will use reflection to determine which one(s) exist, and will create associations and callbacks accordingly.
83
+ Tenantable will use reflection to determine which one(s) exist, and will create associations and callbacks accordingly.
84
+
85
+ Its default scopes reach as far as the primary login, the request state's `current_login`: a root
86
+ reaches every site and an admin every tenant of their site, unless a less privileged login is signed
87
+ in beside them. It reads the request state through `Bp3::Core::Rqid.global_request_state_class`.
75
88
 
76
89
  ## Development
77
90
 
data/bp3-core.gemspec CHANGED
@@ -31,8 +31,9 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ['lib']
33
33
 
34
- spec.add_dependency 'actionview', '>= 7.1.2'
35
- spec.add_dependency 'activesupport', '>= 7.1.2'
34
+ spec.add_dependency 'actionview', '~> 8.1'
35
+ spec.add_dependency 'activesupport', '~> 8.1'
36
+ spec.add_dependency 'bp3-request_state', '~> 0.1'
36
37
 
37
38
  spec.add_development_dependency 'rake', '>= 13.0'
38
39
  spec.add_development_dependency 'rspec', '>= 3.0'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Bp3
4
4
  module Core
5
- # rubocop:disable Metrics/ModuleLength
5
+ # rubocop:disable-next Metrics/ModuleLength
6
6
  module Actions
7
7
  extend ActiveSupport::Concern
8
8
 
@@ -209,6 +209,5 @@ module Bp3
209
209
  @grsc ||= Bp3::Core::Rqid.global_request_state_class
210
210
  end
211
211
  end
212
- # rubocop:enable Metrics/ModuleLength
213
212
  end
214
213
  end
@@ -58,7 +58,7 @@ module Bp3
58
58
  }
59
59
  end
60
60
 
61
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
61
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
62
62
  def check_old_visitor_cookie
63
63
  cookie_value = cookies.signed[visitor_cookie_name]
64
64
  return if cookie_value.blank? && do_not_track
@@ -85,9 +85,8 @@ module Bp3
85
85
  "check_visitor_cookie: cookie[#{visitor_cookie_name}]=#{cookies.signed[visitor_cookie_name]}"
86
86
  end
87
87
  end
88
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
89
88
 
90
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
89
+ # rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
91
90
  def check_new_visitor_cookie
92
91
  cookie_value = cookies.signed[new_visitor_cookie_name]
93
92
  return if cookie_value.blank? && do_not_track
@@ -110,7 +109,6 @@ module Bp3
110
109
  "check_visitor_cookie: cookie[#{new_visitor_cookie_name}]=#{cookies.signed[new_visitor_cookie_name]}"
111
110
  end
112
111
  end
113
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
114
112
 
115
113
  def visitor_cookie_name
116
114
  @visitor_cookie_name ||= "#{VISITOR_COOKIE_NAME_PREFIX}_#{cookie_site_id}_#{cookie_tenant_id}"
@@ -24,18 +24,19 @@ module Bp3
24
24
  Feature::Enabler.on(flag, enablable: ref, default:)
25
25
  end
26
26
 
27
+ # An explicit false wins over the default; the default applies only when the flag is not set
27
28
  def efon?(flag, ref: nil, default: nil)
28
- efon(flag, ref:, default:) || default || false
29
+ efon(flag, ref:, default:) || false
29
30
  end
30
31
 
31
32
  def cfon(flag, ref: nil, default: nil)
32
- return default if ref.nil?
33
-
34
- ref.configs&.[](flag)
33
+ value = ref&.configs&.[](flag)
34
+ value.nil? ? default : value
35
35
  end
36
36
 
37
+ # An explicit false wins over the default; the default applies only when the config is not set
37
38
  def cfon?(flag, ref: nil, default: nil)
38
- cfon(flag, ref:, default:) || default || false
39
+ cfon(flag, ref:, default:) || false
39
40
  end
40
41
  end
41
42
  end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Bp3
6
+ module Core
7
+ module SystemLogs
8
+ extend ActiveSupport::Concern
9
+
10
+ SYSTEM_LOG_DEFAULTS = { log_to_db: true, log_to_io: true }.freeze
11
+
12
+ mattr_accessor :system_exception_name, :system_log_name
13
+
14
+ def self.system_exception_class
15
+ @@system_exception_class ||= system_exception_name.constantize # rubocop:disable Style/ClassVars
16
+ end
17
+
18
+ def self.system_log_class
19
+ @@system_log_class ||= system_log_name.constantize # rubocop:disable Style/ClassVars
20
+ end
21
+
22
+ # Class methods for use in contexts without instance methods
23
+ module ClassMethods
24
+ # rubocop:disable-next Metrics/ParameterLists, Metrics/MethodLength
25
+ def log(level:, key:, message: nil, exception: nil, site: nil, details: {}, **system_log_options)
26
+ system_log_options = SYSTEM_LOG_DEFAULTS.dup.merge(system_log_options)
27
+ record = nil
28
+ if exception
29
+ if system_log_options[:log_to_io]
30
+ rails_logger.debug do
31
+ "SystemLogs, #{level}, #{key}, #{exception.message}"
32
+ end
33
+ end
34
+ record = exception_logger.log_exception(exception, site:, key:, details:) if system_log_options[:log_to_db]
35
+ end
36
+ return record if (message || '').strip == ''
37
+
38
+ rails_logger.debug { "SystemLogs, #{level}, #{key}, #{message}" } if system_log_options[:log_to_io]
39
+ begin
40
+ system_logger.log_message(level:, key:, message:, details:) if system_log_options[:log_to_db]
41
+ rescue StandardError => e
42
+ rails_logger.error { "SystemLogs.log failed: #{e.message}" }
43
+ nil
44
+ end
45
+ end
46
+
47
+ def log_debug(key:, message:, details: {}, **system_log_options)
48
+ log(level: 'debug', key:, message:, details:, **system_log_options)
49
+ end
50
+
51
+ def log_info(key:, message:, details: {}, **system_log_options)
52
+ log(level: 'info', key:, message:, details:, **system_log_options)
53
+ end
54
+
55
+ def log_warn(key:, message:, details: {}, **system_log_options)
56
+ log(level: 'warn', key:, message:, details:, **system_log_options)
57
+ end
58
+
59
+ def log_error(key:, message:, details: {}, **system_log_options)
60
+ log(level: 'error', key:, message:, details:, **system_log_options)
61
+ end
62
+
63
+ def log_exception(exception, key:, details: {}, **system_log_options)
64
+ if exception.nil?
65
+ rails_logger.error { "SystemLogs, #{key}, nil exception logged" }
66
+ return nil
67
+ end
68
+ log(level: 'exception', key:, exception:, details:, **system_log_options)
69
+ end
70
+
71
+ def rails_logger
72
+ return Rails.logger if defined?(Rails)
73
+
74
+ ::Logger.new($stdout, level: Logger::ERROR)
75
+ end
76
+
77
+ def system_logger
78
+ ::Bp3::Core::SystemLogs.system_log_class
79
+ end
80
+
81
+ def exception_logger
82
+ ::Bp3::Core::SystemLogs.system_exception_class
83
+ end
84
+ end
85
+
86
+ # rubocop:disable-next Metrics/ParameterLists
87
+ def log(level:, key:, message: nil, exception: nil, site: nil, details: {}, **system_log_options)
88
+ SystemLogs.log(level:, key:, message:, exception:, site:, details:, **system_log_options)
89
+ end
90
+
91
+ def log_debug(key:, message:, details: {}, **system_log_options)
92
+ SystemLogs.log_debug(key:, message:, details:, **system_log_options)
93
+ end
94
+
95
+ def log_info(key:, message:, details: {}, **system_log_options)
96
+ SystemLogs.log_info(key:, message:, details:, **system_log_options)
97
+ end
98
+
99
+ def log_warn(key:, message:, details: {}, **system_log_options)
100
+ SystemLogs.log_warn(key:, message:, details:, **system_log_options)
101
+ end
102
+
103
+ def log_error(key:, message:, details: {}, **system_log_options)
104
+ SystemLogs.log_error(key:, message:, details:, **system_log_options)
105
+ end
106
+
107
+ def log_exception(exception, key:, details: {}, **system_log_options)
108
+ SystemLogs.log_exception(exception, key:, details:, **system_log_options)
109
+ end
110
+
111
+ # Extend self to make ClassMethods available directly on SystemLogs module
112
+ extend ClassMethods
113
+ end
114
+ end
115
+ end
@@ -5,6 +5,25 @@ module Bp3
5
5
  module Tenantable
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ # The application's request state, the Bp3::RequestState::Base subclass Bp3::Core::Rqid is
9
+ # configured with
10
+ def self.global_request_state
11
+ Bp3::Core::Rqid.global_request_state_class
12
+ end
13
+
14
+ # How far a default scope reaches is decided by the primary login, the request state's
15
+ # current_login: the least privileged one signed in. A root or an admin signed in beside a less
16
+ # privileged login has that login's reach rather than their own.
17
+ def self.reaches_every_site?(state = global_request_state)
18
+ login = state.current_login
19
+ !login.nil? && login == state.current_root
20
+ end
21
+
22
+ def self.reaches_every_tenant?(state = global_request_state)
23
+ login = state.current_login
24
+ !login.nil? && (login == state.current_root || login == state.current_admin)
25
+ end
26
+
8
27
  included do
9
28
  # call connection to make sure the db meta data has been loaded
10
29
  ignore_notifications_string = (ENV['IGNORE_NOTIFICATIONS'] || '').strip
@@ -16,13 +35,13 @@ module Bp3
16
35
  def set_sites_site_id
17
36
  return if sites_site_id || sites_site
18
37
 
19
- self.sites_site_id = GlobalRequestState.either_site_id
38
+ self.sites_site_id = Bp3::Core::Tenantable.global_request_state.either_site_id
20
39
  end
21
40
 
22
41
  def set_tenant_id
23
42
  return if tenant_id || tenant
24
43
 
25
- self.tenant_id = GlobalRequestState.either_tenant_id
44
+ self.tenant_id = Bp3::Core::Tenantable.global_request_state.either_tenant_id
26
45
  end
27
46
 
28
47
  def tenant_matches_site
@@ -38,7 +57,7 @@ module Bp3
38
57
  def set_workspaces_workspace_id
39
58
  return if workspaces_workspace_id || workspaces_workspace
40
59
 
41
- self.workspaces_workspace_id = GlobalRequestState.either_workspace_id
60
+ self.workspaces_workspace_id = Bp3::Core::Tenantable.global_request_state.either_workspace_id
42
61
  end
43
62
 
44
63
  def workspaces_workspace_matches_site
@@ -51,7 +70,6 @@ module Bp3
51
70
  errors.add(:workspaces_workspace, :must_match_site)
52
71
  end
53
72
 
54
- # rubocop:disable: Metrics/BlockLength
55
73
  class_methods do
56
74
  def configure_tenancy(tenancy_configuration = {})
57
75
  @tenancy_configuration = default_configuration.merge(tenancy_configuration)
@@ -76,13 +94,13 @@ module Bp3
76
94
  before_validation :set_sites_site_id
77
95
 
78
96
  default_scope lambda {
79
- site = GlobalRequestState.either_site
80
- site = nil if GlobalRequestState.current_root
97
+ site = Bp3::Core::Tenantable.global_request_state.either_site
98
+ site = nil if Bp3::Core::Tenantable.reaches_every_site?
81
99
  where(sites_site_id: site.id) if site
82
100
  }
83
101
  end
84
102
 
85
- # rubocop:disable Metrics/MethodLength
103
+ # rubocop:disable-next Metrics/MethodLength
86
104
  def may_belong_to_tenant
87
105
  column = columns.detect { |c| c.name == 'tenant_id' }
88
106
  return if column.nil?
@@ -96,10 +114,10 @@ module Bp3
96
114
  validate :tenant_matches_site
97
115
 
98
116
  default_scope lambda {
99
- site = GlobalRequestState.either_site
100
- site = nil if GlobalRequestState.current_root
101
- tenant = GlobalRequestState.either_tenant
102
- tenant = nil if GlobalRequestState.either_admin
117
+ site = Bp3::Core::Tenantable.global_request_state.either_site
118
+ site = nil if Bp3::Core::Tenantable.reaches_every_site?
119
+ tenant = Bp3::Core::Tenantable.global_request_state.either_tenant
120
+ tenant = nil if Bp3::Core::Tenantable.reaches_every_tenant?
103
121
  if site && tenant # for non admins (i.e. users)
104
122
  where(sites_site_id: site.id, tenant_id: tenant.id)
105
123
  elsif site # for site admins
@@ -109,7 +127,6 @@ module Bp3
109
127
  end
110
128
  }
111
129
  end
112
- # rubocop:enable Metrics/MethodLength
113
130
 
114
131
  def may_belong_to_workspace
115
132
  column = columns.detect { |c| c.name == 'workspaces_workspace_id' }
@@ -137,7 +154,6 @@ module Bp3
137
154
  }
138
155
  end
139
156
  end
140
- # rubocop:disable: Metrics/BlockLength
141
157
  end
142
158
  end
143
159
  end
data/lib/bp3/core/test.rb CHANGED
@@ -4,6 +4,40 @@ module Bp3
4
4
  module Core
5
5
  # Bp3::Core::Test provides a convenience class for testing Bp3::Core
6
6
  class Test
7
+ class SystemLogger
8
+ @log_count = 0
9
+
10
+ def self.log_message(level:, key:, message:, details:)
11
+ puts "Test:SystemLogger: #{level} #{key} #{message} #{details}"
12
+ add_count
13
+ end
14
+
15
+ def self.add_count
16
+ @log_count += 1
17
+ end
18
+
19
+ class << self
20
+ attr_reader :log_count
21
+ end
22
+ end
23
+
24
+ class ExceptionLogger
25
+ @log_count = 0
26
+
27
+ def self.log_exception(exception, site:, key:, details:)
28
+ puts "Test:ExceptionLogger: #{site&.display_name} #{key} #{exception.message} #{details}"
29
+ add_count
30
+ end
31
+
32
+ def self.add_count
33
+ @log_count += 1
34
+ end
35
+
36
+ class << self
37
+ attr_reader :log_count
38
+ end
39
+ end
40
+
7
41
  # to test Ransackable
8
42
  include Ransackable
9
43
 
@@ -38,6 +72,10 @@ module Bp3
38
72
  # to test Sqnr
39
73
  include Sqnr
40
74
 
75
+ # to test SystemLogs
76
+ include SystemLogs # to use log methods on an instance
77
+ extend SystemLogs # to use log methods on the class
78
+
41
79
  # to test Tenantable
42
80
  # first define this:
43
81
  def self.connection; end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bp3
4
4
  module Core
5
- VERSION = '0.1.5'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
data/lib/bp3/core.rb CHANGED
@@ -9,6 +9,7 @@ require_relative 'core/displayable'
9
9
  require_relative 'core/feature_flags'
10
10
  require_relative 'core/ransackable'
11
11
  require_relative 'core/settings'
12
+ require_relative 'core/system_logs'
12
13
  require_relative 'core/tenantable'
13
14
  require_relative 'core/rqid'
14
15
  require_relative 'core/sqnr'
metadata CHANGED
@@ -1,43 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bp3-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wim den Braven
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-11-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: actionview
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - ">="
16
+ - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: 7.1.2
18
+ version: '8.1'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
- - - ">="
23
+ - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: 7.1.2
25
+ version: '8.1'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: activesupport
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - ">="
30
+ - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: 7.1.2
32
+ version: '8.1'
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
- - - ">="
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '8.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: bp3-request_state
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
39
52
  - !ruby/object:Gem::Version
40
- version: 7.1.2
53
+ version: '0.1'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: rake
43
56
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +121,6 @@ dependencies:
108
121
  - - ">="
109
122
  - !ruby/object:Gem::Version
110
123
  version: '2.25'
111
- description:
112
124
  email:
113
125
  - wimdenbraven@persuavis.com
114
126
  executables: []
@@ -120,6 +132,7 @@ files:
120
132
  - ".ruby-version"
121
133
  - ".yardopts"
122
134
  - CHANGELOG.md
135
+ - CLAUDE.md
123
136
  - Gemfile
124
137
  - Gemfile.lock
125
138
  - LICENSE.txt
@@ -136,6 +149,7 @@ files:
136
149
  - lib/bp3/core/rqid.rb
137
150
  - lib/bp3/core/settings.rb
138
151
  - lib/bp3/core/sqnr.rb
152
+ - lib/bp3/core/system_logs.rb
139
153
  - lib/bp3/core/tenantable.rb
140
154
  - lib/bp3/core/test.rb
141
155
  - lib/bp3/core/version.rb
@@ -149,7 +163,6 @@ metadata:
149
163
  source_code_uri: https://github.com/persuavis/bp3-core
150
164
  changelog_uri: https://github.com/persuavis/bp3-core/blob/main/CHANGELOG.md
151
165
  rubygems_mfa_required: 'true'
152
- post_install_message:
153
166
  rdoc_options: []
154
167
  require_paths:
155
168
  - lib
@@ -164,8 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
177
  - !ruby/object:Gem::Version
165
178
  version: '0'
166
179
  requirements: []
167
- rubygems_version: 3.5.23
168
- signing_key:
180
+ rubygems_version: 4.0.4
169
181
  specification_version: 4
170
182
  summary: bp3-core provides core concerns for BP3 (persuavis/black_phoebe_3).
171
183
  test_files: []