futurism 0.7.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: {}