Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/antulik/active_interaction-extras
Useful extensions for active_interaction gem
https://github.com/antulik/active_interaction-extras
activemodel extension ruby service-object
Last synced: 2 months ago
JSON representation
Useful extensions for active_interaction gem
- Host: GitHub
- URL: https://github.com/antulik/active_interaction-extras
- Owner: antulik
- License: mit
- Created: 2018-05-25T04:21:31.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-09-26T04:12:30.000Z (4 months ago)
- Last Synced: 2024-10-31T13:59:05.686Z (3 months ago)
- Topics: activemodel, extension, ruby, service-object
- Language: Ruby
- Size: 85.9 KB
- Stars: 52
- Watchers: 4
- Forks: 9
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# ActiveInteraction::Extras
[![Gem Version](https://badge.fury.io/rb/active_interaction-extras.svg)](https://badge.fury.io/rb/active_interaction-extras) ![CI build](https://github.com/antulik/active_interaction-extras/actions/workflows/ci.yml/badge.svg)
This gem contains the collection of useful extensions to [active_interaction](https://github.com/AaronLasseigne/active_interaction) gem.
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Filters](#filters)
- [Anything](#anything)
- [UUID](#uuid)
- [Filter Extensions](#filter-extensions)
- [Hash: auto strip](#hash-auto-strip)
- [Object: multiple classes](#object-multiple-classes)
- [Extensions](#extensions)
- [Filter alias](#filter-alias)
- [Halt](#halt)
- [ModelFields](#modelfields)
- [RunCallback](#runcallback)
- [StrongParams](#strongparams)
- [Transaction](#transaction)
- [Jobs](#jobs)
- [ActiveJob](#activejob)
- [Sidekiq](#sidekiq)
- [RSpec](#rspec)## Installation
```ruby
gem 'active_interaction-extras'
```## Basic Usage
```ruby
# app/services/application_interaction.rb
class ApplicationInteraction < ActiveInteraction::Base
include ActiveInteraction::Extras::All
end
```## Filters
These new filters are added automatically when gem is loaded.
### Anything
Anything filter accepts as you guest it - anything.
```ruby
class Service < ActiveInteraction::Base
anything :model
end
```### UUID
```ruby
class Service < ActiveInteraction::Base
uuid :id
end
```## Filter Extensions
You can load all filter extensions with:
```ruby
# config/initializers/active_interaction.rb
require 'active_interaction/extras/filter_extensions'
```### Hash: auto strip
This small extensions allows to accept full hashes without explicit `strip` option.
```ruby
class Service < ActiveInteraction::Base
hash :options_a, strip: false # (Before) Accept all keys
hash :options_b # (After) Accept all keys
hash :options_c do # (Before and After) Accept only specified keys
string :name
end
end
```### Object: multiple classes
This extension allows using `object` filter with multiple classes.
```ruby
class Service < ActiveInteraction::Base
object :user, class: [User, AdminUser]
end
```## Extensions
### Filter Alias
```ruby
class Service < ActiveInteraction::Base
include ActiveInteraction::Extras::FilterAlias
hash :params, as: :user_attributesdef execute
user_attributes == params # => true
end
end
```### Halt
```ruby
class Service < ActiveInteraction::Base
include ActiveInteraction::Extras::Haltdef execute
other_method
puts('finished') # this won't be called
enddef other_method
errors.add :base, :invalid
halt! if errors.any?
# or
halt_if_errors!
end
end
```### ModelFields
```ruby
class UserForm < ActiveInteraction::Base
include ActiveInteraction::Extras::ModelFieldsanything :user
model_fields(:user) do
string :first_name
string :last_name
enddef execute
model_fields(:user) # => {:first_name=>"Albert", :last_name=>"Balk"}
any_changed?(:first_name, :last_name) # => true
given_model_fields(:user) # => {:first_name=>"Albert"}
changed_model_fields(:user) # => {:first_name=>"Albert"}
end
enduser = OpenStruct.new(first_name: 'Sam', last_name: 'Balk')
UserForm.new(user: user).first_name # => 'Sam'
UserForm.run!(user: user, first_name: 'Albert')
```### RunCallback
```ruby
class Service < ActiveInteraction::Base
include ActiveInteraction::Extras::RunCallbackafter_run do
# LogAttempt.log
endafter_successful_run do
# Email.deliver
endafter_failed_run do
# NotifyAdminEmail.deliver
enddef execute
end
end
```### StrongParams
```ruby
class UpdateUserForm < ActiveInteraction::Base
include ActiveInteraction::Extras::StrongParamsstring :first_name, default: nil, permit: true
string :last_name, default: nildef execute
first_name # => 'Allowed'
last_name # => nil
end
endUpdateUserForm.new.to_model.model_name.param_key # => 'update_user_form'
form_params = ActionController::Parameters.new(
update_user_form: {
first_name: 'Allowed',
last_name: 'Not allowed',
},
)Service.run(params: form_params)
# OR
form_params = ActionController::Parameters.new(
first_name: 'Allowed',
last_name: 'Not allowed',
)Service.run(form_params: form_params)
```### Transaction
```ruby
class UpdateUserForm < ActiveInteraction::Base
include ActiveInteraction::Extras::Transactionrun_in_transaction!
def execute
Comment.create! # succeedserrors.add(:base, :invalid)
end
endUpdateUserForm.run
Comment.count # => 0
```## Jobs
You no longer need to create a separate Job class for the each interaction. This Job extension automatically converts interactions to background jobs. By convention each interaction will have a nested `Job` class which will be inherited from the parent interaction `Job` class (e.g. `ApplicationInteraction::Job`).
### ActiveJob
```ruby
class ApplicationInteraction < ActiveInteraction::Base
include ActiveInteraction::Extras::ActiveJobclass Job < ActiveJob::Base
include ActiveInteraction::Extras::ActiveJob::Perform
end
endclass DoubleService < ApplicationInteraction
integer :xdef execute
x + x
end
endDoubleService.delay.run(x: 2) # queues to run in background
DoubleService.delay(queue: 'low_priority', wait: 1.minute).run(x: 2)
```In ActiveJob mode `delay` method accepts anything ActiveJob `set` [method](https://edgeapi.rubyonrails.org/classes/ActiveJob/Core/ClassMethods.html#method-i-set) does. (`wait`, `wait_until`, `queue`, `priority`)
### Sidekiq
You can use sidekiq directly if you need more control. Sidekiq integration comes with default GlobalID support.
```ruby
class ApplicationInteraction < ActiveInteraction::Base
include ActiveInteraction::Extras::Sidekiqclass Job
include Sidekiq::Worker
include ActiveInteraction::Extras::Sidekiq::Perform
end
endclass DoubleService < ApplicationInteraction
job do
sidekiq_options retry: 1 # configure sidekiq options
endinteger :x
def execute
x + x
end
endDoubleService.delay.run(x: 2) # queues to run in background
DoubleService.delay(queue: 'low_priority', wait: 1.minute).run(x: 2)
```In Sidekiq mode `delay` method accepts anything sidekiq `set` [method](https://github.com/mperham/sidekiq/wiki/Advanced-Options#workers) does (`queue`, `retry`, `backtrace`, etc). Plus two additional `wait` and `wait_until`.
```ruby
# Advance usage: retry based on given params
class DoubleService < ApplicationInteraction
job do
sidekiq_options(retry: ->(job) {
params = deserialize_active_job_args(job)
params[:x]
})
endinteger :x
def execute
x + x
end
end
``````ruby
# Advance usage: Rescue the job but not service
class DoubleService < ApplicationInteraction
job do
def perform(*args)
super
rescue StandardError => e
params = deserialize_active_job_args(args)
params[:x]
end
endinteger :x
def execute
raise
end
endDoubleService.run # => RuntimeError
DoubleService.delay.perform_now(x: 2) # => returns 2
```## Rspec
```ruby
class SomeService < ActiveInteraction::Base
integer :x
endRSpec.describe SomeService do
include ActiveInteraction::Extras::Rspecit 'works' do
expect_to_execute(SomeService,
with: [{ x: 1 }]
return: :asd
)result = SomeService.run! x: 1
expect(result).to eq :asd
endit 'lists all mocks' do
# allow_to_run
# allow_to_execute
# allow_to_delay_run
# allow_to_delay_execute# expect_to_run / expect_not_to_run / expect_to_not_run
# expect_to_execute
# expect_to_delay_run / expect_not_to_run_delayed / expect_to_not_run_delayed
# expect_to_delay_execute
end
end
```## Development
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/antulik/active_interaction-extras. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## Credits
* ActiveInteraction::Extras is brought to you by [Anton Katunin](https://github.com/antulik) and was originally built at [CarNextDoor](https://www.carnextdoor.com.au/).
* Further improvements to this gem brought to you by [Anton Katunin](https://github.com/antulik) once again and the [Split Payments team](https://github.com/splitpayments/split).## Code of Conduct
Everyone interacting in the ActiveInteraction::Extras project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/antulik/active_interaction-extras/blob/master/CODE_OF_CONDUCT.md).