futurism 0.8.0 → 1.0.0

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: 75dd023300659792f3dd9e114727b0003204ed6b2e9e990413fcc1ca82971fba
4
- data.tar.gz: b73280cb140625612fc5e7188e301f98e5be0eb19e7359c7786fae8b4a8f4b8c
3
+ metadata.gz: 414f6857d0112042c7745a9198e9e9c05d2886d3649f955d53d4ed09fed1dddc
4
+ data.tar.gz: cd55e6f01214c0379e2a5b2d0505c76486cc388114daa3888439e608a0a74335
5
5
  SHA512:
6
- metadata.gz: c9285da73bb00137f7aea08b2171d370b032681abd79b8d37e652b1aef546159a2d568ecc695ecaeb0a7f3a8f4a74abb0a6a310dca9969021923d4590c0fd03b
7
- data.tar.gz: ae425d91dcae19d847105b5633a0f0b434166d5cb45f9aed14bfccb2a07cc9661594c2a814d072b541b30d7b401c6273919e6c199ffcdb38c146d6fc8f8a6a74
6
+ metadata.gz: 14de66605b2c41ef1cc6aca2c42507a856dfd67cbb1dc23de029b77bfb5c24b5b6a5a2ecf65b95cef5622c45dfef6ecccd05059daeaf25615a12356015b5b614
7
+ data.tar.gz: a63476d5c42a5db0ae0220f71b4c5a4792aaf529c0741df322a239c8be458b8433c94bd461574833c1b102e1b8f444038adb1c7f66a3a16d6b73025672fa8022
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
6
6
  Lazy-load Rails partials via CableReady
7
7
 
8
- :rotating_light: *Futurism is still in pre-1.0 state. As much as I hope to keep the API backwards-compatible, I cannot guarantee it* :rotating_light:
8
+ :rotating_light: *BREAKING CHANGE: With v1.0, futurism has been transferred to the [stimulusreflex](https://github.com/stimulusreflex) organization. Please update your npm package to `@stimulus_reflex/futurism` accordingly* :rotating_light:
9
9
 
10
10
  <img src="https://user-images.githubusercontent.com/4352208/88374198-9e6f3500-cd99-11ea-804b-0216ed320eff.jpg" alt="birmingham-museums-trust-GrvC6MI-z4w-unsplash" width="50%" align="center"/>
11
11
  <span>Photo by <a href="https://unsplash.com/@birminghammuseumstrust?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Birmingham Museums Trust</a> on <a href="https://unsplash.com/s/photos/futurism?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>
@@ -21,6 +21,7 @@ Lazy-load Rails partials via CableReady
21
21
  - [Explicit Partial](#explicit-partial)
22
22
  - [HTML Options](#html-options)
23
23
  - [Eager Loading](#eager-loading)
24
+ - [Broadcast Partials Individually](#broadcast-partials-individually)
24
25
  - [Events](#events)
25
26
  - [Installation](#installation)
26
27
  - [Manual Installation](#manual-installation)
@@ -33,7 +34,7 @@ Lazy-load Rails partials via CableReady
33
34
 
34
35
  ## Facts
35
36
  - only one dependency: CableReady
36
- - bundle size (without CableReady) is around [~2.46kB](https://bundlephobia.com/result?p=@minthesize/futurism@0.7.2)
37
+ - bundle size (without CableReady) is around [~2.46kB](https://bundlephobia.com/result?p=@stimulus_reflex/futurism@0.7.2)
37
38
 
38
39
  ### Browser Support
39
40
 
@@ -68,6 +69,8 @@ You can pass the placeholder as a block:
68
69
 
69
70
  ![aa601dec1930151f71dbf0d6b02b61c9](https://user-images.githubusercontent.com/4352208/87131629-f768a480-c294-11ea-89a9-ea0a76ee06ef.gif)
70
71
 
72
+ You can also omit the placeholder, which falls back to [eager loading](#eager-loading).
73
+
71
74
  ## API
72
75
 
73
76
  Currently there are two ways to call `futurize`, designed to wrap `render`'s behavior:
@@ -171,6 +174,19 @@ Futurism makes that dead simple:
171
174
  <% end %>
172
175
  ```
173
176
 
177
+ ### Broadcast Partials Individually
178
+ Futurism's default behavior is to `broadcast` partials as they are generated in batches:
179
+
180
+ On the client side, `IntersectionObserver` events are triggered in a debounced fashion, so several `render`s are performed on the server for each of those events. By default, futurism will group those to a single `broadcast` call (to save server CPU time).
181
+
182
+ For collections, however, you can opt into individual broadcasts by specifying `broadcast_each: true` in your helper usage:
183
+
184
+ ```erb
185
+ <%= futurize @posts, broadcast_each: true, extends: :tr do %>
186
+ <div class="placeholder"</td>
187
+ <% end %>
188
+ ```
189
+
174
190
  ## Events
175
191
 
176
192
  Once your futurize element has been rendered, the `futurize:appeared` custom event will be called.
@@ -193,19 +209,19 @@ To copy over the javascript files to your application, run
193
209
  $ bin/rails futurism:install
194
210
  ```
195
211
 
196
- **! Note that the installer will run `yarn add @minthesize/futurism` for you !**
212
+ **! Note that the installer will run `yarn add @stimulus_reflex/futurism` for you !**
197
213
 
198
214
  ### Manual Installation
199
215
  After `bundle`, install the Javascript library:
200
216
 
201
217
  ```bash
202
- $ bin/yarn add @minthesize/futurism
218
+ $ bin/yarn add @stimulus_reflex/futurism
203
219
  ```
204
220
 
205
221
  In your `app/javascript/channels/index.js`, add the following
206
222
 
207
223
  ```js
208
- import * as Futurism from '@minthesize/futurism'
224
+ import * as Futurism from '@stimulus_reflex/futurism'
209
225
 
210
226
  import consumer from './consumer'
211
227
 
@@ -273,7 +289,7 @@ git clone https://github.com/leastbad/stimulus_reflex_harness.git
273
289
  cd stimulus_reflex_harness
274
290
  git checkout futurism
275
291
  # Edit Gemfile to point point to local gem (e.g. `gem "futurism", path: "../futurism"`)
276
- # yarn link @minthesize/futurism
292
+ # yarn link @stimulus_reflex/futurism
277
293
 
278
294
 
279
295
  # Do your work, Submit PR, Profit!
@@ -290,6 +306,12 @@ cd path/to/project
290
306
  yarn install --force
291
307
  ```
292
308
 
309
+ ### Release
310
+
311
+ 1. Update the version numbers in `javascript/package.json` and `lib/futurism/version.rb`
312
+ 2. `rake release`
313
+ 3. `cd javascript && npm publish --access public`
314
+
293
315
  ## License
294
316
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
295
317
 
@@ -302,25 +324,25 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
302
324
  <!-- markdownlint-disable -->
303
325
  <table>
304
326
  <tr>
305
- <td align="center"><a href="http://www.julianrubisch.at"><img src="https://avatars0.githubusercontent.com/u/4352208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Julian Rubisch</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=julianrubisch" title="Code">💻</a></td>
306
- <td align="center"><a href="https://github.com/darkrubyist"><img src="https://avatars2.githubusercontent.com/u/11207292?v=4?s=100" width="100px;" alt=""/><br /><sub><b>darkrubyist</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=darkrubyist" title="Code">💻</a> <a href="https://github.com/julianrubisch/futurism/commits?author=darkrubyist" title="Documentation">📖</a></td>
307
- <td align="center"><a href="https://ParamagicDev.github.io/portfolio"><img src="https://avatars2.githubusercontent.com/u/26425882?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Konnor Rogers</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=ParamagicDev" title="Code">💻</a></td>
327
+ <td align="center"><a href="http://www.julianrubisch.at"><img src="https://avatars0.githubusercontent.com/u/4352208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Julian Rubisch</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=julianrubisch" title="Code">💻</a></td>
328
+ <td align="center"><a href="https://github.com/darkrubyist"><img src="https://avatars2.githubusercontent.com/u/11207292?v=4?s=100" width="100px;" alt=""/><br /><sub><b>darkrubyist</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=darkrubyist" title="Code">💻</a> <a href="https://github.com/stimulusreflex/futurism/commits?author=darkrubyist" title="Documentation">📖</a></td>
329
+ <td align="center"><a href="https://ParamagicDev.github.io/portfolio"><img src="https://avatars2.githubusercontent.com/u/26425882?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Konnor Rogers</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=ParamagicDev" title="Code">💻</a></td>
308
330
  <td align="center"><a href="https://www.andrewm.codes"><img src="https://avatars1.githubusercontent.com/u/18423853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Mason</b></sub></a><br /><a href="#maintenance-andrewmcodes" title="Maintenance">🚧</a></td>
309
- <td align="center"><a href="http://gorails.com"><img src="https://avatars1.githubusercontent.com/u/67093?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Oliver</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=excid3" title="Code">💻</a> <a href="https://github.com/julianrubisch/futurism/pulls?q=is%3Apr+reviewed-by%3Aexcid3" title="Reviewed Pull Requests">👀</a></td>
310
- <td align="center"><a href="https://github.com/leastbad"><img src="https://avatars2.githubusercontent.com/u/38150464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>leastbad</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=leastbad" title="Code">💻</a> <a href="https://github.com/julianrubisch/futurism/pulls?q=is%3Apr+reviewed-by%3Aleastbad" title="Reviewed Pull Requests">👀</a></td>
311
- <td align="center"><a href="http://code.digimonkey.com"><img src="https://avatars0.githubusercontent.com/u/74207?v=4?s=100" width="100px;" alt=""/><br /><sub><b>M. E. Patterson</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/issues?q=author%3Amepatterson" title="Bug reports">🐛</a></td>
331
+ <td align="center"><a href="http://gorails.com"><img src="https://avatars1.githubusercontent.com/u/67093?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chris Oliver</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=excid3" title="Code">💻</a> <a href="https://github.com/stimulusreflex/futurism/pulls?q=is%3Apr+reviewed-by%3Aexcid3" title="Reviewed Pull Requests">👀</a></td>
332
+ <td align="center"><a href="https://github.com/leastbad"><img src="https://avatars2.githubusercontent.com/u/38150464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>leastbad</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=leastbad" title="Code">💻</a> <a href="https://github.com/stimulusreflex/futurism/pulls?q=is%3Apr+reviewed-by%3Aleastbad" title="Reviewed Pull Requests">👀</a></td>
333
+ <td align="center"><a href="http://code.digimonkey.com"><img src="https://avatars0.githubusercontent.com/u/74207?v=4?s=100" width="100px;" alt=""/><br /><sub><b>M. E. Patterson</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/issues?q=author%3Amepatterson" title="Bug reports">🐛</a></td>
312
334
  </tr>
313
335
  <tr>
314
- <td align="center"><a href="http://fractaledmind.com"><img src="https://avatars3.githubusercontent.com/u/5077225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stephen Margheim</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=fractaledmind" title="Code">💻</a></td>
315
- <td align="center"><a href="http://hass.codes"><img src="https://avatars2.githubusercontent.com/u/1064205?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hassanin Ahmed</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=sas1ni69" title="Code">💻</a></td>
316
- <td align="center"><a href="https://marcoroth.dev"><img src="https://avatars2.githubusercontent.com/u/6411752?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marco Roth</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=marcoroth" title="Code">💻</a></td>
317
- <td align="center"><a href="https://viedit.com"><img src="https://avatars1.githubusercontent.com/u/49990587?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viedit com</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=vieditcom" title="Documentation">📖</a></td>
318
- <td align="center"><a href="http://scottbarrow.ca"><img src="https://avatars2.githubusercontent.com/u/5571736?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Barrow</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=scottbarrow" title="Code">💻</a></td>
319
- <td align="center"><a href="http://domchristie.co.uk"><img src="https://avatars0.githubusercontent.com/u/111734?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dom Christie</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/pulls?q=is%3Apr+reviewed-by%3Adomchristie" title="Reviewed Pull Requests">👀</a></td>
320
- <td align="center"><a href="http://www.rickychilcott.com"><img src="https://avatars1.githubusercontent.com/u/445759?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricky Chilcott</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/pulls?q=is%3Apr+reviewed-by%3Arickychilcott" title="Reviewed Pull Requests">👀</a></td>
336
+ <td align="center"><a href="http://fractaledmind.com"><img src="https://avatars3.githubusercontent.com/u/5077225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stephen Margheim</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=fractaledmind" title="Code">💻</a></td>
337
+ <td align="center"><a href="http://hass.codes"><img src="https://avatars2.githubusercontent.com/u/1064205?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hassanin Ahmed</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=sas1ni69" title="Code">💻</a></td>
338
+ <td align="center"><a href="https://marcoroth.dev"><img src="https://avatars2.githubusercontent.com/u/6411752?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marco Roth</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=marcoroth" title="Code">💻</a></td>
339
+ <td align="center"><a href="https://viedit.com"><img src="https://avatars1.githubusercontent.com/u/49990587?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viedit com</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=vieditcom" title="Documentation">📖</a></td>
340
+ <td align="center"><a href="http://scottbarrow.ca"><img src="https://avatars2.githubusercontent.com/u/5571736?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Barrow</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=scottbarrow" title="Code">💻</a></td>
341
+ <td align="center"><a href="http://domchristie.co.uk"><img src="https://avatars0.githubusercontent.com/u/111734?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dom Christie</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/pulls?q=is%3Apr+reviewed-by%3Adomchristie" title="Reviewed Pull Requests">👀</a></td>
342
+ <td align="center"><a href="http://www.rickychilcott.com"><img src="https://avatars1.githubusercontent.com/u/445759?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricky Chilcott</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/pulls?q=is%3Apr+reviewed-by%3Arickychilcott" title="Reviewed Pull Requests">👀</a></td>
321
343
  </tr>
322
344
  <tr>
323
- <td align="center"><a href="https://github.com/mansakondo"><img src="https://avatars.githubusercontent.com/u/47113995?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mansakondo</b></sub></a><br /><a href="https://github.com/julianrubisch/futurism/commits?author=mansakondo" title="Code">💻</a></td>
345
+ <td align="center"><a href="https://github.com/mansakondo"><img src="https://avatars.githubusercontent.com/u/47113995?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mansakondo</b></sub></a><br /><a href="https://github.com/stimulusreflex/futurism/commits?author=mansakondo" title="Code">💻</a></td>
324
346
  </tr>
325
347
  </table>
326
348
 
@@ -0,0 +1,105 @@
1
+ module Futurism
2
+ module Helpers
3
+ def futurize(records_or_string = nil, extends:, **options, &block)
4
+ if Rails.env.test? && Futurism.skip_in_test
5
+ if records_or_string.nil?
6
+ return render(**options)
7
+ else
8
+ return render(records_or_string, **options)
9
+ end
10
+ end
11
+
12
+ placeholder = capture(&block)
13
+
14
+ if records_or_string.is_a?(ActiveRecord::Base) || records_or_string.is_a?(ActiveRecord::Relation)
15
+ futurize_active_record(records_or_string, extends: extends, placeholder: placeholder, **options)
16
+ elsif records_or_string.is_a?(String)
17
+ html_options = options.delete(:html_options)
18
+ futurize_with_options(extends: extends, placeholder: placeholder, partial: records_or_string, locals: options, html_options: html_options)
19
+ else
20
+ futurize_with_options(extends: extends, placeholder: placeholder, **options)
21
+ end
22
+ end
23
+
24
+ def futurize_with_options(extends:, placeholder:, **options)
25
+ collection = options.delete(:collection)
26
+ if collection.nil?
27
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options).render
28
+ else
29
+ collection_class_name = collection.try(:klass).try(:name) || collection.first.class.to_s
30
+ as = options.delete(:as) || collection_class_name.underscore
31
+ collection.each_with_index.map { |record, index|
32
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options.deep_merge(locals: {as.to_sym => record, "#{as}_counter".to_sym => index})).render
33
+ }.join.html_safe
34
+ end
35
+ end
36
+
37
+ def futurize_active_record(records, extends:, placeholder:, **options)
38
+ Array(records).map { |record|
39
+ WrappingFuturismElement.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
40
+ }.join.html_safe
41
+ end
42
+
43
+ # wraps functionality for rendering a futurism element
44
+ class WrappingFuturismElement
45
+ include ActionView::Helpers
46
+ include Futurism::MessageVerifier
47
+
48
+ attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :controller
49
+
50
+ def initialize(extends:, placeholder:, options:)
51
+ @extends = extends
52
+ @placeholder = placeholder
53
+ @eager = options.delete(:eager)
54
+ @controller = options.delete(:controller)
55
+ @html_options = options.delete(:html_options) || {}
56
+ @data_attributes = html_options.fetch(:data, {}).except(:sgid, :signed_params)
57
+ @model = options.delete(:model)
58
+ @options = data_attributes.any? ? options.merge(data: data_attributes) : options
59
+ end
60
+
61
+ def dataset
62
+ data_attributes.merge({
63
+ signed_params: signed_params,
64
+ sgid: model && model.to_sgid.to_s,
65
+ eager: eager.presence,
66
+ signed_controller: signed_controller
67
+ })
68
+ end
69
+
70
+ def render
71
+ case extends
72
+ when :li
73
+ content_tag :li, placeholder, html_options.deep_merge({data: dataset, is: "futurism-li"})
74
+ when :tr
75
+ content_tag :tr, placeholder, html_options.deep_merge({data: dataset, is: "futurism-table-row"})
76
+ else
77
+ content_tag :"futurism-element", placeholder, html_options.deep_merge({data: dataset})
78
+ end
79
+ end
80
+
81
+ def transformed_options
82
+ require_relative "shims/deep_transform_values" unless options.respond_to? :deep_transform_values
83
+
84
+ options.deep_transform_values do |value|
85
+ next(value) unless value.respond_to?(:to_global_id)
86
+ next(value) if value.is_a?(ActiveRecord::Base) && value.new_record?
87
+
88
+ value.to_global_id.to_s
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def signed_params
95
+ message_verifier.generate(transformed_options)
96
+ end
97
+
98
+ def signed_controller
99
+ return unless controller.present?
100
+
101
+ message_verifier.generate(controller.to_s)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -15,14 +15,16 @@ module Futurism
15
15
  end
16
16
 
17
17
  def receive(data)
18
- resources = data.fetch_values("signed_params", "sgids", "signed_controllers", "urls") { |_key| Array.new(data["signed_params"].length, nil) }.transpose
18
+ resources = data.fetch_values("signed_params", "sgids", "signed_controllers", "urls", "broadcast_each") { |_key| Array.new(data["signed_params"].length, nil) }.transpose
19
19
 
20
20
  resolver = Resolver::Resources.new(resource_definitions: resources, connection: connection, params: @params)
21
- resolver.resolve do |selector, html|
21
+ resolver.resolve do |selector, html, broadcast_each|
22
22
  cable_ready[stream_name].outer_html(
23
23
  selector: selector,
24
24
  html: html
25
25
  )
26
+
27
+ cable_ready.broadcast if broadcast_each
26
28
  end
27
29
 
28
30
  cable_ready.broadcast
@@ -1,7 +1,6 @@
1
1
  module Futurism
2
2
  class Channel < ActionCable::Channel::Base
3
3
  include CableReady::Broadcaster
4
- include Futurism::MessageVerifier
5
4
 
6
5
  def stream_name
7
6
  ids = connection.identifiers.map { |identifier| send(identifier).try(:id) || send(identifier) }
@@ -18,23 +17,8 @@ module Futurism
18
17
  def receive(data)
19
18
  resources = data.fetch_values("signed_params", "sgids", "signed_controllers", "urls") { |_key| Array.new(data["signed_params"].length, nil) }.transpose
20
19
 
21
- resources_with_sgids, resources_without_sgids = resources.partition { |signed_params, sgid, *| sgid.present? }
22
-
23
- GlobalID::Locator.locate_many_signed resources_with_sgids.map(&:second)
24
-
25
- resources_without_sgids.each do |signed_params, sgid, signed_controller, url|
26
- selector = "[data-signed-params='#{signed_params}']"
27
- selector << "[data-sgid='#{sgid}']" if sgid.present?
28
-
29
- controller = Resolver::Controller.from(signed_string: signed_controller)
30
- renderer = Resolver::Controller::Renderer.for(controller: controller,
31
- connection: connection,
32
- url: url,
33
- params: @params)
34
-
35
- resource = lookup_resource(signed_params: signed_params, sgid: sgid)
36
- html = renderer.render(resource)
37
-
20
+ resolver = Resolver::Resources.new(resource_definitions: resources, connection: connection, params: @params)
21
+ resolver.resolve do |selector, html|
38
22
  cable_ready[stream_name].outer_html(
39
23
  selector: selector,
40
24
  html: html
@@ -43,15 +27,5 @@ module Futurism
43
27
 
44
28
  cable_ready.broadcast
45
29
  end
46
-
47
- private
48
-
49
- def lookup_resource(signed_params:, sgid:)
50
- return GlobalID::Locator.locate_signed(sgid) if sgid.present?
51
-
52
- message_verifier
53
- .verify(signed_params)
54
- .deep_transform_values { |value| value.is_a?(String) && value.start_with?("gid://") ? GlobalID::Locator.locate(value) : value }
55
- end
56
30
  end
57
31
  end
@@ -9,7 +9,11 @@ module Futurism
9
9
  end
10
10
  end
11
11
 
12
- placeholder = capture(&block)
12
+ if block_given?
13
+ placeholder = capture(&block)
14
+ else
15
+ options[:eager] = true
16
+ end
13
17
 
14
18
  if records_or_string.is_a?(ActiveRecord::Base) || records_or_string.is_a?(ActiveRecord::Relation)
15
19
  futurize_active_record(records_or_string, extends: extends, placeholder: placeholder, **options)
@@ -24,33 +28,38 @@ module Futurism
24
28
  def futurize_with_options(extends:, placeholder:, **options)
25
29
  collection = options.delete(:collection)
26
30
  if collection.nil?
27
- Element.new(extends: extends, placeholder: placeholder, options: options).render
31
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options).render
28
32
  else
29
33
  collection_class_name = collection.try(:klass).try(:name) || collection.first.class.to_s
30
- as = options.delete(:as) || collection_class_name.downcase
34
+ as = options.delete(:as) || collection_class_name.underscore
35
+ broadcast_each = options.delete(:broadcast_each) || false
31
36
  collection.each_with_index.map { |record, index|
32
- Element.new(extends: extends, placeholder: placeholder, options: options.deep_merge(locals: {as.to_sym => record, "#{as}_counter".to_sym => index})).render
37
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options.deep_merge(
38
+ broadcast_each: broadcast_each,
39
+ locals: {as.to_sym => record, "#{as}_counter".to_sym => index}
40
+ )).render
33
41
  }.join.html_safe
34
42
  end
35
43
  end
36
44
 
37
45
  def futurize_active_record(records, extends:, placeholder:, **options)
38
46
  Array(records).map { |record|
39
- Element.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
47
+ WrappingFuturismElement.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
40
48
  }.join.html_safe
41
49
  end
42
50
 
43
51
  # wraps functionality for rendering a futurism element
44
- class Element
52
+ class WrappingFuturismElement
45
53
  include ActionView::Helpers
46
54
  include Futurism::MessageVerifier
47
55
 
48
- attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :controller
56
+ attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :broadcast_each, :controller
49
57
 
50
58
  def initialize(extends:, placeholder:, options:)
51
59
  @extends = extends
52
60
  @placeholder = placeholder
53
61
  @eager = options.delete(:eager)
62
+ @broadcast_each = options.delete(:broadcast_each)
54
63
  @controller = options.delete(:controller)
55
64
  @html_options = options.delete(:html_options) || {}
56
65
  @data_attributes = html_options.fetch(:data, {}).except(:sgid, :signed_params)
@@ -63,6 +72,7 @@ module Futurism
63
72
  signed_params: signed_params,
64
73
  sgid: model && model.to_sgid.to_s,
65
74
  eager: eager.presence,
75
+ broadcast_each: broadcast_each.presence,
66
76
  signed_controller: signed_controller
67
77
  })
68
78
  end
@@ -82,7 +92,10 @@ module Futurism
82
92
  require_relative "shims/deep_transform_values" unless options.respond_to? :deep_transform_values
83
93
 
84
94
  options.deep_transform_values do |value|
85
- value.is_a?(ActiveRecord::Base) && !value.new_record? ? value.to_global_id.to_s : value
95
+ next(value) unless value.respond_to?(:to_global_id)
96
+ next(value) if value.is_a?(ActiveRecord::Base) && value.new_record?
97
+
98
+ value.to_global_id.to_s
86
99
  end
87
100
  end
88
101
 
@@ -9,7 +9,11 @@ module Futurism
9
9
  end
10
10
  end
11
11
 
12
- placeholder = capture(&block)
12
+ if block_given?
13
+ placeholder = capture(&block)
14
+ else
15
+ options[:eager] = true
16
+ end
13
17
 
14
18
  if records_or_string.is_a?(ActiveRecord::Base) || records_or_string.is_a?(ActiveRecord::Relation)
15
19
  futurize_active_record(records_or_string, extends: extends, placeholder: placeholder, **options)
@@ -24,24 +28,28 @@ module Futurism
24
28
  def futurize_with_options(extends:, placeholder:, **options)
25
29
  collection = options.delete(:collection)
26
30
  if collection.nil?
27
- Element.new(extends: extends, placeholder: placeholder, options: options).render
31
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options).render
28
32
  else
29
- collection_class_name = collection.klass.name
30
- as = options.delete(:as) || collection_class_name.downcase
31
- collection.map { |record|
32
- Element.new(extends: extends, placeholder: placeholder, options: options.deep_merge(locals: {as.to_sym => record})).render
33
+ collection_class_name = collection.try(:klass).try(:name) || collection.first.class.to_s
34
+ as = options.delete(:as) || collection_class_name.underscore
35
+ broadcast_each = options.delete(:broadcast_each) || false
36
+ collection.each_with_index.map { |record, index|
37
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options.deep_merge(
38
+ broadcast_each: broadcast_each,
39
+ locals: {as.to_sym => record, "#{as}_counter".to_sym => index}
40
+ )).render
33
41
  }.join.html_safe
34
42
  end
35
43
  end
36
44
 
37
45
  def futurize_active_record(records, extends:, placeholder:, **options)
38
46
  Array(records).map { |record|
39
- Element.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
47
+ WrappingFuturismElement.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
40
48
  }.join.html_safe
41
49
  end
42
50
 
43
51
  # wraps functionality for rendering a futurism element
44
- class Element
52
+ class WrappingFuturismElement
45
53
  include ActionView::Helpers
46
54
  include Futurism::MessageVerifier
47
55
 
@@ -82,7 +90,10 @@ module Futurism
82
90
  require_relative "shims/deep_transform_values" unless options.respond_to? :deep_transform_values
83
91
 
84
92
  options.deep_transform_values do |value|
85
- value.is_a?(ActiveRecord::Base) && !value.new_record? ? value.to_global_id.to_s : value
93
+ next(value) unless value.respond_to?(:to_global_id)
94
+ next(value) if value.is_a?(ActiveRecord::Base) && value.new_record?
95
+
96
+ value.to_global_id.to_s
86
97
  end
87
98
  end
88
99
 
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Futurism
2
4
  module Resolver
3
5
  class Controller
4
6
  class Renderer
7
+ HTTP_METHODS = [:get, :post, :put, :patch, :delete]
8
+
5
9
  def self.for(controller:, connection:, url:, params:)
6
10
  new(controller: controller, connection: connection, url: url, params: params).renderer
7
11
  end
@@ -30,7 +34,7 @@ module Futurism
30
34
  path = ActionDispatch::Journey::Router::Utils.normalize_path(uri.path)
31
35
  query_hash = Rack::Utils.parse_nested_query(uri.query)
32
36
 
33
- path_params = Rails.application.routes.recognize_path(path)
37
+ path_params = recognize_url(url) # use full url to be more likely to match a url with subdomain constraints
34
38
 
35
39
  self.renderer =
36
40
  renderer.new(
@@ -51,6 +55,21 @@ module Futurism
51
55
  new_env = connection.env.merge(renderer.instance_variable_get(:@env))
52
56
  renderer.instance_variable_set(:@env, new_env)
53
57
  end
58
+
59
+ def recognize_url(url)
60
+ HTTP_METHODS.each do |http_method|
61
+ path = Rails.application.routes.recognize_path(url, method: http_method)
62
+ return path if path
63
+ rescue ActionController::RoutingError
64
+ # Route not matched, try next
65
+ end
66
+
67
+ warn "We were unable to find a matching rails route for '#{url}'. " \
68
+ "This may be because there are proc-based routing constraints for this particular url, or " \
69
+ "it truly is an unrecognizable url."
70
+
71
+ {}
72
+ end
54
73
  end
55
74
  end
56
75
  end
@@ -3,7 +3,7 @@ module Futurism
3
3
  class Resources
4
4
  include Futurism::MessageVerifier
5
5
 
6
- # resource definitions are an array of [signed_params, sgid, signed_controller, url]
6
+ # resource definitions are an array of [signed_params, sgid, signed_controller, url, broadcast_each]
7
7
  def initialize(resource_definitions:, connection:, params:)
8
8
  @connection = connection
9
9
  @params = params
@@ -16,24 +16,34 @@ module Futurism
16
16
  resolved_models.zip(@resources_with_sgids).each do |model, resource_definition|
17
17
  html = renderer_for(resource_definition: resource_definition).render(model)
18
18
 
19
- yield(resource_definition.selector, html)
19
+ yield(resource_definition.selector, html, resource_definition.broadcast_each)
20
20
  end
21
21
 
22
22
  @resources_without_sgids.each do |resource_definition|
23
23
  resource = lookup_resource(resource_definition)
24
- html = renderer_for(resource_definition: resource_definition).render(resource)
24
+ renderer = renderer_for(resource_definition: resource_definition)
25
+ html =
26
+ begin
27
+ renderer.render(resource)
28
+ rescue => exception
29
+ error_renderer.render(exception)
30
+ end
25
31
 
26
- yield(resource_definition.selector, html)
32
+ yield(resource_definition.selector, html, resource_definition.broadcast_each)
27
33
  end
28
34
  end
29
35
 
30
36
  private
31
37
 
38
+ def error_renderer
39
+ ErrorRenderer.new
40
+ end
41
+
32
42
  class ResourceDefinition
33
43
  attr_reader :signed_params, :sgid, :signed_controller, :url
34
44
 
35
45
  def initialize(resource_definition)
36
- @signed_params, @sgid, @signed_controller, @url = resource_definition
46
+ @signed_params, @sgid, @signed_controller, @url, @broadcast_each = resource_definition
37
47
  end
38
48
 
39
49
  def selector
@@ -45,6 +55,29 @@ module Futurism
45
55
  def controller
46
56
  Resolver::Controller.from(signed_string: @signed_controller)
47
57
  end
58
+
59
+ def broadcast_each
60
+ @broadcast_each == "true"
61
+ end
62
+ end
63
+
64
+ class ErrorRenderer
65
+ include ActionView::Helpers::TagHelper
66
+
67
+ def render(exception)
68
+ return "" unless render?
69
+
70
+ Futurism.logger.error(exception.to_s)
71
+ Futurism.logger.error(exception.backtrace)
72
+
73
+ tag.div { tag.span(exception.to_s) + tag.div(exception.backtrace.join("\n"), style: "display: none;") }
74
+ end
75
+
76
+ def render?
77
+ Rails.env.development? || Rails.env.test?
78
+ end
79
+
80
+ attr_accessor :output_buffer
48
81
  end
49
82
 
50
83
  def renderer_for(resource_definition:)
@@ -9,65 +9,89 @@ module Futurism
9
9
  @params = params
10
10
  @resources_with_sgids, @resources_without_sgids = resource_definitions
11
11
  .partition { |signed_params, sgid, *| sgid.present? }
12
- .map { |definition| ResourceDefinition.new(definition) }
12
+ .map { |partition| partition.map { |definition| ResourceDefinition.new(definition) } }
13
13
  end
14
14
 
15
15
  def resolve
16
16
  resolved_models.zip(@resources_with_sgids).each do |model, resource_definition|
17
- signed_params, sgid, signed_controller, url = resource_definition
17
+ html = renderer_for(resource_definition: resource_definition).render(model)
18
18
 
19
- selector = selector_for(signed_params: signed_params, sgid: sgid)
20
-
21
- controller = Resolver::Controller.from(signed_string: signed_controller)
22
- renderer = Resolver::Controller::Renderer.for(controller: controller,
23
- connection: @connection,
24
- url: url,
25
- params: @params)
26
-
27
- html = renderer.render(model)
28
-
29
- yield(selector, html)
19
+ yield(resource_definition.selector, html)
30
20
  end
31
21
 
32
- @resources_without_sgids.each do |signed_params, sgid, signed_controller, url|
33
- selector = selector_for(signed_params: signed_params, sgid: sgid)
34
-
35
- controller = Resolver::Controller.from(signed_string: signed_controller)
36
- renderer = Resolver::Controller::Renderer.for(controller: controller,
37
- connection: @connection,
38
- url: url,
39
- params: @params)
22
+ @resources_without_sgids.each do |resource_definition|
23
+ resource = lookup_resource(resource_definition)
24
+ renderer = renderer_for(resource_definition: resource_definition)
25
+ html =
26
+ begin
27
+ renderer.render(resource)
28
+ rescue => exception
29
+ error_renderer.render(exception)
30
+ end
40
31
 
41
- resource = lookup_resource(signed_params: signed_params)
42
- html = renderer.render(resource)
43
-
44
- yield(selector, html)
32
+ yield(resource_definition.selector, html)
45
33
  end
46
34
  end
47
35
 
48
36
  private
49
37
 
38
+ def error_renderer
39
+ ErrorRenderer.new
40
+ end
41
+
50
42
  class ResourceDefinition
43
+ attr_reader :signed_params, :sgid, :signed_controller, :url
44
+
51
45
  def initialize(resource_definition)
52
46
  @signed_params, @sgid, @signed_controller, @url = resource_definition
53
47
  end
48
+
49
+ def selector
50
+ selector = "[data-signed-params='#{@signed_params}']"
51
+ selector << "[data-sgid='#{@sgid}']" if @sgid.present?
52
+ selector
53
+ end
54
+
55
+ def controller
56
+ Resolver::Controller.from(signed_string: @signed_controller)
57
+ end
58
+ end
59
+
60
+ class ErrorRenderer
61
+ include ActionView::Helpers::TagHelper
62
+
63
+ def render(exception)
64
+ return "" unless render?
65
+
66
+ Futurism.logger.error(exception.to_s)
67
+ Futurism.logger.error(exception.backtrace)
68
+
69
+ tag.div { tag.span(exception.to_s) + tag.div(exception.backtrace.join("\n"), style: "display: none;") }
70
+ end
71
+
72
+ def render?
73
+ Rails.env.development? || Rails.env.test?
74
+ end
75
+
76
+ attr_accessor :output_buffer
77
+ end
78
+
79
+ def renderer_for(resource_definition:)
80
+ Resolver::Controller::Renderer.for(controller: resource_definition.controller,
81
+ connection: @connection,
82
+ url: resource_definition.url,
83
+ params: @params)
54
84
  end
55
85
 
56
86
  def resolved_models
57
- GlobalID::Locator.locate_many_signed @resources_with_sgids.map(&:second)
87
+ GlobalID::Locator.locate_many_signed @resources_with_sgids.map(&:sgid)
58
88
  end
59
89
 
60
- def lookup_resource(signed_params:)
90
+ def lookup_resource(resource_definition)
61
91
  message_verifier
62
- .verify(signed_params)
92
+ .verify(resource_definition.signed_params)
63
93
  .deep_transform_values { |value| value.is_a?(String) && value.start_with?("gid://") ? GlobalID::Locator.locate(value) : value }
64
94
  end
65
-
66
- def selector_for(signed_params:, sgid:)
67
- selector = "[data-signed-params='#{signed_params}']"
68
- selector << "[data-sgid='#{sgid}']" if sgid.present?
69
- selector
70
- end
71
95
  end
72
96
  end
73
97
  end
@@ -1,3 +1,3 @@
1
1
  module Futurism
2
- VERSION = "0.8.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module Futurism
2
- VERSION = "0.7.2"
2
+ VERSION = "0.8.0"
3
3
  end
data/lib/futurism.rb CHANGED
@@ -21,7 +21,10 @@ module Futurism
21
21
  (@@default_controller || "::ApplicationController").to_s.constantize
22
22
  end
23
23
 
24
- ActiveSupport.on_load(:action_view) {
24
+ ActiveSupport.on_load(:action_view) do
25
25
  include Futurism::Helpers
26
- }
26
+ end
27
+
28
+ mattr_accessor :logger
29
+ self.logger ||= Rails.logger ? Rails.logger.new : Logger.new($stdout)
27
30
  end
@@ -3,7 +3,7 @@ require "fileutils"
3
3
  namespace :futurism do
4
4
  desc "Let's look into a brighter future with futurism and CableReady"
5
5
  task install: :environment do
6
- system "yarn add @minthesize/futurism"
6
+ system "yarn add @stimulus_reflex/futurism"
7
7
 
8
8
  filepath = %w[
9
9
  app/javascript/channels/index.js
@@ -20,7 +20,7 @@ namespace :futurism do
20
20
 
21
21
  unless lines.find { |line| line.start_with?("import * as Futurism") }
22
22
  matches = lines.select { |line| line =~ /\A(require|import)/ }
23
- lines.insert lines.index(matches.last).to_i + 1, "import * as Futurism from '@minthesize/futurism'\n"
23
+ lines.insert lines.index(matches.last).to_i + 1, "import * as Futurism from '@stimulus_reflex/futurism'\n"
24
24
  end
25
25
 
26
26
  unless lines.find { |line| line.start_with?("import consumer") }
@@ -1,4 +1,39 @@
1
- # desc "Explaining what the task does"
2
- # task :futurism do
3
- # # Task goes here
4
- # end
1
+ require "fileutils"
2
+
3
+ namespace :futurism do
4
+ desc "Let's look into a brighter future with futurism and CableReady"
5
+ task install: :environment do
6
+ system "yarn add @minthesize/futurism"
7
+
8
+ filepath = %w[
9
+ app/javascript/channels/index.js
10
+ app/javascript/channels/index.ts
11
+ app/javascript/packs/application.js
12
+ app/javascript/packs/application.ts
13
+ ]
14
+ .select { |path| File.exist?(path) }
15
+ .map { |path| Rails.root.join(path) }
16
+ .first
17
+
18
+ puts "Updating #{filepath}"
19
+ lines = File.open(filepath, "r") { |f| f.readlines }
20
+
21
+ unless lines.find { |line| line.start_with?("import * as Futurism") }
22
+ matches = lines.select { |line| line =~ /\A(require|import)/ }
23
+ lines.insert lines.index(matches.last).to_i + 1, "import * as Futurism from '@minthesize/futurism'\n"
24
+ end
25
+
26
+ unless lines.find { |line| line.start_with?("import consumer") }
27
+ matches = lines.select { |line| line =~ /\A(require|import)/ }
28
+ lines.insert lines.index(matches.last).to_i + 1, "import consumer from '../channels/consumer'\n"
29
+ end
30
+
31
+ initialize_line = lines.find { |line| line.start_with?("Futurism.initializeElements") }
32
+ lines << "Futurism.initializeElements()\n" unless initialize_line
33
+
34
+ subscribe_line = lines.find { |line| line.start_with?("Futurism.createSubscription") }
35
+ lines << "Futurism.createSubscription(consumer)\n" unless subscribe_line
36
+
37
+ File.open(filepath, "w") { |f| f.write lines.join }
38
+ end
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: futurism
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Rubisch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-23 00:00:00.000000000 Z
11
+ date: 2021-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -150,6 +150,7 @@ files:
150
150
  - config/routes.rb
151
151
  - lib/futurism.rb
152
152
  - lib/futurism.rb~
153
+ - lib/futurism/#helpers.rb#
153
154
  - lib/futurism/channel.rb
154
155
  - lib/futurism/channel.rb~
155
156
  - lib/futurism/engine.rb
@@ -167,7 +168,7 @@ files:
167
168
  - lib/futurism/version.rb~
168
169
  - lib/tasks/futurism_tasks.rake
169
170
  - lib/tasks/futurism_tasks.rake~
170
- homepage: https://github.com/julianrubisch/futurism
171
+ homepage: https://github.com/stimulusreflex/futurism
171
172
  licenses:
172
173
  - MIT
173
174
  metadata: {}