cased-rails 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1e5a6f29a653a735c26a9189ff897f080e17222fb4466fbd3bed2bad7c09bc82
4
+ data.tar.gz: df8009f7e0565fb463d7d898968abc7f76fd99dd7c27c3fafbd0cc121a57da32
5
+ SHA512:
6
+ metadata.gz: 62e55fc3dcd8385135d6303061167a290a1e853325110b2d3d28d274773b67493d7ddec8c18f080f5d57b87ae3a1a400b29e2f404382e5d6cee0399f95644024
7
+ data.tar.gz: e588a0f6abcdbff9c77b48f84e016458d7666538a41db9a2c2239a4301e2ea87845f266f4ee29c38315d242af731b89accec6486c03936537a7d3fe1744b4c31
@@ -0,0 +1,591 @@
1
+ # cased-rails
2
+
3
+ A Cased client for Ruby on Rails applications in your organization to control and monitor the access of information within your organization.
4
+
5
+ ## Overview
6
+
7
+ - [Installation](#installation)
8
+ - [Configuration](#configuration)
9
+ - [Usage](#usage)
10
+ - [Publishing events to Cased](#publishing-events-to-cased)
11
+ - [Publishing audit events for all record creation, updates, and deletions automatically](#publishing-audit-events-for-all-record-creation-updates-and-deletions-automatically)
12
+ - [Retrieving events from a Cased audit trail](#retrieving-events-from-a-cased-audit-trail)
13
+ - [Retrieving events from multiple Cased audit trails](#retrieving-events-from-multiple-cased-audit-trails)
14
+ - [Exporting events](#exporting-events)
15
+ - [Masking & filtering sensitive information](#masking-and-filtering-sensitive-information)
16
+ - [Disable publishing events](#disable-publishing-events)
17
+ - [Context](#context)
18
+ - [Testing](#testing)
19
+ - [Customizing cased-rails](#customizing-cased-rails)
20
+ - [Contributing](#contributing)
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'cased-rails'
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ $ bundle
33
+
34
+ Or install it yourself as:
35
+
36
+ $ gem install cased-rails
37
+
38
+ ## Configuration
39
+
40
+ All configuration options available in cased-rails are available to be configured by an environment variable or manually.
41
+
42
+ ```ruby
43
+ Cased.configure do |config|
44
+ # CASED_POLICY_KEY=policy_live_1dQpY5JliYgHSkEntAbMVzuOROh
45
+ config.policy_key = 'policy_live_1dQpY5JliYgHSkEntAbMVzuOROh'
46
+
47
+ # CASED_USERS_POLICY_KEY=policy_live_1dQpY8bBgEwdpmdpVrrtDzMX4fH
48
+ # CASED_ORGANIZATIONS_POLICY_KEY=policy_live_1dSHQRurWX8JMYMbkRdfzVoo62d
49
+ config.policy_keys = {
50
+ users: 'policy_live_1dQpY8bBgEwdpmdpVrrtDzMX4fH',
51
+ organizations: 'policy_live_1dSHQRurWX8JMYMbkRdfzVoo62d',
52
+ }
53
+
54
+ # CASED_PUBLISH_KEY=publish_live_1dQpY1jKB48kBd3418PjAotmEwA
55
+ config.publish_key = 'publish_live_1dQpY1jKB48kBd3418PjAotmEwA'
56
+
57
+ # CASED_PUBLISH_URL=https://publish.cased.com
58
+ config.publish_url = 'https://publish.cased.com'
59
+
60
+ # CASED_API_URL=https://api.cased.com
61
+ config.api_url = 'https://api.cased.com'
62
+
63
+ # CASED_RAISE_ON_ERRORS=1
64
+ config.raise_on_errors = false
65
+
66
+ # CASED_SILENCE=1
67
+ config.silence = false
68
+
69
+ # CASED_HTTP_OPEN_TIMEOUT=5
70
+ config.http_open_timeout = 5
71
+
72
+ # CASED_HTTP_READ_TIMEOUT=10
73
+ config.http_read_timeout = 10
74
+ end
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ ### Publishing events to Cased
80
+
81
+ Once Cased is setup there are two ways to publish your first audit trail event.
82
+ The first is using the `cased` helper method included in all ActiveRecord models.
83
+ Using the `cased` helper method will automatically include the current model's
84
+ machine representation and string representation in all audit events published
85
+ from within the model. In this case the Team model would have a `team` field.
86
+
87
+ ```ruby
88
+ class Team < ApplicationRecord
89
+ def add_member(user)
90
+ cased :add_member, user: user
91
+ end
92
+ end
93
+ ```
94
+
95
+ The second way to publish events to Cased is manually using the `Cased.publish` method:
96
+
97
+ ```ruby
98
+ Cased.publish(
99
+ action: 'team.add_member',
100
+ user: user,
101
+ team: team,
102
+ )
103
+ ```
104
+
105
+ Both examples above are equivalent in that they publish the following `credit_card.charge` audit event to Cased:
106
+
107
+ ```json
108
+ {
109
+ "action": "team.add_member",
110
+ "user": "user@cased.com",
111
+ "user_id": "User;2",
112
+ "team": "Employees",
113
+ "team_id": "Team;1",
114
+ "timestamp": "2020-06-23T02:02:39.932759Z"
115
+ }
116
+ ```
117
+
118
+ It's important when considering where to publish audit trail events in your application you publish them in places you can guarantee information has actually changed. You should also take into account that every model may be created across many places in your application. Only publish audit trail events when you can guarantee something has been created, updated, or deleted.
119
+
120
+ For those reasons, we highly recommend using `after_commit` callbacks whenever possible:
121
+
122
+ ```ruby
123
+ class User < ApplicationRecord
124
+ after_commit :publish_user_create_to_cased, on: :create
125
+
126
+ private
127
+
128
+ def publish_user_create_to_cased
129
+ cased :create
130
+ end
131
+ end
132
+ ```
133
+
134
+ If you use any other callback method in the ActiveRecord lifecycle other than `*_commit` you risk publishing an audit event when it does not pass validation or persist to your database.
135
+
136
+ Take the example of publishing an audit event for creating a new team in a controller:
137
+
138
+ ```ruby
139
+ class TeamsController < ApplicationController
140
+ def create
141
+ team = current_organization.teams.new(team_params)
142
+ if team.save
143
+ team.cased(:create)
144
+ # ...
145
+ else
146
+ # ...
147
+ end
148
+ end
149
+ end
150
+ ```
151
+
152
+ By publishing the `team.create` audit event within the controller directly as shown you risk not having a complete and comprehensive audit trail for each team created in your application as it may happen in your API, model callbacks, and more.
153
+
154
+ ### Publishing audit events for all record creation, updates, and deletions automatically
155
+
156
+ Cased provides a mixin you can include in your models or in `ApplicationRecord` to automatically publish when new models are created, updated, or destroyed.
157
+
158
+ ```ruby
159
+ class User < ApplicationRecord
160
+ include Cased::Model::Automatic
161
+ end
162
+ ```
163
+
164
+ Or for all models in your codebase:
165
+
166
+ ```ruby
167
+ class ApplicationRecord < ActiveRecord::Base
168
+ self.abstract_class = true
169
+
170
+ include Cased::Model::Automatic
171
+ end
172
+ ```
173
+
174
+ This mixin is intended to get you up and running quickly. You'll likely need to configure your own callbacks to control what exactly gets published to Cased.
175
+
176
+ ### Retrieving events from a Cased audit trail
177
+
178
+ If you plan on retrieving events from your audit trails to power a user facing audit trail or API you must use a Cased API key.
179
+
180
+ ```ruby
181
+ Cased.configure do |config|
182
+ config.policy_key = 'policy_live_1dQpY5JliYgHSkEntAbMVzuOROh'
183
+ end
184
+
185
+ class AuditTrailController < ApplicationController
186
+ def index
187
+ query = Cased.policy.events(phrase: params[:query])
188
+ results = query.page(params[:page]).limit(params[:limit])
189
+
190
+ respond_to do |format|
191
+ format.json do
192
+ render json: results
193
+ end
194
+
195
+ format.xml do
196
+ render xml: results
197
+ end
198
+ end
199
+ end
200
+ end
201
+ ```
202
+
203
+ ### Retrieving events from multiple Cased audit trails
204
+
205
+ To retrieve events from one or more Cased audit trails you can configure multiple Cased API keys and retrieve events for each one by fetching their respective clients.
206
+
207
+ ```ruby
208
+ Cased.configure do |config|
209
+ config.policy_keys = {
210
+ users: 'policy_live_1dQpY8bBgEwdpmdpVrrtDzMX4fH',
211
+ organizations: 'policy_live_1dSHQRurWX8JMYMbkRdfzVoo62d',
212
+ }
213
+ end
214
+
215
+ query = Cased.policies[:users].events.limit(25).page(1)
216
+ results = query.results
217
+ results.each do |event|
218
+ puts event['action'] # => user.login
219
+ puts event['timestamp'] # => 2020-06-23T02:02:39.932759Z
220
+ end
221
+
222
+ query = Cased.policies[:organizations].events.limit(25).page(1)
223
+ results = query.results
224
+ results.each do |event|
225
+ puts event['action'] # => organization.create
226
+ puts event['timestamp'] # => 2020-06-22T22:16:31.055655Z
227
+ end
228
+ ```
229
+
230
+ ### Exporting events
231
+
232
+ Exporting events from Cased allows you to provide users with exports of their own data or to respond to data requests.
233
+
234
+ ```ruby
235
+ Cased.configure do |config|
236
+ config.policy_key = 'policy_live_1dQpY5JliYgHSkEntAbMVzuOROh'
237
+ end
238
+
239
+ export = Cased.policy.exports.create(
240
+ format: :json,
241
+ phrase: 'action:credit_card.charge',
242
+ )
243
+ export.download_url # => https://api.cased.com/exports/export_1dSHQSNtAH90KA8zGTooMnmMdiD/download?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlcl8xZFFwWThiQmdFd2RwbWRwVnJydER6TVg0ZkgiLCJ
244
+ ```
245
+
246
+ ### Masking & filtering sensitive information
247
+
248
+ If you are handling sensitive information on behalf of your users you should consider masking or filtering any sensitive information.
249
+
250
+ ```ruby
251
+ Cased.configure do |config|
252
+ config.publish_key = 'publish_live_1dQpY1jKB48kBd3418PjAotmEwA'
253
+ end
254
+
255
+ Cased.publish(
256
+ action: 'credit_card.charge',
257
+ user: Cased::Sensitive::String.new('user@domain.com', label: :email),
258
+ )
259
+ ```
260
+
261
+ ### Console Usage
262
+
263
+ Most Cased events will be created by users from actions on the website from
264
+ custom defined events or lifecycle callbacks. The exception is any console
265
+ session where models may generate Cased events as you start to modify records.
266
+
267
+ By default any console session will include the hostname of where the console
268
+ session takes place. Since every event must have an actor, you must set the
269
+ actor at the beginning of your console session. If you don't know the user,
270
+ it's recommended you create a system/robot user.
271
+
272
+ ```ruby
273
+ Rails.application.console do
274
+ Cased.context.merge(actor: User.find_by!(login: ENV['USER']))
275
+ end
276
+ ```
277
+
278
+ ### Disable publishing events
279
+
280
+ Although rare, there may be times where you wish to disable publishing events to Cased. To do so wrap your transaction inside of a `Cased.disable` block:
281
+
282
+ ```ruby
283
+ Cased.disable do
284
+ user.cased(:login)
285
+ end
286
+ ```
287
+
288
+ Or you can configure the entire process to disable publishing events.
289
+
290
+ ```
291
+ CASED_DISABLE_PUBLISHING=1 bundle exec ruby crawl.rb
292
+ ```
293
+
294
+ ### Context
295
+
296
+ When you include `cased-rails` in your application your Ruby on Rails application is configures a [Rack middleware](https://github.com/cased/cased-ruby/blob/master/lib/cased/rack_middleware.rb) that populates `Cased.context` with the following information for each request:
297
+
298
+ - Request IP address
299
+ - User agent
300
+ - Request ID
301
+ - Request URL
302
+ - Request HTTP method
303
+
304
+ To customize the information included in all events that occur through your controllers you can do so by returning a hash in the `cased_initial_request_context` method:
305
+
306
+ ```ruby
307
+ class ApplicationController < ActionController::Base
308
+ def cased_initial_request_context
309
+ {
310
+ location: request.remote_ip,
311
+ request_http_method: request.method,
312
+ request_user_agent: request.headers['User-Agent'],
313
+ request_url: request.original_url,
314
+ request_id: request.request_id,
315
+ }
316
+ end
317
+ end
318
+ ```
319
+
320
+ Any information stored in `Cased.context` will be included for all audit events published to Cased.
321
+
322
+ ```ruby
323
+ Cased.context.merge(location: 'hostname.local')
324
+
325
+ Cased.publish(
326
+ action: 'console.start',
327
+ user: 'john',
328
+ )
329
+ ```
330
+
331
+ Results in:
332
+
333
+ ```json
334
+ {
335
+ "cased_id": "5f8559cd-4cd9-48c3-b1d0-6eedc4019ec1",
336
+ "action": "user.login",
337
+ "user": "john",
338
+ "location": "hostname.local",
339
+ "timestamp": "2020-06-22T21:43:06.157336"
340
+ }
341
+ ```
342
+
343
+ You can provide a block to `Cased.context.merge` and the provided context will only be present for the duration of the block:
344
+
345
+ ```ruby
346
+ Cased.context.merge(location: 'hostname.local') do
347
+ # Will include { "location": "hostname.local" }
348
+ Cased.publish(
349
+ action: 'console.start',
350
+ user: 'john',
351
+ )
352
+ end
353
+
354
+ # Will not include { "location": "hostname.local" }
355
+ Cased.publish(
356
+ action: 'console.end',
357
+ user: 'john',
358
+ )
359
+ ```
360
+
361
+ To clear/reset the context:
362
+
363
+ ```ruby
364
+ Cased.context.clear
365
+ ```
366
+
367
+ ### Testing
368
+
369
+ `cased-rails` provides a Cased::TestHelper test helper class that you can use to test events are being published to Cased.
370
+
371
+ ```ruby
372
+ require 'test-helper'
373
+
374
+ class CreditCardTest < Test::Unit::TestCase
375
+ include Cased::TestHelper
376
+
377
+ def test_charging_credit_card_publishes_credit_card_create_event
378
+ credit_card = credit_cards(:visa)
379
+ credit_card.charge
380
+
381
+ assert_cased_events 1, action: 'credit_card.charge', amount: 2000
382
+ end
383
+
384
+ def test_charging_credit_card_publishes_credit_card_create_event_with_block
385
+ credit_card = credit_cards(:visa)
386
+
387
+ assert_cased_events 1, action: 'credit_card.charge', amount: 2000 do
388
+ credit_card.charge
389
+ end
390
+ end
391
+
392
+ def test_charging_credit_card_with_zero_amount_does_not_publish_credit_card_create_event
393
+ credit_card = credit_cards(:visa)
394
+
395
+ assert_no_cased_events do
396
+ credit_card.charge
397
+ end
398
+ end
399
+ end
400
+ ```
401
+
402
+ ## Customizing cased-rails
403
+
404
+ Out of the box cased-rails takes care of serializing objects for you to the best of its ability, but you can customize cased-rails should you like to fit your products needs.
405
+
406
+ Let's look at each of these methods independently as they all work together to
407
+ create the event.
408
+
409
+ `Cased::Model#cased`
410
+
411
+ This method is what publishes events for you to Cased. You include information specific to a particular event when calling `Cased::Model#cased`:
412
+
413
+ ```ruby
414
+ class CreditCard < ApplicationRecord
415
+ def charge
416
+ Stripe::Charge.create(
417
+ amount: amount,
418
+ currency: currency,
419
+ source: source,
420
+ description: description,
421
+ )
422
+
423
+ cased(:charge, payload: {
424
+ amount: amount,
425
+ currency: currency,
426
+ description: description,
427
+ })
428
+ end
429
+ end
430
+ ```
431
+
432
+ Or you can customize information that is included anytime `Cased::Model#cased` is called in your class:
433
+
434
+ ```ruby
435
+ class CreditCard < ApplicationRecord
436
+ def charge
437
+ Stripe::Charge.create(
438
+ amount: amount,
439
+ currency: currency,
440
+ source: source,
441
+ description: description,
442
+ )
443
+
444
+ cased(:charge)
445
+ end
446
+
447
+ def cased_payload
448
+ {
449
+ credit_card: self,
450
+ amount: amount,
451
+ currency: currency,
452
+ description: description,
453
+ }
454
+ end
455
+ end
456
+ ```
457
+
458
+ Both examples are equivelent.
459
+
460
+ `Cased::Model#cased_category`
461
+
462
+ By default `cased_category` will use the underscore class name to generate the
463
+ prefix for all events generated by this class. If you published a
464
+ `CreditCard#charge` event it would be delivered to Cased `credit_card.charge`. If you want to
465
+ customize what cased-rails uses you can do so by re-opening the method:
466
+
467
+ ```ruby
468
+ class CreditCard < ApplicationRecord
469
+ def cased_category
470
+ :card
471
+ end
472
+ end
473
+ ```
474
+
475
+ `Cased::Model#cased_id`
476
+
477
+ Per our guide on [Human and machine readable information](https://docs.cased.com/guides/design-audit-trail-events#human-and-machine-readable-information) for [Designing audit trail events](https://docs.cased.com/guides/design-audit-trail-events) we encourage you to publish a unique identifier that will never change to Cased along with your events. This way when you [retrieve events](#retrieving-events-from-a-cased-audit-trail) from Cased you'll be able to locate the corresponding object in your system.
478
+
479
+ ```ruby
480
+ class User < ApplicationRecord
481
+ def cased_id
482
+ database_id
483
+ end
484
+ end
485
+ ```
486
+
487
+ `Cased::Model#cased_context`
488
+
489
+ To assist you in publishing events to Cased that are consistent and predictable, cased-rails attempts to build your `cased_context` as long as you implement either `to_s` or `cased_id` in your class:
490
+
491
+ ```ruby
492
+ class Plan < ApplicationRecord
493
+ def to_s
494
+ name
495
+ end
496
+ end
497
+
498
+ plan = Plan.new(name: 'Free')
499
+ plan.name # => 'Free'
500
+ plan.to_s # => 'Free'
501
+ plan.id # => 1
502
+ plan.cased_id # => Plan;1
503
+ plan.cased_context # => { plan: 'Free', plan_id: 'Plan;1' }
504
+ ```
505
+
506
+ If your class does not implement `to_s` it will only include `cased_id`:
507
+
508
+ ```ruby
509
+ class Plan < ApplicationRecord
510
+ end
511
+
512
+ plan = Plan.new(name: 'Free')
513
+ plan.to_s # => '#<Plan:0x00007feadf63b7e0>'
514
+ plan.cased_context # => { plan_id: 'Plan;1' }
515
+ ```
516
+
517
+ Or you can customize it if your `to_s` implementation is not suitable for Cased:
518
+
519
+ ```ruby
520
+ class Plan < ApplicationRecord
521
+ has_many :credit_cards
522
+
523
+ def to_s
524
+ name
525
+ end
526
+
527
+ def cased_context(category: cased_category)
528
+ {
529
+ "#{category}_id".to_sym => cased_id,
530
+ category => @name.parameterize,
531
+ }
532
+ end
533
+ end
534
+
535
+ class CreditCard < ApplicationRecord
536
+ belongs_to :plan
537
+
538
+ def charge
539
+ Stripe::Charge.create(
540
+ amount: amount,
541
+ currency: currency,
542
+ source: source,
543
+ description: description,
544
+ )
545
+
546
+ cased(:charge, payload: {
547
+ amount: amount,
548
+ currency: currency,
549
+ description: description,
550
+ })
551
+ end
552
+
553
+ def cased_payload
554
+ {
555
+ credit_card: self,
556
+ plan: plan,
557
+ }
558
+ end
559
+ end
560
+
561
+ credit_card = CreditCard.new(
562
+ amount: 2000,
563
+ currency: 'usd',
564
+ source: 'tok_amex',
565
+ description: 'My First Test Charge (created for API docs)',
566
+ )
567
+
568
+ credit_card.charge
569
+ ```
570
+
571
+ Results in:
572
+
573
+ ```json
574
+ {
575
+ "cased_id": "5f8559cd-4cd9-48c3-b1d0-6eedc4019ec1",
576
+ "action": "credit_card.charge",
577
+ "credit_card": "personal",
578
+ "credit_card_id": "card_1dQpXqQwXxsQs9sohN9HrzRAV6y",
579
+ "plan": "Free",
580
+ "plan_id": "plan_1dQpY1jKB48kBd3418PjAotmEwA",
581
+ "timestamp": "2020-06-22T20:24:04.815758"
582
+ }
583
+ ```
584
+
585
+ ## Contributing
586
+
587
+ 1. Fork it ( https://github.com/cased/cased-rails/fork )
588
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
589
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
590
+ 4. Push to the branch (`git push origin my-new-feature`)
591
+ 5. Create a new Pull Request
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'bundler/gem_tasks'
10
+ require_relative './test/dummy/config/application'
11
+
12
+ Rails.application.load_tasks
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module ControllerHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :cased_setup_request_context
9
+ end
10
+
11
+ private
12
+
13
+ def cased_setup_request_context
14
+ Cased.context.merge(cased_initial_request_context)
15
+ end
16
+
17
+ def cased_initial_request_context
18
+ {
19
+ location: request.remote_ip,
20
+ request_http_method: request.method,
21
+ request_user_agent: request.headers['User-Agent'],
22
+ request_url: request.original_url,
23
+ request_id: request.request_id,
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Model
5
+ module Automatic
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ after_commit :publish_cased_create, on: :create
10
+ after_commit :publish_cased_update, on: :update
11
+ after_commit :publish_cased_destroy, on: :destroy
12
+ end
13
+
14
+ private
15
+
16
+ def publish_cased_create
17
+ cased :create
18
+ end
19
+
20
+ def publish_cased_update
21
+ cased :update
22
+ end
23
+
24
+ def publish_cased_destroy
25
+ cased :destroy
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased-ruby'
4
+ require 'cased/rails/railtie'
5
+ require 'cased/rails/engine'
6
+ require 'cased/model/automatic'
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Rails
5
+ module ActiveJob
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_accessor :cased_context
10
+
11
+ around_perform do |job, block|
12
+ context = (job.cased_context || {})
13
+ context['job_class'] = job.class.name
14
+
15
+ Cased.context.merge(context) do
16
+ block.call
17
+ end
18
+ end
19
+
20
+ after_perform do
21
+ Cased::Context.clear!
22
+ end
23
+ end
24
+
25
+ def deserialize(job_data)
26
+ super
27
+
28
+ self.cased_context = (job_data['cased_context'] || {})
29
+ end
30
+
31
+ def serialize
32
+ super.merge('cased_context' => Cased::Context.current.context)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ engine_name 'cased'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Rails
5
+ module Model
6
+ def cased_id
7
+ primary_key_column = self.class.primary_key
8
+ "#{self.class.name};#{send(primary_key_column)}"
9
+ end
10
+
11
+ def cased_payload
12
+ {
13
+ cased_category => self,
14
+ }.tap do |payload|
15
+ cased_payload_belongs_to_associations(self, payload)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # @param payload [Hash] The cased_payload to mutate.
22
+ # @param object [ActiveRecord::Base] The ActiveRecord instance to continue traversing objects on.
23
+ # @param prefix [String, Symbol] The cased
24
+ def cased_payload_belongs_to_associations(object, payload, prefix: nil)
25
+ klass = object.class
26
+ klass.reflect_on_all_associations(:belongs_to).each do |association|
27
+ association_value = object.send(association.name)
28
+ if association_value.nil?
29
+ next if association.options[:optional]
30
+
31
+ raise ArgumentError, "Expected #{klass}##{association.name} association to not return nil"
32
+ end
33
+
34
+ key = "#{prefix && "#{prefix}_"}#{association.name}".to_sym
35
+ payload[key] = association_value
36
+
37
+ cased_payload_belongs_to_associations(association_value, payload, prefix: association.name)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module Cased
6
+ module Rails
7
+ class Railtie < ::Rails::Railtie
8
+ initializer 'cased.include_controller_helpers' do
9
+ ActiveSupport.on_load(:action_controller) do
10
+ require 'cased/controller_helpers'
11
+ include Cased::ControllerHelpers
12
+ end
13
+ end
14
+
15
+ initializer 'cased.instrumentation_controller' do
16
+ ActiveSupport.on_load(:action_controller) do
17
+ require 'cased/instrumentation/controller'
18
+ include Cased::Instrumentation::Controller
19
+ end
20
+ end
21
+
22
+ initializer 'cased.include_model' do
23
+ ActiveSupport.on_load(:active_record) do
24
+ require 'cased/model'
25
+ require 'cased/rails/model'
26
+
27
+ include Cased::Model
28
+ include Cased::Rails::Model
29
+ end
30
+ end
31
+
32
+ initializer 'cased.active_job' do
33
+ ActiveSupport.on_load(:active_job) do
34
+ require 'cased/rails/active_job'
35
+ include Cased::Rails::ActiveJob
36
+ end
37
+ end
38
+
39
+ initializer 'cased.rack_middleware' do |app|
40
+ app.middleware.use Cased::RackMiddleware
41
+ end
42
+
43
+ # :nocov:
44
+ console do
45
+ Cased.console
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Rails
5
+ VERSION = '0.3.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cased-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Garrett Bjerkhoel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cased-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: mocha
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.11.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.11.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.2.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.2.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.77.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.77.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 3.8.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 3.8.3
97
+ description: Ruby on Rails SDK/client library for Cased
98
+ email:
99
+ - garrett@cased.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - README.md
105
+ - Rakefile
106
+ - lib/cased/controller_helpers.rb
107
+ - lib/cased/model/automatic.rb
108
+ - lib/cased/rails.rb
109
+ - lib/cased/rails/active_job.rb
110
+ - lib/cased/rails/engine.rb
111
+ - lib/cased/rails/model.rb
112
+ - lib/cased/rails/railtie.rb
113
+ - lib/cased/rails/version.rb
114
+ homepage: https://github.com/cased/cased-rails
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubygems_version: 3.0.3
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: Ruby on Rails SDK/client library for Cased
137
+ test_files: []