Giter Club home page Giter Club logo

Comments (5)

AaronLasseigne avatar AaronLasseigne commented on July 23, 2024

What if we handled interactions like we handle other attributes?

class Interaction < ActiveInteraction::Base
  model :account
  interaction :access # Access < ActiveInteraction::Base

  def execute
    access # => returns the result of the interaction
  end
end

Interaction.run(account: current_user, access: {a: 1, b: 2})

I was thinking we could call the sub interactions with the same run method used to call the interaction. The data would be passed into Interaction as a hash called :access as seen above. We would probably only need to give result back so that the value inside of execute is consistent. Errors would be handled the same way we do with hashes (e.g. "Access is invalid."). I haven't thought through all of the issues or use cases but right now I like this direction. Thoughts?

from active_interaction.

justinsteffy avatar justinsteffy commented on July 23, 2024

At first glance I really liked it, but I think having the sub-interaction explicitly defined in the interaction limits reusability. Now one interaction is tightly coupled to another when it really only needs to care about the type of the result.

For instance:

class DownloadWordsInteraction < ActiveInteraction::Base
  string :url
  def execute; get_contents(url).split(/\W+/) end
end

class CountWordsInteraction < ActiveInteraction::Base
  array :words do
    string
  end
  def execute; count_words(words) end
end

If CountWordsInteraction was tightly coupled to DownloadWordsInteraction then I couldn't reuse it if I already had my word list from a file, had downloaded it earlier, or used another interaction that also returns an array of words.

I don't think an interaction implementation should care about composability. Composing interactions should simply be pipelining results from one interaction to another until there is a failure, at which point that failure is returned.

from active_interaction.

AaronLasseigne avatar AaronLasseigne commented on July 23, 2024

The tight coupling is an excellent point. My idea would work well for something like a form that builds more than one model but it definitely falls apart in the case you gave.

from active_interaction.

clifton avatar clifton commented on July 23, 2024

Definitely good points. What about pulling in already-defined input fields with validations?

As an example, let's look at the account import interaction. https://github.com/orgsync/orgsync/pull/3701/files

There are many potential interactions that make up an operation over a single row in the spreadsheet:

  • Create/update user, attaching to default school if not already attached
  • Create/update card swipe data for user in school context
  • Create/update custom profile information
  • Join select portals / groups
  • Get added to classifications

Many of these interactions, by themselves, will take some basic information (ie school_id/account_id) that is already known when running the account importer interaction, so maybe there's a middle road we could find. I'm sure there's an API we could come up with that would provide a declarative, composable interface.

class AttachCardInteraction < ActiveInteraction::Base
  model :account
  model :school
  string :card_number

  def execute
    # ...
  end
end

class ImportAccountInteraction < ActiveInteraction::Base
  model :account
  model :school
  # ... many other fields

  # not a fan of the names I'm using
  interaction :card, class: AttachCardInteraction do
    # inherit card number validations/metadata from AttachCardInteraction
    inherit_attribute :card_number

    use_attribute :account
    # or `use_attribute account: :user` if the field is named
    # :user in ImportAccountInteraction

    use_attribute :school { account.default_school }
    # evaluated in the context of the ImportAccountInteraction
    # this would be incorrect in this context, but just an idea
  end

  # perhaps we could make sure all required fields in the specified interaction
  # are specified when the interactions are loaded to catch early errors

  # another idea to take a sequence of interaction fields
  # eg `cards: [{card_number: "1234"}, {card_number: "5678"}]
  interactions :cards, class: AttachCardInteraction do
    use_attribute :account, :school
    inherit_attribute :card_number
  end

  def execute
    # ...

    # we'd need to be able to declare whether failures will
    # a) halt the process with an error
    # b) throw an exception
    # c) continue and notify
    # d) ignore
    #
    # also, the order in which these are executed would certainly
    # need to be specified in the `execute` method body. all errors
    # would bubble up to the top level interaction in a traditional errors
    # hash
    update_account.execute!
    group_memberships.execute_all!
    cards.execute_all!
    profile_fields.execute_all!

    # ...
  end
end

This would also allow for better introspection of required sub-fields (vs which are taken care of by the enclosing interaction). What do you guys think?

from active_interaction.

tfausak avatar tfausak commented on July 23, 2024

@justinsteffy and I talked this over. We landed on something like this:

ActiveInteraction.pipeline do
  run AdditionInteraction, with: { x: 3, y: 5 }
  and_then -> (result) { { x: result, y: 7 } }, MultiplicationInteraction
  and_then :x, SquareInteraction
  no_more_and_then
end

That effectively computes ((3 + 5) * 7) ^ 2 using interactions. Ignoring error handling, it might currently look like this:

outcome1 = AdditionInteraction.run(x: 3, y: 5)
outcome2 = MultiplicationInteraction.run(x: outcome1.result, y: 7)
outcome3 = SquareInteraction.run(x: outcome2.result)

from active_interaction.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.