chrono_trigger 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbca0c2caa1b69582cd2e5e35bb6df578b9ae913d2da07f8e3b84d76c812ade7
4
- data.tar.gz: 02c0808b8df6dc06be2aa7c8812f1cac72bfb53192c97acb0ba9ba6a48f5225e
3
+ metadata.gz: 02eacba629d85393eb1079826cea360b4b9bf4c6ab98e21df5a3d0359fc017ec
4
+ data.tar.gz: 86fc4dc406ad41a089a62ee674e579f30969fe4d680cd29a2eee14749ae61c1b
5
5
  SHA512:
6
- metadata.gz: 016a3b4aacbf0c960de0c17885287eb4505da012d130eb6c0ea9a96cd4098d1848cc37af9072d4907816e3c25bc6d2f712ef0f449245cff598020cad6bea184d
7
- data.tar.gz: dfb36735721ce71346ac77bbd0a031f91ffa98082850f89fc1d201a5dafe6de0875cfe36fa6c95f6b39b3233593b5e0006c3bc6792eb2d887991fc19806f74c6
6
+ metadata.gz: cac2cd072ad1f2fa00fc7f024ab6819a778f50a602d60018c770e7fdddfe5e8c5789b54e16da8aa9c25cd9f8f8206f64c02e65bfe1417baea4983a340821a78c
7
+ data.tar.gz: f6d6c901a5e73ac682ed954f92b2878ad5ec7c975ca27c86dfd8f571b2434765290c1b55b994888c86b12c57688ec2b886dd74d38fa3370f0c467b2d67e80a13
data/README.md CHANGED
@@ -1,2 +1,89 @@
1
- # Initial page
1
+ ---
2
+ description: Make Rails apps that feel alive.
3
+ ---
4
+
5
+ # ChronoTrigger
6
+
7
+ [![GitHub stars](https://img.shields.io/github/stars/leastbad/chrono_trigger?style=social)](https://github.com/leastbad/chrono_trigger) [![GitHub forks](https://img.shields.io/github/forks/leastbad/chrono_trigger?style=social)](https://github.com/leastbad/chrono_trigger) [![Twitter follow](https://img.shields.io/twitter/follow/theleastbad?style=social)](https://twitter.com/theleastbad) [![Discord](https://img.shields.io/discord/681373845323513862)](https://discord.gg/GnweR3)
8
+
9
+ ChronoTrigger is a clock-based scheduler that runs inside your Rails app. Use it to run code \(events\) at specific times and intervals.
10
+
11
+ ![](.gitbook/assets/chrono-trigger.jpg)
12
+
13
+ ## Why use ChronoTrigger?
14
+
15
+ Nobody wants to be the first person to arrive at a club, and yet, this is exactly what most app onboarding processes feel like.
16
+
17
+ A typical visitor will spend _7-12 seconds_ evaluating your hard work before deciding if they will engage. You must convey that _exciting things are happening_, or they will close the tab and forget you exist.
18
+
19
+ **ChronoTrigger is a tool designed to breathe life into otherwise static user interfaces and onboarding experiences.**
20
+
21
+ It allows you to subtly expose users to a crafted narrative that nudges them forward, without feeling like a canned theme park ride. The story is advanced by their actions and their engagement is rewarded with simulated interaction, even if they are user number one.
22
+
23
+ ## Is ChronoTrigger for you?
24
+
25
+ If you spend months building something cool, and then hustle to get people to check it out, you get exactly one chance to make your first and only impression. It's up to you to do everything you can to make sure that your app is special enough to love. To succeed long-term, you need these first users to become cheerleaders and evangelize to their friends.
26
+
27
+ If you think about the stand-out [onboarding success stories](https://www.useronboard.com/user-onboarding-teardowns/) like Slack, they all prioritize anticipating what the user will be thinking, feeling and wondering during each _moment_ of the first minutes the user spends on the site.
28
+
29
+ ChronoTrigger is for developers who are proud of what they have created; tech founders looking for a way to give new users an aspirational story to tell themselves. This converging path leads to feelings of ownership and drives conversion without a hard sell.
30
+
31
+ You will use ChronoTrigger to orchestrate the onboarding experience your app deserves:
32
+
33
+ * interactive demos, charts and UI elements
34
+ * automation / wizards that feel personal
35
+ * placeholder content that changes to help tell a story
36
+ * a path through features instead of just clicking everything
37
+ * simulated human responses and exchanges
38
+
39
+ ### Why not ActiveJob?
40
+
41
+ ActiveJob is amazing, but creating Jobs for typical ChronoTrigger use cases feels like taking a taxi to the next house.
42
+
43
+ Jobs are not designed to run immediately, and priority should be given to important things like mail delivery. There's also functionality in the Event class that would be hard to retrofit to Job classes.
44
+
45
+ It's also worth mentioning that requiring Sidekiq would require Heroku users to set up a worker dyno, even if you're not using ActiveJob. Finally, Sidekiq sometimes runs jobs multiple times!
46
+
47
+ ## How does ChronoTrigger work?
48
+
49
+ ChronoTrigger runs on real world time, like trains. Every second, on the second, ChronoTrigger decides whether there are new events to run. If so, there's a thread-safe and highly-optimized pool of workers waiting.
50
+
51
+ {% hint style="info" %}
52
+ Other event scheduling libraries tend to be either `cron` wrappers or use timing offsets \(think: `sleep 1`\) from whenever they are called and don't factor in their own timing footprint. In other words, 1000 loops later, more than 1000 seconds have passed. No good!
53
+ {% endhint %}
54
+
55
+ You start the ChronoTrigger Clock after your web server, and it runs inside your Rails app process.
56
+
57
+ ChronoTrigger Events live in `app/events` and follow a structure that will be familiar to ActiveJob users.
58
+
59
+ You can schedule Events from anywhere in your application that it makes sense to do so, such as:
60
+
61
+ * Controller actions and webhook callbacks
62
+ * ActiveRecord model callbacks
63
+ * Devise session/registration callbacks
64
+ * Reflex action methods
65
+ * ActionCable Connection/Channel subscription callbacks
66
+
67
+ ## Design concepts and goals
68
+
69
+ * Not a replacement for ActiveJob \(or cron!\)
70
+ * Events are ephemeral and disposable; failure should be fine 🤷
71
+ * Borrow the best ideas from the ActiveJob and CableReady APIs
72
+ * ActiveSupport::TimeWithZone all the way down
73
+ * All times are today, rounded to 1s for easy comparison
74
+ * Times are memoized to avoid side effects
75
+ * Events should be short term and soon; _there is no tomorrow_
76
+ * Run in-process with no additional infrastructure dependencies
77
+
78
+ ## Key features and advantages
79
+
80
+ * A natural fit with [CableReady](https://cableready.stimulusreflex.com/)
81
+ * Easy to learn, quick to implement
82
+ * Plays well with tools such as [StimulusReflex](https://docs.stimulusreflex.com/) and [Turbo Drive](https://turbo.hotwire.dev/handbook/drive)
83
+ * Configurable via an optional initializer file
84
+ * Worker pool provided by the excellent [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) library
85
+
86
+ ## Try it now
87
+
88
+ ![](.gitbook/assets/soon.jpg)
2
89
 
data/SUMMARY.md CHANGED
@@ -1,4 +1,8 @@
1
1
  # Table of contents
2
2
 
3
- * [Initial page](README.md)
3
+ * [ChronoTrigger](README.md)
4
+ * [Setup](setup.md)
5
+ * [Events](events.md)
6
+ * [The Clock](the-clock.md)
7
+ * [The Timeline](the-timeline.md)
4
8
 
@@ -4,6 +4,8 @@ require "rails/engine"
4
4
  require "active_support/all"
5
5
  require "singleton"
6
6
  require "concurrent-edge"
7
+ require "chrono_trigger/helpers/mitf"
8
+ require "chrono_trigger/helpers/now"
7
9
  require "chrono_trigger/clock"
8
10
  require "chrono_trigger/config"
9
11
  require "chrono_trigger/event"
@@ -15,7 +15,6 @@ module ChronoTrigger
15
15
  after: @after
16
16
  )
17
17
  new(options, args)
18
- id
19
18
  end
20
19
 
21
20
  def scope(value)
@@ -43,7 +42,6 @@ module ChronoTrigger
43
42
  return self unless value
44
43
  value = Time.zone.parse(value) if value.is_a?(String)
45
44
  @at = moment_in_the_future(value) if value.is_a?(ActiveSupport::TimeWithZone)
46
- puts @at.class
47
45
  self
48
46
  end
49
47
 
@@ -63,15 +61,7 @@ module ChronoTrigger
63
61
 
64
62
  private
65
63
 
66
- def moment_in_the_future(time_with_zone)
67
- Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
68
- end
69
- end
70
-
71
- class LostInTimeError < StandardError
72
- end
73
-
74
- class TodayIsTomorrowsYesterday < StandardError
64
+ include ChronoTrigger::Helpers::MITF
75
65
  end
76
66
 
77
67
  attr_reader :id, :scope, :args, :every, :before, :after, :purge
@@ -98,15 +88,11 @@ module ChronoTrigger
98
88
  @purge = true
99
89
  end
100
90
 
101
- def right_now
102
- now = Time.zone.now
103
- Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
104
- end
91
+ include ChronoTrigger::Helpers::MITF
92
+ include ChronoTrigger::Helpers::Now
105
93
 
106
- def moment_in_the_future(time_with_zone)
107
- raise(LostInTimeError, "Argument must be of type ActiveSupport::TimeWithZone eg. right_now + 1.second") unless time_with_zone.is_a?(ActiveSupport::TimeWithZone)
108
- raise(TodayIsTomorrowsYesterday, "Time must be in the future eg. 1.second.from_now") unless time_with_zone.future?
109
- Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
94
+ def to_s
95
+ inspect
110
96
  end
111
97
 
112
98
  private
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChronoTrigger
4
+ module Helpers
5
+ module MITF
6
+ class LostInTimeError < StandardError
7
+ end
8
+
9
+ class TodayIsTomorrowsYesterday < StandardError
10
+ end
11
+
12
+ def moment_in_the_future(time_with_zone)
13
+ raise(LostInTimeError, "Argument must be of type ActiveSupport::TimeWithZone eg. right_now + 1.second") unless time_with_zone.is_a?(ActiveSupport::TimeWithZone)
14
+ raise(TodayIsTomorrowsYesterday, "Time must be in the future eg. 1.second.from_now") unless time_with_zone.future?
15
+ Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChronoTrigger
4
+ module Helpers
5
+ module Now
6
+ def right_now
7
+ now = Time.zone.now
8
+ Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
9
+ end
10
+ end
11
+ end
12
+ end
@@ -19,6 +19,8 @@ module ChronoTrigger
19
19
  end
20
20
 
21
21
  def remove(uuid)
22
+ return self unless uuid
23
+ uuid = uuid.id if uuid.is_a?(ChronoTrigger::Event)
22
24
  uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
23
25
  @events.each { |event| event.purge! if event.id == uuid } if uuid && uuid_regex.match?(uuid.to_s.downcase)
24
26
  self
@@ -63,9 +65,6 @@ module ChronoTrigger
63
65
 
64
66
  private
65
67
 
66
- def right_now
67
- now = Time.zone.now
68
- Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
69
- end
68
+ include ChronoTrigger::Helpers::Now
70
69
  end
71
70
  end
@@ -4,25 +4,11 @@ module ChronoTrigger
4
4
  module Timeline
5
5
  extend ::ActiveSupport::Concern
6
6
 
7
- class LostInTimeError < StandardError
8
- end
9
-
10
- class TodayIsTomorrowsYesterday < StandardError
11
- end
12
-
13
7
  def chrono_trigger
14
8
  ChronoTrigger::Schedule.instance
15
9
  end
16
10
 
17
- def right_now
18
- now = Time.zone.now
19
- Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
20
- end
21
-
22
- def moment_in_the_future(time_with_zone)
23
- raise(LostInTimeError, "Argument must be of type ActiveSupport::TimeWithZone eg. right_now + 1.second") unless time_with_zone.is_a?(ActiveSupport::TimeWithZone)
24
- raise(TodayIsTomorrowsYesterday, "Time must be in the future eg. 1.second.from_now") unless time_with_zone.future?
25
- Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
26
- end
11
+ include ChronoTrigger::Helpers::MITF
12
+ include ChronoTrigger::Helpers::Now
27
13
  end
28
14
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChronoTrigger
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.5"
5
5
  end
@@ -4,7 +4,7 @@ module ChronoTrigger
4
4
  class Worker < Concurrent::Actor::RestartingContext
5
5
  def on_message(event)
6
6
  Rails.logger.debug "ChronoTrigger: #{event.inspect}"
7
- event.perform(*event.args) if event.at.nil? || (event.before && event.at < event.before)
7
+ event.perform(*event.args) if event.at.nil? || (event.at && event.before.nil?) || (event.before && event.at < event.before)
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chrono_trigger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - leastbad
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-12 00:00:00.000000000 Z
11
+ date: 2021-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -161,14 +161,18 @@ files:
161
161
  - lib/chrono_trigger/clock.rb
162
162
  - lib/chrono_trigger/config.rb
163
163
  - lib/chrono_trigger/event.rb
164
+ - lib/chrono_trigger/helpers/mitf.rb
165
+ - lib/chrono_trigger/helpers/now.rb
164
166
  - lib/chrono_trigger/schedule.rb
165
167
  - lib/chrono_trigger/timeline.rb
166
168
  - lib/chrono_trigger/version.rb
167
169
  - lib/chrono_trigger/worker.rb
168
- homepage: https://github.com/leastbad/chrono_trigger
170
+ homepage: https://chronotrigger.leastbad.com/
169
171
  licenses:
170
172
  - MIT
171
- metadata: {}
173
+ metadata:
174
+ source_code_uri: https://github.com/leastbad/chrono_trigger
175
+ documentation_uri: https://chronotrigger.leastbad.com/
172
176
  post_install_message:
173
177
  rdoc_options: []
174
178
  require_paths: