aaronlasseigne / active_interaction Goto Github PK
View Code? Open in Web Editor NEW:briefcase: Manage application specific business logic.
License: MIT License
:briefcase: Manage application specific business logic.
License: MIT License
You can't list multiple attributes inside a block:
class Interaction < ActiveInteraction::Base
hash :a do hash :b, :c end
def execute; end
end
Interaction.run!(a: { b: {}, c: {} })
# => .../active_interaction/lib/active_interaction/filter_method.rb:10:in `dup': can't dup Symbol (TypeError)
class HashInteraction < ActiveInteraction::Base
hash :a do; hash :b end
end
HashInteraction.run(a: { b: {} })
# => in `hash': wrong number of arguments(1 for 0) (ArgumentError)
<span class="line-numbers"><a href="#n1" name="n1">1</a></span>gem 'active_interaction', '~> 0.1.0'
This is basically because we switched to kramdown (due to #9). Everything looks find on RubyDoc.info (which is weird), but locally it looks like garbage.
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
Specifically look at wrapping execute
in a transaction by default with an option to disable it.
If you try to call an attribute in execute
it will have the original value passed in to run
or run!
. It should return the filtered value.
In a nutshell, they don't like the file filter.
JRuby: https://travis-ci.org/orgsync/active_interaction/jobs/8945563
1) ActiveInteraction::FileFilter#prepare(key, value, options = {}) behaves like options includes :allow_nil options :allow_nil is true allows the options to be set to nil
Failure/Error: expect(described_class.prepare(:key, nil, allow_nil: true)).to eq nil
NoMethodError:
undefined method `[]' for nil:NilClass
Shared Example Group: "options includes :allow_nil" called from ./spec/active_interaction/filters/file_filter_spec.rb:5
# ./lib/active_interaction/filter.rb:15:in `prepare'
# ./lib/active_interaction/filters/file_filter.rb:9:in `prepare'
# ./spec/support/filters.rb:6:in `(root)'
2) ActiveInteraction::FileFilter#prepare(key, value, options = {}) behaves like options includes :allow_nil options :allow_nil is false throws an error
Failure/Error: expect {
expected ActiveInteraction::MissingValue, got #<NoMethodError: undefined method `[]' for nil:NilClass> with backtrace:
# ./lib/active_interaction/filter.rb:15:in `prepare'
# ./lib/active_interaction/filters/file_filter.rb:9:in `prepare'
# ./spec/support/filters.rb:13:in `(root)'
# ./spec/support/filters.rb:12:in `(root)'
Shared Example Group: "options includes :allow_nil" called from ./spec/active_interaction/filters/file_filter_spec.rb:5
# ./spec/support/filters.rb:12:in `(root)'
3) FileInteraction behaves like an integration interaction with required option "a" returns the correct value
Failure/Error: expect(response).to eq a
expected: #<File:/home/travis/build/orgsync/active_interaction/spec/active_interaction/integration/file_interaction_spec.rb>
got: nil
(compared using ==)
Shared Example Group: "an integration interaction" called from ./spec/active_interaction/integration/file_interaction_spec.rb:9
# ./spec/support/interactions.rb:27:in `(root)'
Rubinius: https://travis-ci.org/orgsync/active_interaction/jobs/8945564
1) ActiveInteraction::FileFilter#prepare(key, value, options = {}) behaves like options includes :allow_nil options :allow_nil is true allows the options to be set to nil
Failure/Error: expect(described_class.prepare(:key, nil, allow_nil: true)).to eq nil
Rubinius::InvalidBytecode:
more arguments than local slots - method prepare
Shared Example Group: "options includes :allow_nil" called from ./spec/active_interaction/filters/file_filter_spec.rb:5
# ./spec/support/filters.rb:6:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
2) ActiveInteraction::FileFilter#prepare(key, value, options = {}) behaves like options includes :allow_nil options :allow_nil is false throws an error
Failure/Error: expect {
expected ActiveInteraction::MissingValue, got #<Rubinius::InvalidBytecode: more arguments than local slots> with backtrace:
# ./spec/support/filters.rb:13:in `__script__'
# kernel/bootstrap/proc.rb:22:in `call'
# ./spec/support/filters.rb:12:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
Shared Example Group: "options includes :allow_nil" called from ./spec/active_interaction/filters/file_filter_spec.rb:5
# ./spec/support/filters.rb:12:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
3) ActiveInteraction::FileFilter#prepare(key, value, options = {}) value is a File passes it on through
Failure/Error: let(:result) { described_class.prepare(key, value, options) }
Rubinius::InvalidBytecode:
more arguments than local slots - method prepare
# ./spec/active_interaction/filters/file_filter_spec.rb:10:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/active_interaction/filters/file_filter_spec.rb:16:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
4) ActiveInteraction::FileFilter#prepare(key, value, options = {}) value is not nil throws an error
Failure/Error: expect { result }.to raise_error ActiveInteraction::InvalidValue
expected ActiveInteraction::InvalidValue, got #<Rubinius::InvalidBytecode: more arguments than local slots> with backtrace:
# ./spec/active_interaction/filters/file_filter_spec.rb:10:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/active_interaction/filters/file_filter_spec.rb:24:in `__script__'
# kernel/bootstrap/proc.rb:22:in `call'
# ./spec/active_interaction/filters/file_filter_spec.rb:24:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
# ./spec/active_interaction/filters/file_filter_spec.rb:24:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
5) FileInteraction behaves like an integration interaction is invalid without required options
Failure/Error: let(:outcome) { described_class.run(options) }
Rubinius::InvalidBytecode:
more arguments than local slots - method prepare
Shared Example Group: "an integration interaction" called from ./spec/active_interaction/integration/file_interaction_spec.rb:9
# ./lib/active_interaction/base.rb:231:in `method_missing'
# ./lib/active_interaction/base.rb:63:in `run'
# ./spec/support/interactions.rb:3:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/support/interactions.rb:20:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
6) FileInteraction behaves like an integration interaction with required option "a" returns the correct value
Failure/Error: let(:outcome) { described_class.run(options) }
Rubinius::InvalidBytecode:
more arguments than local slots - method prepare
Shared Example Group: "an integration interaction" called from ./spec/active_interaction/integration/file_interaction_spec.rb:9
# ./lib/active_interaction/base.rb:231:in `method_missing'
# ./lib/active_interaction/base.rb:63:in `run'
# ./spec/support/interactions.rb:3:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/support/interactions.rb:4:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/support/interactions.rb:27:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
7) FileInteraction behaves like an integration interaction with required option "a" with optional option "b" returns the correct value
Failure/Error: let(:outcome) { described_class.run(options) }
Rubinius::InvalidBytecode:
more arguments than local slots - method prepare
Shared Example Group: "an integration interaction" called from ./spec/active_interaction/integration/file_interaction_spec.rb:9
# ./lib/active_interaction/base.rb:231:in `method_missing'
# ./lib/active_interaction/base.rb:63:in `run'
# ./spec/support/interactions.rb:3:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/support/interactions.rb:4:in `__script__'
# kernel/common/hash19.rb:256:in `fetch'
# ./spec/support/interactions.rb:34:in `__script__'
# kernel/common/eval19.rb:45:in `instance_eval'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/array19.rb:18:in `map'
# kernel/bootstrap/proc.rb:22:in `call'
# kernel/loader.rb:734:in `run_at_exits'
# kernel/loader.rb:754:in `epilogue'
# kernel/loader.rb:887:in `main'
It's possible to add your own filters, but that feature is not documented. For instance, if I wanted a filter that always set the value to 'x'
, it would look like this:
module ActiveInteraction
class XFilter < Filter
def self.prepare(key, value, options = {}, &block)
'x'
end
end
end
Then I could use it in an interaction:
class Interaction < ActiveInteraction::Base
x :a
def execute; a end
end
p Interaction.run!(a: 'y?')
# => "x"
This is distressing, to say the least:
class Interaction < ActiveInteraction::Base
boolean :a
def execute; a = a end
end
p Interaction.run!(a: true)
# => nil
For array and hash filters, it's invalid to set defaults on nested attributes. However, we don't catch that error until you call run
. As noted in the specs, we should raise an error when the class is defined.
For instance, this code should raise an ArgumentError
:
class InvalidInteraction < ActiveInteraction::Base
array :a do
array default: []
end
end
As it is, you'd have to call InvalidInteraction.run(a: [])
to get that to fail.
time :start_time, format: '%Y-%m-%dT%H:%M:%S%z'
Bundler could not find compatible versions for gem "activemodel":
In snapshot (Gemfile.lock):
activemodel (3.2.12)
In Gemfile:
active_interaction (~> 0.1.0) ruby depends on
activemodel (>= 3.0.0, ~> 4.0) ruby
Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
[2] pry> params[:file]
#<ActionDispatch::Http::UploadedFile:0x007fb0cfc68428 @content_type="text/plain", @original_filename="test.txt", @tempfile=#<File:/var/folders/r4/f5d85zsd007fnwjbspj2nbb80000gn/T/RackMultipart20130711-83548-djpvl6>, @headers="Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\nContent-Type: text/plain\r\n">
[3] pry> params[:file].tempfile
#<File:/var/folders/r4/f5d85zsd007fnwjbspj2nbb80000gn/T/RackMultipart20130711-83548-djpvl6>
-rw------- 1 aaron staff 4 Jul 11 14:42 /var/folders/r4/f5d85zsd007fnwjbspj2nbb80000gn/T/RackMultipart20130711-83548-djpvl6
[4] pry> params[:file].tempfile.class
Tempfile < #<Class:0x007fb0e3c835c0>
Time should be accepting both the Time
class and ActiveSupport::TimeWithZone
.
class ExampleInteraction < ActiveInteraction::Base
hash :h
def execute; end
end
outcome = ExampleInteraction.run(h: {})
/Users/t/Documents/GitHub/orgsync/active_interaction/lib/active_interaction/filter.rb:7:in `factory': NoMethodError (NoMethodError)
from /Users/t/Documents/GitHub/orgsync/active_interaction/lib/active_interaction/base.rb:208:in `method_missing'
from /Users/t/Documents/GitHub/orgsync/active_interaction/lib/active_interaction/base.rb:162:in `hash'
from example.rb:4:in `<class:ExampleInteraction>'
from example.rb:3:in `<main>'
For hashes with strip: false
, it might be useful to specify the types of the keys and/or values. For example:
hash :hash,
strip: false,
keys: Symbol,
values: Integer
Both return_nil
and bad_value
are pretty much useless. I think they should be removed entirely.
There needs to be a way to cast incoming attributes to specific types. There should also be a way to set a default and do minor adjustments (like strip
on String
).
Reference: https://github.com/cypriss/mutations/wiki/Filtering-Input
I want to be able to get a list of an interaction's filters, along with some metadata about them. Mutations has input_filters
, which is pretty powerful.
Composing interactions leaves a little to be desired.
class AlphaInteraction < ActiveInteraction::Base
integer :alpha, default: 0
validates :alpha, numericality: { odd: true }
def execute; alpha end
end
class BetaInteraction < ActiveInteraction::Base
model :outcome, class: ActiveInteraction::Base
validate :validator
def execute; outcome.result end
def validator
errors.add(:outcome, 'invalid') if outcome.invalid?
end
end
p BetaInteraction.run!(outcome: AlphaInteraction.run(alpha: 1))
# => 1
p BetaInteraction.run!(outcome: AlphaInteraction.run)
# => Outcome invalid (ActiveInteraction::InteractionInvalid)
How should they behave when composed?
Redcarpet doesn't play nice with JRuby on Travis.
https://travis-ci.org/orgsync/active_interaction/jobs/8912905
NotImplementedError: C extension support is not enabled. Pass -Xcext.enabled=true to JRuby or set JRUBY_OPTS or modify .jrubyrc to enable.
We could switch to kramdown, but it's code fencing syntax is different than GFM's.
GFM:
```ruby
{ a: true }
```
kramdown:
~~~ ruby
{ a: true }
~~~
class Interaction < ActiveInteraction::Base
boolean :response # can be any type
end
Interaction.run('response' => 0).response
# => 0
Interaction.run(response: 0)
# => ArgumentError
class Interaction < ActiveInteraction::Base
boolean :b
def execute; errors.add(:b) end
end
p Interaction.run(b: true).valid?
# => true
Mutations removes keys that you didn't tell it to expect from hashes:
class Mutation < Mutations::Command
required { hash :h }
def execute; h end
end
p Mutation.run!(h: { b: true })
# => {}
We currently don't do that:
class Interaction < ActiveInteraction::Base
hash :h
def execute; h end
end
p Interaction.run!(h: { b: true })
# => {"b"=>true}
Should we?
class Foo < ActiveInteraction::Base
string :query, default: ''
def execute; end
end
Foo.run(query: nil).errors.full_messages
["Query is required"]
There are times where a parameter is not posted to the page and the interaction has a default for it. Does it make sense to view an explicitly provided nil as permission to use the default?
errors.add(attribute, 'is invalid')
That's too generic.
This is how mutations does it and it seems reasonable.
We should probably check to see if Time
responds to zone
and use that if it's available.
We need end-to-end integration tests for the interactions. Although we have all the parts unit tested, we need to make sure they all play nice together.
class ExampleInteraction < ActiveInteraction::Base
date :date
def execute; end
end
That should add an error if the user inputs "2001" instead of raising an exception.
Similar to #44, I'd love to be able to add programmatically-accessible documentation to individual attributes. This would require adding the ability to inspect an interaction's fitlers programmatically, too. Something like this:
class Interaction < ActiveInteraction::Base
boolean :a, desc: "It's an attribute, alright!"
def execute; end
end
Interaction.inputs[:a]
# => "It's an attribute, alright!"
It's possible to set the @result
instance variable and then call outcome.result
on an invalid outcome.
class Interaction < ActiveInteraction::Base
boolean :attribute
validate do
@result = 'result'
errors.add(:attribute, 'invalid')
end
end
outcome = Interaction.run(attribute: true)
outcome.invalid?
# => true
outcome.result
# => "result"
We should have it print out what errors caused run!
to throw an error.
class Interaction < ActiveInteraction::Base
hash :x
end
Interaction.run
# => in `hash': wrong number of arguments (1 for 0) (ArgumentError)
class Interaction < ActiveInteraction::Base
date_time :x
end
Interaction.run
# => lib/active_interaction/attr.rb:6:in `factory': NoMethodError (NoMethodError)
class ExampleInteraction < ActiveInteraction::Base
boolean :x
def execute; end
end
ExampleInteraction.run!
class Foo < ActiveInteraction::Base
boolean :a, :b, default: true
def execute; end
end
Foo.run.errors.full_messages
# => ["B is required"]
The Mutations README is a good starting point.
If you don't supply a required nested attribute, you'll get a confusing error message.
class Interaction < ActiveInteraction::Base
hash :h do; boolean :b end
end
p Interaction.run(h: {}).errors.messages
# => {:h=>["is required"]}
when NilClass
return_nil(options[:allow_nil])
else
bad_value
I'd love it if I could add programmatically-accessible documentation to an interaction. I'm not sure how it would look, but something like this wouldn't be too bad (stolen from Grape):
class Interaction < ActiveInteraction::Base
desc "It's an interaction, alright!"
def execute; end
end
Interaction.desc
# => "It's an interaction, alright!"
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.