yael 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 14cc29be49dd968570eb537527f0966466d2e335270b021af321ee44316ba6a3
4
+ data.tar.gz: 3d62f3a69076d0d2242e6f3395c9234e36de7b208e6ee497dd3d0f81c66d6271
5
+ SHA512:
6
+ metadata.gz: c2c52a79448e78d5ee94d9e06fa4c659220b9cf7c7cb893752c0eff523e42be0d28f3b6fa0e1082f135ed197e82c936796a82258a55d561a44766e5a50acdfdf
7
+ data.tar.gz: 10f77c4d8ad62e0101778988b88c2ed675e2531b0f08831453c38461a6b68865429b296dc5dc71446892ab60feebcc2a2652fca5725f0d0b6315534d1f48487e
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ ; EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ ; top-most EditorConfig file
4
+ root = true
5
+
6
+ ; Unix-style newlines with a newline ending every file
7
+ [*]
8
+ indent_style = spaces
9
+ end_of_line = lf
10
+ insert_final_newline = true
11
+ trim_trailing_whitespace = true
12
+ indent_size = 2
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /pkg
data/.rubocop.yml ADDED
@@ -0,0 +1,69 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+ DisplayCopNames: true
4
+ DisplayStyleGuide: true
5
+ NewCops: disable
6
+
7
+ Layout/LineLength:
8
+ Enabled: false
9
+
10
+
11
+ Layout/FirstArrayElementIndentation:
12
+ EnforcedStyle: consistent
13
+
14
+ Layout/ParameterAlignment:
15
+ EnforcedStyle: with_fixed_indentation
16
+
17
+ Style/ConditionalAssignment:
18
+ Enabled: false
19
+
20
+ Style/Documentation:
21
+ Enabled: false
22
+
23
+ Style/EmptyMethod:
24
+ EnforcedStyle: expanded
25
+
26
+ Style/MultilineBlockChain:
27
+ Enabled: false
28
+
29
+ Style/DoubleNegation:
30
+ Enabled: false
31
+
32
+ Layout/MultilineMethodCallIndentation:
33
+ EnforcedStyle: indented
34
+
35
+ Style/NilComparison:
36
+ Enabled: false
37
+
38
+ Style/ClassAndModuleChildren:
39
+ Enabled: false
40
+
41
+ Style/NumericLiterals:
42
+ MinDigits: 15
43
+
44
+ Style/NumericPredicate:
45
+ Enabled: false
46
+
47
+ Style/WordArray:
48
+ EnforcedStyle: brackets
49
+
50
+ Style/SymbolArray:
51
+ EnforcedStyle: brackets
52
+
53
+ Style/RescueModifier:
54
+ Enabled: false
55
+
56
+ Style/Lambda:
57
+ EnforcedStyle: literal
58
+
59
+ Style/RegexpLiteral:
60
+ Enabled: false
61
+
62
+ Style/StringLiterals:
63
+ EnforcedStyle: double_quotes
64
+
65
+ Style/Alias:
66
+ Enabled: false
67
+
68
+ Metrics:
69
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ yael (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ yael!
15
+
16
+ BUNDLED WITH
17
+ 2.0.2
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Yael
2
+
3
+ Yael is **Y**et **A**nother **E**vent **L**ibrary based on top of ActiveJob. It helps you dispatching events asynchronously in your code base and react to them just like routing in Rails.
4
+
5
+ ## Usage
6
+ Define your routes in `config/events.rb` like this:
7
+
8
+ ```ruby
9
+ Yael::Bus.shared.routing do
10
+ dispatch :order_confirmed, to: "order_mailer#confirm", delay: 5.minutes, queue: :low_priority
11
+ dispatch :order_confirmed, to: "slack"
12
+ end
13
+ ```
14
+
15
+ Now include the publisher in your class and dispatch events:
16
+
17
+ ```ruby
18
+ class Order < ApplicationRecord
19
+ include Yael::Publisher
20
+
21
+ def confirm_order
22
+ publish :order_confirmed, ip: some_id, some_other_data: data
23
+ end
24
+ end
25
+
26
+ class Slack
27
+ def self.on_order_confirmed(ip:)
28
+ # send a notification to slack
29
+ end
30
+ end
31
+ ```
32
+
33
+ Calling `confirm_order` now will invoke `OrderMailer.confirm` and `Slack.notify_order`.
34
+
35
+ You can also request historical events of an object: `Yael::Event.for(Order.find(id))` will return all events recorded.
36
+
37
+ ### Some Things to Keep in Mind
38
+ - the subscriber signature does not have to exactly match the published payload - yael will return all matching parameters
39
+ - everything is executed asynchronously inside of a job, even dispatching events
40
+ - you can not only publish from `ActiveRecord` models, but from any class by providing a stream name: `Yael::Bus.shared.dispatch(:event_name, stream: "my_stream", payload: {})`
41
+
42
+ ## Installation
43
+
44
+ Add this line to your application's Gemfile:
45
+
46
+ ```ruby
47
+ gem 'yael'
48
+ ```
49
+
50
+ And then execute:
51
+ ```bash
52
+ bundle install
53
+ ```
54
+
55
+ Generate a migration for your database:
56
+
57
+ ```ruby
58
+ rails generate yeal:install
59
+ rails db:migrate
60
+ ```
61
+
62
+ ## Development
63
+
64
+ 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.
65
+
66
+ 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
67
+
68
+ ## Contributing
69
+
70
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/yael. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/yael/blob/master/CODE_OF_CONDUCT.md).
71
+
72
+ ## License
73
+
74
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
75
+
76
+ ## Code of Conduct
77
+
78
+ Everyone interacting in the Yael project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/yael/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Yael
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ desc "This generator installs Yael"
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ def copy_dispatch_map
12
+ template "events.rb", "config/events.rb"
13
+ end
14
+
15
+ def copy_migration
16
+ template "create_events.rb", "db/migrate/#{DateTime.current.strftime '%Y%m%d%H%M%S'}_create_yael_events.rb"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateYaelEvents < ActiveRecord::Migration[5.1]
4
+ def change
5
+ create_table :yael_events do |t|
6
+ t.string :name, null: false
7
+ t.string :stream, null: false
8
+ t.jsonb :payload, null: false
9
+ t.datetime :created_at, null: false, default: "NOW()"
10
+ end
11
+
12
+ add_index :yael_events, :name
13
+ add_index :yael_events, :stream
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Yael::Bus.shared.routing do
4
+ # dispatch :order_confirmed, to: "order_mailer#confirm", queue: :low_priority
5
+ end
data/lib/yael.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "yael/version"
4
+ require_relative "yael/bus"
5
+ require_relative "yael/dispatch_job"
6
+ require_relative "yael/dispatch_map"
7
+ require_relative "yael/event"
8
+ require_relative "yael/execution_job"
9
+ require_relative "yael/publisher"
10
+ require_relative "yael/route"
11
+
12
+ module Yael
13
+ class Error < StandardError; end
14
+ # Your code goes here...
15
+ end
data/lib/yael/bus.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ class Bus
5
+ class << self
6
+ def shared
7
+ unless @shared
8
+ @shared = Bus.new
9
+ path = Rails.root.join("config/events.rb")
10
+ if path.exist?
11
+ reloader = Rails.configuration.file_watcher.new([path]) do
12
+ load path
13
+ end
14
+ Rails.application.reloader.to_prepare do
15
+ reloader.execute_if_updated
16
+ end
17
+ Rails.application.reloaders << reloader
18
+ reloader.execute
19
+ end
20
+ end
21
+ @shared
22
+ end
23
+ end
24
+
25
+ def routes
26
+ @routes ||= []
27
+ end
28
+
29
+ def routing(&block)
30
+ @routes = DispatchMap.new(block).routes
31
+ end
32
+
33
+ def dispatch(name, stream:, payload:)
34
+ stream = Event.stream_for(stream) unless stream.is_a?(String)
35
+ DispatchJob.perform_later(name: name, stream: stream, payload: payload, created_at: DateTime.current)
36
+ end
37
+
38
+ def process(event)
39
+ routes.each do |route|
40
+ route.dispatch(event) if route.matches? event.name
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ class DispatchJob < ActiveJob::Base
5
+ retry_on ActiveRecord::Deadlocked
6
+
7
+ queue_as :dispatch
8
+
9
+ def perform(name:, stream:, payload:, created_at:, persist: true)
10
+ event = Event.new id: SecureRandom.uuid, name: name, stream: stream, payload: payload, created_at: created_at
11
+ event.save! if persist
12
+ Yael::Bus.shared.process(event)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ class DispatchMap
5
+ def initialize(block)
6
+ @routes = []
7
+ instance_eval(&block)
8
+ end
9
+
10
+ attr_reader :routes
11
+
12
+ protected
13
+
14
+ def dispatch(descriptor, to:, queue: :default, after: nil)
15
+ @routes.push Route.new descriptor: descriptor, target: to, queue: queue, delay: after
16
+ end
17
+ end
18
+ end
data/lib/yael/event.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ class Event < ActiveRecord::Base
5
+ class << self
6
+ def stream_for(object)
7
+ "#{object.class.name.tableize}_#{object.id}"
8
+ end
9
+ end
10
+
11
+ self.table_name = "yael_events"
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ class ExecutionJob < ActiveJob::Base
5
+ retry_on ActiveRecord::Deadlocked
6
+
7
+ def perform(target_name, method, args)
8
+ target_constant = target_name.constantize
9
+ parameters = extract_parameters(target_constant.method(method), args)
10
+ target_constant.public_send method, **parameters
11
+ end
12
+
13
+ private
14
+
15
+ def extract_parameters(method, args)
16
+ requested = method.parameters.select { |e| e.first == :keyreq }.map(&:second)
17
+ args.slice(*requested)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ module Publisher
5
+ extend ActiveSupport::Concern
6
+
7
+ def publish(name, on: self, **payload)
8
+ Yael::Bus.shared.dispatch(name, stream: on, payload: payload)
9
+ end
10
+ end
11
+ end
data/lib/yael/route.rb ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ class Route
5
+ attr_reader :descriptor, :target, :queue, :delay
6
+
7
+ def initialize(descriptor:, target:, queue: :default, delay: nil)
8
+ @descriptor = descriptor
9
+ @target = target
10
+ @queue = queue
11
+ @delay = delay
12
+ @target_name = target.split("#").first.classify
13
+ @target_method = target.split("#").second
14
+ end
15
+
16
+ def matches?(stream)
17
+ return true if descriptor == :all
18
+
19
+ descriptor.to_s == stream
20
+ end
21
+
22
+ def dispatch(event)
23
+ method = target_method || "on_#{event.name}"
24
+ args = event.payload.symbolize_keys
25
+ ExecutionJob.set(queue: queue, wait: delay).perform_later(target_name, method, args)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :target_name, :target_method
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yael
4
+ VERSION = "0.0.1"
5
+ end
data/yael.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/yael/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "yael"
7
+ spec.version = Yael::VERSION
8
+ spec.authors = ["Jens Ravens"]
9
+ spec.email = ["jens@nerdgeschoss.de"]
10
+
11
+ spec.summary = "Event based Rails with ActiveJob"
12
+ spec.description = "Yael is Yet Another Event Library based on top of ActiveJob. It helps you dispatching events asynchronously in your code base and react to them just like routing in Rails."
13
+ spec.homepage = "https://nerdgeschoss.de"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/nerdgeschoss/yael"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ # Uncomment to register a new dependency of your gem
30
+ # spec.add_dependency "example-gem", "~> 1.0"
31
+
32
+ # For more information and examples about making a new gem, checkout our
33
+ # guide at: https://bundler.io/guides/creating_gem.html
34
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yael
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jens Ravens
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-02-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Yael is Yet Another Event Library based on top of ActiveJob. It helps
14
+ you dispatching events asynchronously in your code base and react to them just like
15
+ routing in Rails.
16
+ email:
17
+ - jens@nerdgeschoss.de
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".editorconfig"
23
+ - ".gitignore"
24
+ - ".rubocop.yml"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - README.md
28
+ - Rakefile
29
+ - lib/generators/yael/install/install_generator.rb
30
+ - lib/generators/yael/install/templates/create_events.rb
31
+ - lib/generators/yael/install/templates/events.rb
32
+ - lib/yael.rb
33
+ - lib/yael/bus.rb
34
+ - lib/yael/dispatch_job.rb
35
+ - lib/yael/dispatch_map.rb
36
+ - lib/yael/event.rb
37
+ - lib/yael/execution_job.rb
38
+ - lib/yael/publisher.rb
39
+ - lib/yael/route.rb
40
+ - lib/yael/version.rb
41
+ - yael.gemspec
42
+ homepage: https://nerdgeschoss.de
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://nerdgeschoss.de
47
+ source_code_uri: https://github.com/nerdgeschoss/yael
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.4.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.1.4
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Event based Rails with ActiveJob
67
+ test_files: []