Announcing ActiveInteraction 2
(Aaron Lasseigne co-authored this post for the OrgSync Dev Blog.)
We are proud to announce the release of ActiveInteraction 2. This is the first major version change since we released ActiveInteraction more than a year ago. We made some backwards-incompatible changes that make working with interactions easier and faster.
To simplify transitioning from 1.5.1 to 2.0.0, we are also releasing 1.6.0. It backports some features from 2.0.0 and adds deprecations warnings for features that will be removed.
This blog post explains the changes we made and why we made them. It also shows you how to update interactions from 1.5.1 to 2.0.0.
Transactions
We removed support for ActiveRecord transactions (issue 205).
This means that interactions are not wrapped in a transaction by
default. To retain the old behavior, wrap your execute
method in
an ActiveRecord::Base.transaction
block.
We also removed the transaction
method, since it does not do
anything anymore.
We decided to remove transactions because we saw that most interactions did not need them. The added cost and chance of deadlocks was not worth it in general.
# v1.6
class Example < ActiveInteraction::Base
# This is the default.
transaction true
def execute
# ...
end
end
# v2.0
class Example < ActiveInteraction::Base
def execute
ActiveRecord::Base.transaction do
# ...
end
end
end
You will get a deprecation warning if you use transaction
with
1.6.
Errors
We replaced symbolic errors with detailed errors (issue 250). Detailed errors will be part of Rails 5. We started using symbolic errors in version 0.6.0 and are happy to see something similar make its way into Rails 5. Unfortunately their APIs differ slightly. See the example below for details.
If you want to use detailed errors in your own code, check out the active_model-errors_details gem.
# v1.6
class Example < ActiveInteraction::Base
def execute
errors.add_sym :base, :invalid
errors.add_sym :base, :custom, '...'
end
end
Example.run.errors.symbolic
# => {:base=>[:invalid,:custom]}
# v2.0
class Example < ActiveInteraction::Base
def execute
errors.add :base, :invalid
errors.add :base, :custom, message: '...'
end
end
Example.run.errors.details
# => {:base=>[{:error=>:invalid},{:error=>:custom,:message=>'...'}]}
You will get a deprecation warning if you use either add_sym
or
symbolic
with 1.6.
Objects
We renamed the model
filter to object
(issue 264). This
more accurately reflects what the filter can be used for. We initially
used the model
filter for ActiveModel objects. But it works with
any object, so the name was misleading.
# v1.6
class Example < ActiveInteraction::Base
model :regexp
end
Example.run(regexp: /.../)
# v2.0
class Example < ActiveInteraction::Base
object :regexp
end
Example.run(regexp: /.../)
You will get a deprecation warning if you use model
with 1.6.
Hashes
We switched the hash
filter to use indifferent access (issue
164). This prevents a possible denial of service attack. As a
result, hash keys will be strings instead of symbols.
class Example < ActiveInteraction::Base
hash :options,
strip: false
def execute
options.keys
end
end
# v1.6
Example.run!(options: { enable: true })
# => [:enable]
# v2.0
Example.run!(options: { enable: true })
# => ["enable"]
Files
We changed the file
filter to accept anything that responds to
eof?
(pull 236). It used to accept only File
s and
Tempfile
s. Now it accepts a wider range of IO objects.
class Example < ActiveInteraction::Base
file :io
def execute
io.read
end
end
# v1.6
Example.run!(io: StringIO.new('Hello, world!'))
# ActiveInteraction::InvalidInteractionError: Io is not a valid file
# v2.0
Example.run!(io: StringIO.new('Hello, world!'))
# => "Hello, world!"
Results
We added the ability to return results from invalid interactions
(issue 168). Setting the result to nil
was unnecessary. The
right way to check for validity is to use valid?
. This change
allows you to return something from an interaction even if there
are errors. This can be very useful when updating an existing record.
class Example < ActiveInteraction::Base
def execute
errors.add(:base)
'something'
end
end
# v1.6
outcome = Example.run
outcome.valid?
# => false
outcome.result
# => nil
# v2.0
outcome = Example.run
outcome.valid?
# => false
outcome.result
# => "something"
Defaults
We made it so Proc
defaults are not eagerly evaluated (issue
269). They never should have been in the first place. This was
an oversight when we introduced this feature.
class Example < ActiveInteraction::Base
boolean :flag,
default: -> {
puts 'defaulting...'
true
}
def execute
puts 'executing...'
end
end
# v1.6
# defaulting...
Example.run
# executing...
# v2.0
Example.run
# defaulting...
# executing...
Contributors
A big thanks to everyone who contributed to ActiveInteraction! We could not have done it without you.