futurism 0.7.1 → 1.0.1

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: 8bbd52daa2dfe15d3a6dc9c9e374d6be65299a42a7ee3975cee51838811f7783
4
- data.tar.gz: 29bcba992840173fe09f1e67ecdfa387dfedc8b8574d1ef988a4f98b55142bd2
3
+ metadata.gz: d03b4cdaed591ec0a7f0031ddc637ce51030b65e7394031c0f6621b46dbf50ef
4
+ data.tar.gz: fd47bcaefebc50d2e5c3fdafcbdd9908505a3fa8b6f5a3c5c96838290c814649
5
5
  SHA512:
6
- metadata.gz: 6efdbccf19c4c0b9a16f92f8461cb9fb8153796b81f6cbdd981e7d16779cca93a907033fbff2dac3bd354a22335e22d014a46f14bda18d4ad973605447ef1f20
7
- data.tar.gz: b642b4d646ae5b1047330a833f7d4c7eeb8c684f15337835afc1b56f5d1b51cf0fea9ac9234a693562152b92d09f5febea8eb415b0aa4a8c97a77c96ae2cd42f
6
+ metadata.gz: 3942643f2fcadb5be781030aaf2eebd806da1d672e034b799c5ca43b3ed3d3aed3708d97ec70d4400f843dc134d0499f236a2e8e41a3d201e7cb2ba05c6b7ec0
7
+ data.tar.gz: 71d2546809de6dc4355e57e4f54e7bf783098175f4cc221deb4c874b508b35b33796990ae7d633382b80a28c753b4e9f9d34dfb3661f3535e6f05655b0d10be9
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Futurism
2
2
  [![Twitter follow](https://img.shields.io/twitter/follow/julian_rubisch?style=social)](https://twitter.com/julian_rubisch)
3
3
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
4
- [![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-)
4
+ [![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-)
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 [~1.04kB](https://bundlephobia.com/result?p=@minthesize/futurism@0.1.3)
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,14 @@ 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. `git commit -m "Bump version to x.x.x"`
313
+ 3. Run `bundle exec rake build`
314
+ 4. Run `bundle exec rake release`
315
+ 5. `cd javascript && npm publish --access public`
316
+
293
317
  ## License
294
318
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
295
319
 
@@ -302,27 +326,31 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
302
326
  <!-- markdownlint-disable -->
303
327
  <table>
304
328
  <tr>
305
- <td align="center"><a href="http://www.julianrubisch.at"><img src="https://avatars0.githubusercontent.com/u/4352208?v=4" 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" 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" 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>
308
- <td align="center"><a href="https://www.andrewm.codes"><img src="https://avatars1.githubusercontent.com/u/18423853?v=4" 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" 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" 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" 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>
329
+ <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>
330
+ <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>
331
+ <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>
332
+ <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>
333
+ <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>
334
+ <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>
335
+ <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
336
  </tr>
313
337
  <tr>
314
- <td align="center"><a href="http://fractaledmind.com"><img src="https://avatars3.githubusercontent.com/u/5077225?v=4" 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" 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" 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" 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" 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" 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" 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>
338
+ <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>
339
+ <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>
340
+ <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>
341
+ <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>
342
+ <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>
343
+ <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>
344
+ <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>
345
+ </tr>
346
+ <tr>
347
+ <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>
321
348
  </tr>
322
349
  </table>
323
350
 
324
- <!-- markdownlint-enable -->
351
+ <!-- markdownlint-restore -->
325
352
  <!-- prettier-ignore-end -->
353
+
326
354
  <!-- ALL-CONTRIBUTORS-LIST:END -->
327
355
 
328
356
  This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -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
@@ -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) }
@@ -16,38 +15,19 @@ module Futurism
16
15
  end
17
16
 
18
17
  def receive(data)
19
- resources = data.fetch_values("signed_params", "sgids", "signed_controllers", "urls") { |_key| Array.new(data["signed_params"].length, nil) }.transpose
20
-
21
- resources.each do |signed_params, sgid, signed_controller, url|
22
- selector = "[data-signed-params='#{signed_params}']"
23
- selector << "[data-sgid='#{sgid}']" if sgid.present?
24
-
25
- controller = Resolver::Controller.from(signed_string: signed_controller)
26
- renderer = Resolver::Controller::Renderer.for(controller: controller,
27
- connection: connection,
28
- url: url,
29
- params: @params)
30
-
31
- resource = lookup_resource(signed_params: signed_params, sgid: sgid)
32
- html = renderer.render(resource)
18
+ resources = data.fetch_values("signed_params", "sgids", "signed_controllers", "urls", "broadcast_each") { |_key| Array.new(data["signed_params"].length, nil) }.transpose
33
19
 
20
+ resolver = Resolver::Resources.new(resource_definitions: resources, connection: connection, params: @params)
21
+ resolver.resolve do |selector, html, broadcast_each|
34
22
  cable_ready[stream_name].outer_html(
35
23
  selector: selector,
36
24
  html: html
37
25
  )
26
+
27
+ cable_ready.broadcast if broadcast_each
38
28
  end
39
29
 
40
30
  cable_ready.broadcast
41
31
  end
42
-
43
- private
44
-
45
- def lookup_resource(signed_params:, sgid:)
46
- return GlobalID::Locator.locate_signed(sgid) if sgid.present?
47
-
48
- message_verifier
49
- .verify(signed_params)
50
- .deep_transform_values { |value| value.is_a?(String) && value.start_with?("gid://") ? GlobalID::Locator.locate(value) : value }
51
- end
52
32
  end
53
33
  end
@@ -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,19 +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.each do |signed_params, sgid, signed_controller, url|
22
- selector = "[data-signed-params='#{signed_params}']"
23
- selector << "[data-sgid='#{sgid}']" if sgid.present?
24
-
25
- controller = Resolver::Controller.from(signed_string: signed_controller)
26
- renderer = Resolver::Controller::Renderer.for(controller: controller,
27
- connection: connection,
28
- url: url,
29
- params: @params)
30
-
31
- resource = lookup_resource(signed_params: signed_params, sgid: sgid)
32
- html = renderer.render(resource)
33
-
20
+ resolver = Resolver::Resources.new(resource_definitions: resources, connection: connection, params: @params)
21
+ resolver.resolve do |selector, html|
34
22
  cable_ready[stream_name].outer_html(
35
23
  selector: selector,
36
24
  html: html
@@ -39,15 +27,5 @@ module Futurism
39
27
 
40
28
  cable_ready.broadcast
41
29
  end
42
-
43
- private
44
-
45
- def lookup_resource(signed_params:, sgid:)
46
- return GlobalID::Locator.locate_signed(sgid) if sgid.present?
47
-
48
- message_verifier
49
- .verify(signed_params)
50
- .deep_transform_values { |value| value.is_a?(String) && value.start_with?("gid://") ? GlobalID::Locator.locate(value) : value }
51
- end
52
30
  end
53
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
- 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
 
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
@@ -0,0 +1,101 @@
1
+ module Futurism
2
+ module Resolver
3
+ class Resources
4
+ include Futurism::MessageVerifier
5
+
6
+ # resource definitions are an array of [signed_params, sgid, signed_controller, url, broadcast_each]
7
+ def initialize(resource_definitions:, connection:, params:)
8
+ @connection = connection
9
+ @params = params
10
+ @resources_with_sgids, @resources_without_sgids = resource_definitions
11
+ .partition { |signed_params, sgid, *| sgid.present? }
12
+ .map { |partition| partition.map { |definition| ResourceDefinition.new(definition) } }
13
+ end
14
+
15
+ def resolve
16
+ resolved_models.zip(@resources_with_sgids).each do |model, resource_definition|
17
+ html = renderer_for(resource_definition: resource_definition).render(model)
18
+
19
+ yield(resource_definition.selector, html, resource_definition.broadcast_each)
20
+ end
21
+
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
31
+
32
+ yield(resource_definition.selector, html, resource_definition.broadcast_each)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def error_renderer
39
+ ErrorRenderer.new
40
+ end
41
+
42
+ class ResourceDefinition
43
+ attr_reader :signed_params, :sgid, :signed_controller, :url
44
+
45
+ def initialize(resource_definition)
46
+ @signed_params, @sgid, @signed_controller, @url, @broadcast_each = resource_definition
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
+
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
81
+ end
82
+
83
+ def renderer_for(resource_definition:)
84
+ Resolver::Controller::Renderer.for(controller: resource_definition.controller,
85
+ connection: @connection,
86
+ url: resource_definition.url,
87
+ params: @params)
88
+ end
89
+
90
+ def resolved_models
91
+ GlobalID::Locator.locate_many_signed @resources_with_sgids.map(&:sgid)
92
+ end
93
+
94
+ def lookup_resource(resource_definition)
95
+ message_verifier
96
+ .verify(resource_definition.signed_params)
97
+ .deep_transform_values { |value| value.is_a?(String) && value.start_with?("gid://") ? GlobalID::Locator.locate(value) : value }
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,97 @@
1
+ module Futurism
2
+ module Resolver
3
+ class Resources
4
+ include Futurism::MessageVerifier
5
+
6
+ # resource definitions are an array of [signed_params, sgid, signed_controller, url]
7
+ def initialize(resource_definitions:, connection:, params:)
8
+ @connection = connection
9
+ @params = params
10
+ @resources_with_sgids, @resources_without_sgids = resource_definitions
11
+ .partition { |signed_params, sgid, *| sgid.present? }
12
+ .map { |partition| partition.map { |definition| ResourceDefinition.new(definition) } }
13
+ end
14
+
15
+ def resolve
16
+ resolved_models.zip(@resources_with_sgids).each do |model, resource_definition|
17
+ html = renderer_for(resource_definition: resource_definition).render(model)
18
+
19
+ yield(resource_definition.selector, html)
20
+ end
21
+
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
31
+
32
+ yield(resource_definition.selector, html)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def error_renderer
39
+ ErrorRenderer.new
40
+ end
41
+
42
+ class ResourceDefinition
43
+ attr_reader :signed_params, :sgid, :signed_controller, :url
44
+
45
+ def initialize(resource_definition)
46
+ @signed_params, @sgid, @signed_controller, @url = resource_definition
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)
84
+ end
85
+
86
+ def resolved_models
87
+ GlobalID::Locator.locate_many_signed @resources_with_sgids.map(&:sgid)
88
+ end
89
+
90
+ def lookup_resource(resource_definition)
91
+ message_verifier
92
+ .verify(resource_definition.signed_params)
93
+ .deep_transform_values { |value| value.is_a?(String) && value.start_with?("gid://") ? GlobalID::Locator.locate(value) : value }
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,3 +1,3 @@
1
1
  module Futurism
2
- VERSION = "0.7.1"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -0,0 +1,3 @@
1
+ module Futurism
2
+ VERSION = "1.0.0"
3
+ end
data/lib/futurism.rb CHANGED
@@ -3,6 +3,7 @@ require "action_cable"
3
3
  require "cable_ready"
4
4
  require "futurism/engine"
5
5
  require "futurism/message_verifier"
6
+ require "futurism/resolver/resources"
6
7
  require "futurism/resolver/controller"
7
8
  require "futurism/resolver/controller/renderer"
8
9
  require "futurism/channel"
@@ -20,7 +21,10 @@ module Futurism
20
21
  (@@default_controller || "::ApplicationController").to_s.constantize
21
22
  end
22
23
 
23
- ActiveSupport.on_load(:action_view) {
24
+ ActiveSupport.on_load(:action_view) do
24
25
  include Futurism::Helpers
25
- }
26
+ end
27
+
28
+ mattr_accessor :logger
29
+ self.logger ||= Rails.logger ? Rails.logger.new : Logger.new($stdout)
26
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,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: futurism
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Rubisch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-30 00:00:00.000000000 Z
11
+ date: 2021-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: appraisal
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +150,7 @@ files:
164
150
  - config/routes.rb
165
151
  - lib/futurism.rb
166
152
  - lib/futurism.rb~
153
+ - lib/futurism/#helpers.rb#
167
154
  - lib/futurism/channel.rb
168
155
  - lib/futurism/channel.rb~
169
156
  - lib/futurism/engine.rb
@@ -174,11 +161,14 @@ files:
174
161
  - lib/futurism/resolver/controller.rb~
175
162
  - lib/futurism/resolver/controller/renderer.rb
176
163
  - lib/futurism/resolver/controller/renderer.rb~
164
+ - lib/futurism/resolver/resources.rb
165
+ - lib/futurism/resolver/resources.rb~
177
166
  - lib/futurism/shims/deep_transform_values.rb
178
167
  - lib/futurism/version.rb
168
+ - lib/futurism/version.rb~
179
169
  - lib/tasks/futurism_tasks.rake
180
170
  - lib/tasks/futurism_tasks.rake~
181
- homepage: https://github.com/julianrubisch/futurism
171
+ homepage: https://github.com/stimulusreflex/futurism
182
172
  licenses:
183
173
  - MIT
184
174
  metadata: {}