better_service 1.0.0 → 1.0.1

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +253 -16
  3. data/Rakefile +1 -1
  4. data/config/locales/better_service.en.yml +37 -0
  5. data/lib/better_service/concerns/serviceable/messageable.rb +45 -2
  6. data/lib/better_service/concerns/serviceable/validatable.rb +0 -5
  7. data/lib/better_service/concerns/serviceable/viewable.rb +0 -16
  8. data/lib/better_service/presenter.rb +131 -0
  9. data/lib/better_service/railtie.rb +17 -0
  10. data/lib/better_service/services/base.rb +78 -21
  11. data/lib/better_service/services/create_service.rb +3 -0
  12. data/lib/better_service/services/destroy_service.rb +3 -0
  13. data/lib/better_service/services/update_service.rb +3 -0
  14. data/lib/better_service/subscribers/log_subscriber.rb +25 -5
  15. data/lib/better_service/version.rb +1 -1
  16. data/lib/better_service.rb +1 -0
  17. data/lib/generators/better_service/install_generator.rb +38 -0
  18. data/lib/generators/better_service/locale_generator.rb +54 -0
  19. data/lib/generators/better_service/presenter_generator.rb +60 -0
  20. data/lib/generators/better_service/templates/better_service_initializer.rb.tt +90 -0
  21. data/lib/generators/better_service/templates/locale.en.yml.tt +27 -0
  22. data/lib/generators/better_service/templates/presenter.rb.tt +53 -0
  23. data/lib/generators/better_service/templates/presenter_test.rb.tt +46 -0
  24. data/lib/generators/serviceable/scaffold_generator.rb +9 -0
  25. data/lib/generators/serviceable/templates/create_service.rb.tt +11 -1
  26. data/lib/generators/serviceable/templates/destroy_service.rb.tt +11 -1
  27. data/lib/generators/serviceable/templates/index_service.rb.tt +19 -1
  28. data/lib/generators/serviceable/templates/show_service.rb.tt +19 -1
  29. data/lib/generators/serviceable/templates/update_service.rb.tt +11 -1
  30. metadata +10 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26d2fc11e6f3151030f88f3a82fbcac4e08fd056809099f82046a8e8f6a24bcd
4
- data.tar.gz: eca9dd6ea5cf3bc365eb25af7a46837906cc0fe67225742ef1d6cbaada77650c
3
+ metadata.gz: c796a9b8cd27afc4ae26e29b42decb8a606d88d0fdd6158b8599766cfa33df48
4
+ data.tar.gz: afdee95a722f85f08c919243b5ff8da37adb211b4d7b1dd3ba7a334e97d3b2f6
5
5
  SHA512:
6
- metadata.gz: 66f920ce24ec9b96e32a83ee4ec4a20652be82d92abd98434cfe8fce6e59017fc5192ed98ad34752af42f6465fa9e4d5aa3a5aa53195ed23307a88b915773767
7
- data.tar.gz: 645b218a1fc1166f35b60b829f4c417b31c82f41710dd89aa251f28f6337956f9e35b100438819486014482f0706a7c175b398d65926c32cbe0c90393f741d77
6
+ metadata.gz: 5fb831ec7c495b453b0915c33427080012faac9aa870919d7e0e30303251fd477c9309ccfb0e8c6e257d36fe45b9c03f7af7445290641f4af898d6f62ab5c800
7
+ data.tar.gz: f34a008657a3f9208c220d286f3c33201daadac7a7114c729d97e3a5734574907cce3ce0fc85c6ea6964ab8339266be0b31d9c6f67a5211d0eb1c7b7ff18a176
data/README.md CHANGED
@@ -23,9 +23,12 @@ BetterService is a comprehensive Service Objects framework for Rails that brings
23
23
  - 🔐 **Flexible Authorization**: `authorize_with` DSL that works with any auth system (Pundit, CanCanCan, custom)
24
24
  - ⚠️ **Rich Error Handling**: Pure Exception Pattern with hierarchical errors, rich context, and detailed debugging info
25
25
  - 💾 **Cache Management**: Built-in `CacheService` for invalidating cache by context, user, or globally with async support
26
+ - 🔄 **Auto-Invalidation**: Write operations (Create/Update/Destroy) automatically invalidate cache when configured
27
+ - 🌍 **I18n Support**: Built-in internationalization with `message()` helper, custom namespaces, and fallback chain
28
+ - 🎨 **Presenter System**: Optional data transformation layer with `BetterService::Presenter` base class
26
29
  - 📊 **Metadata Tracking**: Automatic action metadata in all service responses
27
30
  - 🔗 **Workflow Composition**: Chain multiple services into pipelines with conditional steps, rollback support, and lifecycle hooks
28
- - 🏗️ **Powerful Generators**: 8 generators for rapid scaffolding (scaffold, index, show, create, update, destroy, action, workflow)
31
+ - 🏗️ **Powerful Generators**: 10 generators for rapid scaffolding (scaffold, CRUD services, action, workflow, locale, presenter)
29
32
  - 📦 **6 Service Types**: Specialized services for different use cases
30
33
  - 🎨 **DSL-Based**: Clean, expressive DSL with `search_with`, `process_with`, `authorize_with`, etc.
31
34
 
@@ -95,9 +98,9 @@ Comprehensive guides and examples are available in the `/docs` directory:
95
98
 
96
99
  ### 🎓 Guides
97
100
 
98
- - **[Getting Started](docs/getting-started.md)** - Installation, core concepts, your first service
99
- - **[Service Types](docs/service-types.md)** - Deep dive into all 6 service types (Index, Show, Create, Update, Destroy, Action)
100
- - **[Concerns Reference](docs/concerns-reference.md)** - Complete reference for all 8 concerns (Validatable, Authorizable, Cacheable, etc.)
101
+ - **[Getting Started](docs/start/getting-started.md)** - Installation, core concepts, your first service
102
+ - **[Service Types](docs/services/01_services_structure.md)** - Deep dive into all 6 service types (Index, Show, Create, Update, Destroy, Action)
103
+ - **[Concerns Reference](docs/concerns-reference.md)** - Complete reference for all 7 concerns (Validatable, Authorizable, Cacheable, etc.)
101
104
 
102
105
  ### 💡 Examples
103
106
 
@@ -105,7 +108,7 @@ Comprehensive guides and examples are available in the `/docs` directory:
105
108
 
106
109
  ### 🔧 Configuration
107
110
 
108
- See `config/initializers/better_service.rb` for all configuration options including:
111
+ See **[Configuration Guide](docs/start/configuration.md)** for all options including:
109
112
  - Instrumentation & Observability
110
113
  - Built-in LogSubscriber and StatsSubscriber
111
114
  - Cache configuration
@@ -868,9 +871,241 @@ The CacheService works with any Rails cache store, but pattern-based deletion (`
868
871
 
869
872
  ---
870
873
 
874
+ ## 🔄 Auto-Invalidation Cache
875
+
876
+ Write operations (Create/Update/Destroy) can automatically invalidate cache after successful execution.
877
+
878
+ ### How It Works
879
+
880
+ Auto-invalidation is **enabled by default** for Create, Update, and Destroy services when cache contexts are defined:
881
+
882
+ ```ruby
883
+ class Products::CreateService < BetterService::Services::CreateService
884
+ cache_contexts :products, :homepage
885
+
886
+ # Cache is automatically invalidated for these contexts after create!
887
+ # No need to call invalidate_cache_for manually
888
+ end
889
+ ```
890
+
891
+ When the service completes successfully:
892
+ 1. The product is created/updated/deleted
893
+ 2. Cache is automatically invalidated for all defined contexts
894
+ 3. All cache keys matching the patterns are cleared
895
+
896
+ ### Disabling Auto-Invalidation
897
+
898
+ Control auto-invalidation with the `auto_invalidate_cache` DSL:
899
+
900
+ ```ruby
901
+ class Products::CreateService < BetterService::Services::CreateService
902
+ cache_contexts :products
903
+ auto_invalidate_cache false # Disable automatic invalidation
904
+
905
+ process_with do |data|
906
+ product = user.products.create!(params)
907
+
908
+ # Manual control: only invalidate for featured products
909
+ invalidate_cache_for(user) if product.featured?
910
+
911
+ { resource: product }
912
+ end
913
+ end
914
+ ```
915
+
916
+ ### Async Invalidation
917
+
918
+ Combine with async option for non-blocking cache invalidation:
919
+
920
+ ```ruby
921
+ class Products::CreateService < BetterService::Services::CreateService
922
+ cache_contexts :products, :homepage
923
+
924
+ # Auto-invalidation happens async via ActiveJob
925
+ cache_async true
926
+ end
927
+ ```
928
+
929
+ **Note**: Auto-invalidation only applies to Create, Update, and Destroy services. Index and Show services don't trigger cache invalidation since they're read-only operations.
930
+
931
+ ---
932
+
933
+ ## 🌍 Internationalization (I18n)
934
+
935
+ BetterService includes built-in I18n support for service messages with automatic fallback.
936
+
937
+ ### Using the message() Helper
938
+
939
+ All service templates use the `message()` helper for response messages:
940
+
941
+ ```ruby
942
+ class Products::CreateService < BetterService::Services::CreateService
943
+ respond_with do |data|
944
+ success_result(message("create.success"), data)
945
+ end
946
+ end
947
+ ```
948
+
949
+ ### Default Messages
950
+
951
+ BetterService ships with English defaults in `config/locales/better_service.en.yml`:
952
+
953
+ ```yaml
954
+ en:
955
+ better_service:
956
+ services:
957
+ default:
958
+ created: "Resource created successfully"
959
+ updated: "Resource updated successfully"
960
+ deleted: "Resource deleted successfully"
961
+ listed: "Resources retrieved successfully"
962
+ shown: "Resource retrieved successfully"
963
+ ```
964
+
965
+ ### Custom Messages
966
+
967
+ Generate custom locale files for your services:
968
+
969
+ ```bash
970
+ rails generate better_service:locale products
971
+ ```
972
+
973
+ This creates `config/locales/products_services.en.yml`:
974
+
975
+ ```yaml
976
+ en:
977
+ products:
978
+ services:
979
+ create:
980
+ success: "Product created and added to inventory"
981
+ update:
982
+ success: "Product updated successfully"
983
+ destroy:
984
+ success: "Product removed from catalog"
985
+ ```
986
+
987
+ Then configure the namespace in your service:
988
+
989
+ ```ruby
990
+ class Products::CreateService < BetterService::Services::CreateService
991
+ messages_namespace :products
992
+
993
+ respond_with do |data|
994
+ # Uses products.services.create.success
995
+ success_result(message("create.success"), data)
996
+ end
997
+ end
998
+ ```
999
+
1000
+ ### Fallback Chain
1001
+
1002
+ Messages follow a 3-level fallback:
1003
+ 1. Custom namespace (e.g., `products.services.create.success`)
1004
+ 2. BetterService defaults (e.g., `better_service.services.default.created`)
1005
+ 3. Key itself (e.g., `"create.success"`)
1006
+
1007
+ ### Message Interpolations
1008
+
1009
+ Pass dynamic values to messages:
1010
+
1011
+ ```ruby
1012
+ respond_with do |data|
1013
+ success_result(
1014
+ message("create.success", product_name: data[:resource].name),
1015
+ data
1016
+ )
1017
+ end
1018
+ ```
1019
+
1020
+ **Locale file:**
1021
+ ```yaml
1022
+ en:
1023
+ products:
1024
+ services:
1025
+ create:
1026
+ success: "Product '%{product_name}' created successfully"
1027
+ ```
1028
+
1029
+ ---
1030
+
1031
+ ## 🎨 Presenter System
1032
+
1033
+ BetterService includes an optional presenter layer for formatting data for API/view consumption.
1034
+
1035
+ ### Creating Presenters
1036
+
1037
+ Generate a presenter class:
1038
+
1039
+ ```bash
1040
+ rails generate better_service:presenter Product
1041
+ ```
1042
+
1043
+ This creates:
1044
+ - `app/presenters/product_presenter.rb`
1045
+ - `test/presenters/product_presenter_test.rb`
1046
+
1047
+ ```ruby
1048
+ class ProductPresenter < BetterService::Presenter
1049
+ def as_json(opts = {})
1050
+ {
1051
+ id: object.id,
1052
+ name: object.name,
1053
+ price: object.price,
1054
+ display_name: "#{object.name} - $#{object.price}",
1055
+
1056
+ # Conditional fields based on user permissions
1057
+ **(admin_fields if current_user&.admin?)
1058
+ }
1059
+ end
1060
+
1061
+ private
1062
+
1063
+ def admin_fields
1064
+ {
1065
+ cost: object.cost,
1066
+ margin: object.price - object.cost
1067
+ }
1068
+ end
1069
+ end
1070
+ ```
1071
+
1072
+ ### Using Presenters in Services
1073
+
1074
+ Configure presenters via the `presenter` DSL:
1075
+
1076
+ ```ruby
1077
+ class Products::IndexService < BetterService::Services::IndexService
1078
+ presenter ProductPresenter
1079
+
1080
+ presenter_options do
1081
+ { current_user: user }
1082
+ end
1083
+
1084
+ # Items are automatically formatted via ProductPresenter#as_json
1085
+ end
1086
+ ```
1087
+
1088
+ ### Presenter Features
1089
+
1090
+ **Available Methods:**
1091
+ - `object` - The resource being presented
1092
+ - `options` - Options hash passed via `presenter_options`
1093
+ - `current_user` - Shortcut for `options[:current_user]`
1094
+ - `as_json(opts)` - Format object as JSON
1095
+ - `to_json(opts)` - Serialize to JSON string
1096
+ - `to_h` - Alias for `as_json`
1097
+
1098
+ **Example with scaffold:**
1099
+ ```bash
1100
+ # Generate services + presenter in one command
1101
+ rails generate serviceable:scaffold Product --presenter
1102
+ ```
1103
+
1104
+ ---
1105
+
871
1106
  ## 🏗️ Generators
872
1107
 
873
- BetterService includes 8 powerful generators:
1108
+ BetterService includes 10 powerful generators:
874
1109
 
875
1110
  ### Scaffold Generator
876
1111
 
@@ -878,6 +1113,9 @@ Generates all 5 CRUD services at once:
878
1113
 
879
1114
  ```bash
880
1115
  rails generate serviceable:scaffold Product
1116
+
1117
+ # With presenter
1118
+ rails generate serviceable:scaffold Product --presenter
881
1119
  ```
882
1120
 
883
1121
  Creates:
@@ -886,23 +1124,16 @@ Creates:
886
1124
  - `app/services/product/create_service.rb`
887
1125
  - `app/services/product/update_service.rb`
888
1126
  - `app/services/product/destroy_service.rb`
1127
+ - (Optional) `app/presenters/product_presenter.rb` with `--presenter`
889
1128
 
890
1129
  ### Individual Generators
891
1130
 
892
1131
  ```bash
893
- # Index service
1132
+ # CRUD Services
894
1133
  rails generate serviceable:index Product
895
-
896
- # Show service
897
1134
  rails generate serviceable:show Product
898
-
899
- # Create service
900
1135
  rails generate serviceable:create Product
901
-
902
- # Update service
903
1136
  rails generate serviceable:update Product
904
-
905
- # Destroy service
906
1137
  rails generate serviceable:destroy Product
907
1138
 
908
1139
  # Custom action service
@@ -910,6 +1141,12 @@ rails generate serviceable:action Product publish
910
1141
 
911
1142
  # Workflow for composing services
912
1143
  rails generate serviceable:workflow OrderPurchase --steps create_order charge_payment
1144
+
1145
+ # Presenter for data transformation
1146
+ rails generate better_service:presenter Product
1147
+
1148
+ # Custom locale file for I18n messages
1149
+ rails generate better_service:locale products
913
1150
  ```
914
1151
 
915
1152
  ---
@@ -1302,7 +1539,7 @@ ActiveSupport::Notifications.subscribe("service.completed") do |name, start, fin
1302
1539
  end
1303
1540
  ```
1304
1541
 
1305
- See [Configuration](docs/getting-started.md#configuration) for more details.
1542
+ See [Configuration Guide](docs/start/configuration.md) for more details.
1306
1543
 
1307
1544
  ---
1308
1545
 
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.test_files = FileList["test/**/*_test.rb"].exclude(
9
9
  "test/dummy/**/*",
10
- "test/generators/**/*"
10
+ "test/generators/**/*" # Generator tests require Rails context - run manually with: bundle exec ruby -Itest test/generators/*_test.rb
11
11
  )
12
12
  t.verbose = false
13
13
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # BetterService Default Locale File (English)
4
+ #
5
+ # This file contains default messages for BetterService operations.
6
+ # These messages are used as fallbacks when services don't define
7
+ # their own custom messages via the message() helper.
8
+ #
9
+ # To override messages for a specific namespace:
10
+ # 1. Define a namespace with messages_namespace in your service
11
+ # 2. Create locale entries under that namespace
12
+ #
13
+ # Example:
14
+ # products:
15
+ # services:
16
+ # create:
17
+ # success: "Product created successfully"
18
+
19
+ en:
20
+ better_service:
21
+ services:
22
+ # Default success messages for standard CRUD operations
23
+ default:
24
+ created: "Resource created successfully"
25
+ updated: "Resource updated successfully"
26
+ deleted: "Resource deleted successfully"
27
+ listed: "Resources retrieved successfully"
28
+ shown: "Resource retrieved successfully"
29
+ action_completed: "Action completed successfully"
30
+
31
+ # Default error messages
32
+ errors:
33
+ validation_failed: "Validation failed"
34
+ unauthorized: "You are not authorized to perform this action"
35
+ not_found: "Resource not found"
36
+ database_error: "A database error occurred"
37
+ execution_error: "An error occurred while processing your request"
@@ -18,11 +18,54 @@ module BetterService
18
18
 
19
19
  private
20
20
 
21
+ # Get translated message with fallback support
22
+ #
23
+ # Lookup order:
24
+ # 1. Custom namespace: "#{namespace}.services.#{key_path}"
25
+ # 2. Default namespace: "better_service.services.default.#{action}"
26
+ # 3. Key itself as final fallback
27
+ #
28
+ # @param key_path [String] Message key path (e.g., "create.success")
29
+ # @param interpolations [Hash] Variables to interpolate
30
+ # @return [String] Translated message
21
31
  def message(key_path, interpolations = {})
22
- return key_path if self.class._messages_namespace.nil?
32
+ # If no namespace defined, use default BetterService messages
33
+ if self.class._messages_namespace.nil?
34
+ # Extract action from key_path (e.g., "create.success" -> "created")
35
+ action = extract_action_from_key(key_path)
36
+ default_key = "better_service.services.default.#{action}"
37
+ return I18n.t(default_key, default: key_path, **interpolations)
38
+ end
23
39
 
40
+ # Try custom namespace first, fallback to default, then to key itself
24
41
  full_key = "#{self.class._messages_namespace}.services.#{key_path}"
25
- I18n.t(full_key, **interpolations)
42
+ action = extract_action_from_key(key_path)
43
+ fallback_key = "better_service.services.default.#{action}"
44
+
45
+ # I18n supports array of fallback keys: try each in order
46
+ I18n.t(full_key, default: [fallback_key.to_sym, key_path], **interpolations)
47
+ end
48
+
49
+ # Extract action name from key path for fallback lookup
50
+ #
51
+ # Examples:
52
+ # "create.success" -> "created"
53
+ # "update.success" -> "updated"
54
+ # "destroy.success" -> "deleted"
55
+ # "custom_action" -> "action_completed"
56
+ #
57
+ # @param key_path [String] Full key path
58
+ # @return [String] Action name for default messages
59
+ def extract_action_from_key(key_path)
60
+ # Handle common patterns
61
+ case key_path
62
+ when /create/i then "created"
63
+ when /update/i then "updated"
64
+ when /destroy|delete/i then "deleted"
65
+ when /index|list/i then "listed"
66
+ when /show/i then "shown"
67
+ else "action_completed"
68
+ end
26
69
  end
27
70
  end
28
71
  end
@@ -10,7 +10,6 @@ module BetterService
10
10
 
11
11
  included do
12
12
  class_attribute :_schema, default: nil
13
- attr_reader :validation_errors
14
13
  end
15
14
 
16
15
  class_methods do
@@ -23,10 +22,6 @@ module BetterService
23
22
  end
24
23
  end
25
24
 
26
- def valid?
27
- @validation_errors.empty?
28
- end
29
-
30
25
  private
31
26
 
32
27
  def validate_params!
@@ -20,22 +20,6 @@ module BetterService
20
20
 
21
21
  private
22
22
 
23
- # Override respond to add viewer phase
24
- def respond(data)
25
- # Get base result from parent respond
26
- if self.class._respond_block
27
- result = instance_exec(data, &self.class._respond_block)
28
- else
29
- result = success_result("Operation completed successfully", data)
30
- end
31
-
32
- # Add viewer if enabled
33
- return result unless viewer_enabled?
34
-
35
- view_config = execute_viewer(data, data, result)
36
- result.merge(view: view_config)
37
- end
38
-
39
23
  def viewer_enabled?
40
24
  self.class._viewer_enabled && self.class._viewer_block.present?
41
25
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterService
4
+ # Presenter - Base class for presenting service data
5
+ #
6
+ # Presenters transform raw model data into view-friendly formats.
7
+ # They are typically used with the Presentable concern's presenter DSL.
8
+ #
9
+ # Example:
10
+ # class ProductPresenter < BetterService::Presenter
11
+ # def as_json(opts = {})
12
+ # {
13
+ # id: object.id,
14
+ # name: object.name,
15
+ # price_formatted: "$#{object.price}",
16
+ # available: object.stock > 0,
17
+ # # Conditional fields based on current user
18
+ # **(admin_fields if current_user&.admin?)
19
+ # }
20
+ # end
21
+ #
22
+ # private
23
+ #
24
+ # def admin_fields
25
+ # {
26
+ # cost: object.cost,
27
+ # margin: object.price - object.cost
28
+ # }
29
+ # end
30
+ # end
31
+ #
32
+ # Usage with services:
33
+ # class Products::IndexService < IndexService
34
+ # presenter ProductPresenter
35
+ #
36
+ # presenter_options do
37
+ # { current_user: user }
38
+ # end
39
+ #
40
+ # search_with do
41
+ # { items: Product.all.to_a }
42
+ # end
43
+ # end
44
+ class Presenter
45
+ attr_reader :object, :options
46
+
47
+ # Initialize presenter
48
+ #
49
+ # @param object [Object] The object to present (e.g., ActiveRecord model)
50
+ # @param options [Hash] Additional options (e.g., current_user, permissions)
51
+ def initialize(object, **options)
52
+ @object = object
53
+ @options = options
54
+ end
55
+
56
+ # Override in subclass to define JSON representation
57
+ #
58
+ # @param opts [Hash] JSON serialization options
59
+ # @return [Hash] Hash representation of the object
60
+ def as_json(opts = {})
61
+ # Default implementation delegates to object
62
+ if object.respond_to?(:as_json)
63
+ object.as_json(opts)
64
+ else
65
+ { data: object }
66
+ end
67
+ end
68
+
69
+ # JSON string representation
70
+ #
71
+ # @param opts [Hash] JSON serialization options
72
+ # @return [String] JSON string
73
+ def to_json(opts = {})
74
+ as_json(opts).to_json
75
+ end
76
+
77
+ # Hash representation (alias for as_json)
78
+ #
79
+ # @return [Hash] Hash representation
80
+ def to_h
81
+ as_json
82
+ end
83
+
84
+ private
85
+
86
+ # Get current user from options
87
+ #
88
+ # @return [Object, nil] Current user if provided in options
89
+ def current_user
90
+ options[:current_user]
91
+ end
92
+
93
+ # Check if a field should be included based on options
94
+ #
95
+ # Useful for selective field rendering based on client requests.
96
+ #
97
+ # @param field [Symbol, String] Field name to check
98
+ # @return [Boolean] Whether field should be included
99
+ #
100
+ # @example
101
+ # # In service:
102
+ # presenter_options do
103
+ # { fields: params[:fields]&.split(',')&.map(&:to_sym) }
104
+ # end
105
+ #
106
+ # # In presenter:
107
+ # def as_json(opts = {})
108
+ # {
109
+ # id: object.id,
110
+ # name: object.name,
111
+ # **(expensive_data if include_field?(:details))
112
+ # }
113
+ # end
114
+ def include_field?(field)
115
+ return true unless options[:fields]
116
+
117
+ options[:fields].include?(field.to_sym)
118
+ end
119
+
120
+ # Check if current user has a specific role/permission
121
+ #
122
+ # @param role [Symbol, String] Role to check
123
+ # @return [Boolean] Whether user has the role
124
+ def user_can?(role)
125
+ return false unless current_user
126
+ return false unless current_user.respond_to?(:has_role?)
127
+
128
+ current_user.has_role?(role)
129
+ end
130
+ end
131
+ end
@@ -1,6 +1,23 @@
1
1
  if defined?(Rails)
2
2
  module BetterService
3
3
  class Railtie < ::Rails::Railtie
4
+ # Initialize subscribers after Rails boots
5
+ #
6
+ # This hook runs after all initializers have been executed,
7
+ # ensuring BetterService.configuration is fully loaded.
8
+ config.after_initialize do
9
+ # Attach LogSubscriber if enabled in configuration
10
+ if BetterService.configuration.log_subscriber_enabled
11
+ BetterService::Subscribers::LogSubscriber.attach
12
+ Rails.logger.info "[BetterService] LogSubscriber attached" if Rails.logger
13
+ end
14
+
15
+ # Attach StatsSubscriber if enabled in configuration
16
+ if BetterService.configuration.stats_subscriber_enabled
17
+ BetterService::Subscribers::StatsSubscriber.attach
18
+ Rails.logger.info "[BetterService] StatsSubscriber attached" if Rails.logger
19
+ end
20
+ end
4
21
  end
5
22
  end
6
23
  end