observable_model 0.1.0 โ†’ 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a939a2bb754dc9c5992a26b998b47d4f443a375d35a11b7f2eb840008befa43
4
- data.tar.gz: 85372a16208f607ab3d7e47cb58dcbe918946a30e1f6d1c56555651a1e2d513c
3
+ metadata.gz: d3bd27344026bbc33d4329242d221f0163ee2655cabd79872a7fce079cd10db3
4
+ data.tar.gz: c4fdd4dfb9817e46e7eabcf0832e819c9b4c15520439b8b60412a7f8495e522f
5
5
  SHA512:
6
- metadata.gz: db0a4ea664067980f74e61b18430667889b773f24fdfe9ea86f0bf297ba7e65ff886e599368ab6c7617f4e11f60c864c3902ba089179194c5e8a95eeeb5ed718
7
- data.tar.gz: f99c5c875395af8e25d8293931f8c33e830c4e998b01eb6e3727b90b0db5c0a58df36cb740f656cb4b2bd726cf1507306d96296e740983ec65e4a4a65d93e9cd
6
+ metadata.gz: 58ad1ab5c99f79de7ddade0e4406acd7d2ae9fe105f78bee4bfa6cc61d34fb8b4f24f7dce8ca65da77a68be0aa00a4b74085bae97f91dd4e6dd5987549b1497b
7
+ data.tar.gz: a79492c661e875f5cb3060424d8d0ff6c210db7790030f875d48ffa425824b1760908ff87c763e190e927e9b58b57f1360125099970fc0bd328107abf5ef6f62
data/README.md CHANGED
@@ -1,24 +1,201 @@
1
1
  # ObservableModel
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ A Ruby gem that implements the Observer pattern for ActiveRecord models in Rails applications. ObservableModel provides a clean, organized way to respond to model lifecycle events (create, update, destroy) without cluttering your models with callback logic.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/observable_model`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ## Why ObservableModel?
6
6
 
7
- ## Installation
7
+ As Rails applications grow, model callbacks can become cluttered with business logic, side effects, and external service integrations. ObservableModel helps you:
8
+
9
+ - **Separate concerns** - Keep models focused on data and validation
10
+ - **Organize side effects** - Isolate external service calls, notifications, and async jobs
11
+ - **Improve testability** - Easily skip observers in tests with `skip_observers` flag
12
+ - **Follow patterns** - Implement the classic Observer pattern in a Rails-friendly way
13
+ - **Maintain clarity** - Know exactly where lifecycle-triggered logic lives
14
+
15
+ ## Features
16
+
17
+ - ๐ŸŽฏ **Simple integration** - Just include a module in your ActiveRecord models
18
+ - ๐Ÿ”„ **Lifecycle hooks** - Respond to before and after commit events for create, update, and destroy
19
+ - ๐Ÿšซ **Skippable observers** - Disable observers per-instance when needed
20
+ - ๐Ÿงฉ **Convention-based** - Auto-discovers observer classes (e.g., `UserObserver` for `User`)
21
+ - โšก **Before-commit callbacks** - Enables observer actions to run before changes are committed
22
+ - โšก **After-commit callbacks** - Ensures database transactions complete before running observer actions
23
+ - ๐Ÿงช **Test-friendly** - Easy to bypass observers in test scenarios
8
24
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
25
+ ## Requirements
26
+
27
+ - Ruby >= 3.2.0
28
+ - Rails >= 6.1
29
+
30
+ ## Installation
10
31
 
11
- Install the gem and add to the application's Gemfile by executing:
32
+ Add this line to your application's Gemfile:
12
33
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
34
+ ```ruby
35
+ gem 'observable_model'
36
+ ```
14
37
 
15
- If bundler is not being used to manage dependencies, install the gem by executing:
38
+ And then execute:
16
39
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
40
+ ```bash
41
+ $ bundle install
42
+ ```
18
43
 
19
44
  ## Usage
20
45
 
21
- TODO: Write usage instructions here
46
+ ### Basic Setup
47
+
48
+ 1. **Include the module in your ActiveRecord model:**
49
+
50
+ ```ruby
51
+ class User < ApplicationRecord
52
+ include ObservableModel::Sources::ActiveRecordObservable
53
+
54
+ # Your model code...
55
+ end
56
+ ```
57
+
58
+ 2. **Create an observer class:**
59
+
60
+ ObservableModel uses a naming convention: for a model named `User`, create a `UserObserver` class.
61
+
62
+ ```ruby
63
+ # app/observers/user_observer.rb
64
+ class UserObserver < ObservableModel::Observers::Base
65
+ def pre_create
66
+ # Called before the record is created
67
+ end
68
+
69
+ def pre_update
70
+ # Called before the record is updated
71
+ end
72
+
73
+ def pre_destroy
74
+ # Called before the record is destroyed
75
+ end
76
+
77
+ def on_create_commit
78
+ # Called after a user is created and committed to the database
79
+ WelcomeMailer.welcome_email(@observable).deliver_later
80
+ AnalyticsService.track_signup(@observable)
81
+ end
82
+
83
+ def on_update_commit
84
+ # Called after a user is updated and committed
85
+ if @observable.saved_change_to_email?
86
+ EmailChangeNotifier.notify(@observable)
87
+ end
88
+ end
89
+
90
+ def on_destroy_commit
91
+ # Called after a user is destroyed and committed
92
+ CleanupService.remove_user_data(@observable.id)
93
+ end
94
+ end
95
+ ```
96
+
97
+ The `@observable` instance variable contains the model instance that triggered the event.
98
+
99
+ ### Skipping Observers
100
+
101
+ To bypass observers for a specific operation:
102
+
103
+ ```ruby
104
+ user = User.new(name: "John Doe")
105
+ user.skip_observers = true
106
+ user.save # No observer callbacks will be triggered
107
+ ```
108
+
109
+ ### Custom Observer Class Names
110
+
111
+ Override `observer_class_name` to use a non-conventional class name:
112
+
113
+ ```ruby
114
+ class User < ApplicationRecord
115
+ include ObservableModel::Sources::ActiveRecordObservable
116
+
117
+ def observer_class_name
118
+ "CustomUserObserver"
119
+ end
120
+ end
121
+ ```
122
+
123
+ ### Organizing Observers
124
+
125
+ We recommend creating an `app/observers` directory in your Rails application:
126
+
127
+ ```
128
+ app/
129
+ observers/
130
+ user_observer.rb
131
+ order_observer.rb
132
+ payment_observer.rb
133
+ ```
134
+
135
+ Make sure to add this to your `config/application.rb`:
136
+
137
+ ```ruby
138
+ config.autoload_paths += %W(#{config.root}/app/observers)
139
+ ```
140
+
141
+ ## How It Works
142
+
143
+ When you include `ObservableModel::Sources::ActiveRecordObservable` in your model, six callbacks are registered:
144
+
145
+ - `before_create` โ†’ `pre_create`
146
+ - `before_update` โ†’ `pre_update`
147
+ - `before_destroy` โ†’ `pre_destroy`
148
+ - `after_create_commit` โ†’ `on_create_commit`
149
+ - `after_update_commit` โ†’ `on_update_commit`
150
+ - `after_destroy_commit` โ†’ `on_destroy_commit`
151
+
152
+ On each event, the model looks up the observer class by convention, instantiates it with itself, and delegates the callback to it. If `skip_observers` is `true`, no observer is instantiated and all callbacks are silently skipped.
153
+
154
+ ## Examples
155
+
156
+ ### Example: Send Welcome Email on User Registration
157
+
158
+ ```ruby
159
+ class UserObserver < ObservableModel::Observers::Base
160
+ def on_create_commit
161
+ UserMailer.welcome_email(@observable).deliver_later
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Example: Validate State Before Destruction
167
+
168
+ ```ruby
169
+ class OrderObserver < ObservableModel::Observers::Base
170
+ def pre_destroy
171
+ raise "Cannot delete a completed order" if @observable.completed?
172
+ end
173
+ end
174
+ ```
175
+
176
+ ### Example: Track Order Status Changes
177
+
178
+ ```ruby
179
+ class OrderObserver < ObservableModel::Observers::Base
180
+ def on_update_commit
181
+ if @observable.saved_change_to_status?
182
+ OrderStatusNotifier.notify_customer(@observable)
183
+ AnalyticsService.track_status_change(@observable)
184
+ end
185
+ end
186
+ end
187
+ ```
188
+
189
+ ### Example: Cleanup Related Data on Deletion
190
+
191
+ ```ruby
192
+ class AccountObserver < ObservableModel::Observers::Base
193
+ def on_destroy_commit
194
+ DeleteUserDataJob.perform_later(@observable.id)
195
+ AuditLog.create(action: 'account_deleted', account_id: @observable.id)
196
+ end
197
+ end
198
+ ```
22
199
 
23
200
  ## Development
24
201
 
@@ -26,6 +203,28 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
26
203
 
27
204
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
205
 
206
+ ## Testing
207
+
208
+ Run the test suite with:
209
+
210
+ ```bash
211
+ $ bundle exec rake spec
212
+ ```
213
+
214
+ ## Roadmap
215
+
216
+ - [ ] Observer registration/configuration DSL
217
+ - [ ] Built-in async observer execution
218
+ - [ ] Observer metrics and monitoring hooks
219
+
29
220
  ## Contributing
30
221
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/observable_model.
222
+ Bug reports and pull requests are welcome on GitHub at https://github.com/CodeTectonics/observable-model. This project is intended to be a safe, welcoming space for collaboration.
223
+
224
+ ## License
225
+
226
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
227
+
228
+ ## Code of Conduct
229
+
230
+ Everyone interacting in the ObservableModel project's codebases and issue trackers is expected to follow the project's code of conduct.
@@ -5,6 +5,12 @@ module ObservableModel
5
5
  @observable = observable
6
6
  end
7
7
 
8
+ def pre_create; end
9
+
10
+ def pre_update; end
11
+
12
+ def pre_destroy; end
13
+
8
14
  def on_create_commit; end
9
15
 
10
16
  def on_update_commit; end
@@ -6,6 +6,9 @@ module ObservableModel
6
6
  included do
7
7
  attr_accessor :skip_observers
8
8
 
9
+ before_create :pre_create
10
+ before_update :pre_update
11
+ before_destroy :pre_destroy
9
12
  after_create_commit :on_create_commit
10
13
  after_update_commit :on_update_commit
11
14
  after_destroy_commit :on_destroy_commit
@@ -18,6 +21,12 @@ module ObservableModel
18
21
  observer_class_name.constantize.new(self) unless skip_observers
19
22
  end
20
23
 
24
+ delegate :pre_create, to: :observer, allow_nil: true
25
+
26
+ delegate :pre_update, to: :observer, allow_nil: true
27
+
28
+ delegate :pre_destroy, to: :observer, allow_nil: true
29
+
21
30
  delegate :on_create_commit, to: :observer, allow_nil: true
22
31
 
23
32
  delegate :on_update_commit, to: :observer, allow_nil: true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ObservableModel
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: observable_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Harbison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-01 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails