futurism 0.8.0 → 1.2.0.pre1

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: 75dd023300659792f3dd9e114727b0003204ed6b2e9e990413fcc1ca82971fba
4
- data.tar.gz: b73280cb140625612fc5e7188e301f98e5be0eb19e7359c7786fae8b4a8f4b8c
3
+ metadata.gz: 9159c27d53f4cb3a84298c2481e022d9f3979f67d1e580cba63918151bbd4011
4
+ data.tar.gz: f7f8b7b2e32c7d134a67084dfb431a2938f205129177ae45a78b868616fddf16
5
5
  SHA512:
6
- metadata.gz: c9285da73bb00137f7aea08b2171d370b032681abd79b8d37e652b1aef546159a2d568ecc695ecaeb0a7f3a8f4a74abb0a6a310dca9969021923d4590c0fd03b
7
- data.tar.gz: ae425d91dcae19d847105b5633a0f0b434166d5cb45f9aed14bfccb2a07cc9661594c2a814d072b541b30d7b401c6273919e6c199ffcdb38c146d6fc8f8a6a74
6
+ metadata.gz: 7978fded05dd4f23e6a776b8b8c8a358ab61ca1b05d1c3d2c42b1502a32e1046419b254e26a5f2f727bb35b3d7f1eb58e656737cf657a8003e957cad898dfa29
7
+ data.tar.gz: 2b242632064a29721d40cb7d0b3e1f2d5be72bafc0078e48cda4dffdf2994944a1b36f7ed8a2be1692a8245df43f0907607fb43ecfc8aaa8e5ba78f17ae77d39
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,8 @@ 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)
25
+ - [Contextual Placeholder Arguments](#contextual-placeholder-arguments)
24
26
  - [Events](#events)
25
27
  - [Installation](#installation)
26
28
  - [Manual Installation](#manual-installation)
@@ -33,7 +35,7 @@ Lazy-load Rails partials via CableReady
33
35
 
34
36
  ## Facts
35
37
  - only one dependency: CableReady
36
- - bundle size (without CableReady) is around [~2.46kB](https://bundlephobia.com/result?p=@minthesize/futurism@0.7.2)
38
+ - bundle size (without CableReady) is around [~2.46kB](https://bundlephobia.com/result?p=@stimulus_reflex/futurism@0.7.2)
37
39
 
38
40
  ### Browser Support
39
41
 
@@ -68,6 +70,8 @@ You can pass the placeholder as a block:
68
70
 
69
71
  ![aa601dec1930151f71dbf0d6b02b61c9](https://user-images.githubusercontent.com/4352208/87131629-f768a480-c294-11ea-89a9-ea0a76ee06ef.gif)
70
72
 
73
+ You can also omit the placeholder, which falls back to [eager loading](#eager-loading).
74
+
71
75
  ## API
72
76
 
73
77
  Currently there are two ways to call `futurize`, designed to wrap `render`'s behavior:
@@ -171,6 +175,41 @@ Futurism makes that dead simple:
171
175
  <% end %>
172
176
  ```
173
177
 
178
+ ### Broadcast Partials Individually
179
+ Futurism's default behavior is to `broadcast` partials as they are generated in batches:
180
+
181
+ 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).
182
+
183
+ For collections, however, you can opt into individual broadcasts by specifying `broadcast_each: true` in your helper usage:
184
+
185
+ ```erb
186
+ <%= futurize @posts, broadcast_each: true, extends: :tr do %>
187
+ <div class="placeholder"</td>
188
+ <% end %>
189
+ ```
190
+
191
+ ## Contextual Placeholder Arguments
192
+
193
+ For individual models or arbitrary collections, you can pass `record` and `index` to the placeholder block as arguments:
194
+
195
+ ```erb
196
+ <%= futurize @post, extends: :div do |post| %>
197
+ <div><%= post.title %></div>
198
+ <% end %>
199
+ ```
200
+
201
+ ```erb
202
+ <%= futurize @posts, extends: :tr do |post, index| %>
203
+ <td><%= index + 1 %></td><td><%= post.title %></td>
204
+ <% end %>
205
+ ```
206
+
207
+ ```erb
208
+ <%= futurize partial: "users/user", collection: users, extends: "tr" do |user, index| %>
209
+ <td><%= index + 1 %></td><td><%= user.name %></td>
210
+ <% end >
211
+ ```
212
+
174
213
  ## Events
175
214
 
176
215
  Once your futurize element has been rendered, the `futurize:appeared` custom event will be called.
@@ -193,19 +232,19 @@ To copy over the javascript files to your application, run
193
232
  $ bin/rails futurism:install
194
233
  ```
195
234
 
196
- **! Note that the installer will run `yarn add @minthesize/futurism` for you !**
235
+ **! Note that the installer will run `yarn add @stimulus_reflex/futurism` for you !**
197
236
 
198
237
  ### Manual Installation
199
238
  After `bundle`, install the Javascript library:
200
239
 
201
240
  ```bash
202
- $ bin/yarn add @minthesize/futurism
241
+ $ bin/yarn add @stimulus_reflex/futurism
203
242
  ```
204
243
 
205
244
  In your `app/javascript/channels/index.js`, add the following
206
245
 
207
246
  ```js
208
- import * as Futurism from '@minthesize/futurism'
247
+ import * as Futurism from '@stimulus_reflex/futurism'
209
248
 
210
249
  import consumer from './consumer'
211
250
 
@@ -273,7 +312,7 @@ git clone https://github.com/leastbad/stimulus_reflex_harness.git
273
312
  cd stimulus_reflex_harness
274
313
  git checkout futurism
275
314
  # Edit Gemfile to point point to local gem (e.g. `gem "futurism", path: "../futurism"`)
276
- # yarn link @minthesize/futurism
315
+ # yarn link @stimulus_reflex/futurism
277
316
 
278
317
 
279
318
  # Do your work, Submit PR, Profit!
@@ -290,6 +329,14 @@ cd path/to/project
290
329
  yarn install --force
291
330
  ```
292
331
 
332
+ ### Release
333
+
334
+ 1. Update the version numbers in `javascript/package.json` and `lib/futurism/version.rb`
335
+ 2. `git commit -m "Bump version to x.x.x"`
336
+ 3. Run `bundle exec rake build`
337
+ 4. Run `bundle exec rake release`
338
+ 5. `cd javascript && npm publish --access public`
339
+
293
340
  ## License
294
341
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
295
342
 
@@ -302,25 +349,25 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
302
349
  <!-- markdownlint-disable -->
303
350
  <table>
304
351
  <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>
352
+ <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>
353
+ <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>
354
+ <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
355
  <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>
356
+ <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>
357
+ <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>
358
+ <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
359
  </tr>
313
360
  <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>
361
+ <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>
362
+ <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>
363
+ <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>
364
+ <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>
365
+ <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>
366
+ <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>
367
+ <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
368
  </tr>
322
369
  <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>
370
+ <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
371
  </tr>
325
372
  </table>
326
373
 
@@ -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,48 +9,60 @@ module Futurism
9
9
  end
10
10
  end
11
11
 
12
- placeholder = capture(&block)
12
+ options[:eager] = true unless block_given?
13
13
 
14
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)
15
+ futurize_active_record(records_or_string, extends: extends, **options, &block)
16
16
  elsif records_or_string.is_a?(String)
17
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)
18
+ futurize_with_options(extends: extends, partial: records_or_string, locals: options, html_options: html_options, &block)
19
19
  else
20
- futurize_with_options(extends: extends, placeholder: placeholder, **options)
20
+ futurize_with_options(extends: extends, **options, &block)
21
21
  end
22
22
  end
23
23
 
24
- def futurize_with_options(extends:, placeholder:, **options)
24
+ def futurize_with_options(extends:, **options, &block)
25
25
  collection = options.delete(:collection)
26
26
  if collection.nil?
27
- Element.new(extends: extends, placeholder: placeholder, options: options).render
27
+ placeholder = capture(&block) if block_given?
28
+
29
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options).render
28
30
  else
29
31
  collection_class_name = collection.try(:klass).try(:name) || collection.first.class.to_s
30
- as = options.delete(:as) || collection_class_name.downcase
32
+ as = options.delete(:as) || collection_class_name.underscore
33
+ broadcast_each = options.delete(:broadcast_each) || false
34
+
31
35
  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
36
+ placeholder = capture(record, index, &block) if block_given?
37
+
38
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options.deep_merge(
39
+ broadcast_each: broadcast_each,
40
+ locals: {as.to_sym => record, "#{as}_counter".to_sym => index}
41
+ )).render
33
42
  }.join.html_safe
34
43
  end
35
44
  end
36
45
 
37
- def futurize_active_record(records, extends:, placeholder:, **options)
38
- Array(records).map { |record|
39
- Element.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
46
+ def futurize_active_record(records, extends:, **options, &block)
47
+ Array(records).map.with_index { |record, index|
48
+ placeholder = capture(record, index, &block) if block_given?
49
+
50
+ WrappingFuturismElement.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
40
51
  }.join.html_safe
41
52
  end
42
53
 
43
54
  # wraps functionality for rendering a futurism element
44
- class Element
55
+ class WrappingFuturismElement
45
56
  include ActionView::Helpers
46
57
  include Futurism::MessageVerifier
47
58
 
48
- attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :controller
59
+ attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :broadcast_each, :controller
49
60
 
50
61
  def initialize(extends:, placeholder:, options:)
51
62
  @extends = extends
52
63
  @placeholder = placeholder
53
64
  @eager = options.delete(:eager)
65
+ @broadcast_each = options.delete(:broadcast_each)
54
66
  @controller = options.delete(:controller)
55
67
  @html_options = options.delete(:html_options) || {}
56
68
  @data_attributes = html_options.fetch(:data, {}).except(:sgid, :signed_params)
@@ -63,6 +75,7 @@ module Futurism
63
75
  signed_params: signed_params,
64
76
  sgid: model && model.to_sgid.to_s,
65
77
  eager: eager.presence,
78
+ broadcast_each: broadcast_each.presence,
66
79
  signed_controller: signed_controller
67
80
  })
68
81
  end
@@ -82,7 +95,10 @@ module Futurism
82
95
  require_relative "shims/deep_transform_values" unless options.respond_to? :deep_transform_values
83
96
 
84
97
  options.deep_transform_values do |value|
85
- value.is_a?(ActiveRecord::Base) && !value.new_record? ? value.to_global_id.to_s : value
98
+ next(value) unless value.respond_to?(:to_global_id)
99
+ next(value) if value.is_a?(ActiveRecord::Base) && value.new_record?
100
+
101
+ value.to_global_id.to_s
86
102
  end
87
103
  end
88
104
 
@@ -9,48 +9,59 @@ module Futurism
9
9
  end
10
10
  end
11
11
 
12
- placeholder = capture(&block)
12
+ options[:eager] = true unless block_given?
13
13
 
14
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)
15
+ futurize_active_record(records_or_string, extends: extends, **options, &block)
16
16
  elsif records_or_string.is_a?(String)
17
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)
18
+ futurize_with_options(extends: extends, partial: records_or_string, locals: options, html_options: html_options, &block)
19
19
  else
20
- futurize_with_options(extends: extends, placeholder: placeholder, **options)
20
+ futurize_with_options(extends: extends, **options, &block)
21
21
  end
22
22
  end
23
23
 
24
- def futurize_with_options(extends:, placeholder:, **options)
24
+ def futurize_with_options(extends:, **options, &block)
25
25
  collection = options.delete(:collection)
26
26
  if collection.nil?
27
- Element.new(extends: extends, placeholder: placeholder, options: options).render
27
+ placeholder = capture(&block) if block_given?
28
+
29
+ WrappingFuturismElement.new(extends: extends, placeholder: placeholder, options: options).render
28
30
  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
31
+ placeholder = capture(record, index, &block) if block_given?
32
+
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
- def futurize_active_record(records, extends:, placeholder:, **options)
38
- Array(records).map { |record|
39
- Element.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
45
+ def futurize_active_record(records, extends:, **options, &block)
46
+ Array(records).map.with_index { |record, index|
47
+ placeholder = capture(record, index, &block) if block_given?
48
+
49
+ WrappingFuturismElement.new(extends: extends, options: options.merge(model: record), placeholder: placeholder).render
40
50
  }.join.html_safe
41
51
  end
42
52
 
43
53
  # wraps functionality for rendering a futurism element
44
- class Element
54
+ class WrappingFuturismElement
45
55
  include ActionView::Helpers
46
56
  include Futurism::MessageVerifier
47
57
 
48
- attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :controller
58
+ attr_reader :extends, :placeholder, :html_options, :data_attributes, :model, :options, :eager, :broadcast_each, :controller
49
59
 
50
60
  def initialize(extends:, placeholder:, options:)
51
61
  @extends = extends
52
62
  @placeholder = placeholder
53
63
  @eager = options.delete(:eager)
64
+ @broadcast_each = options.delete(:broadcast_each)
54
65
  @controller = options.delete(:controller)
55
66
  @html_options = options.delete(:html_options) || {}
56
67
  @data_attributes = html_options.fetch(:data, {}).except(:sgid, :signed_params)
@@ -63,6 +74,7 @@ module Futurism
63
74
  signed_params: signed_params,
64
75
  sgid: model && model.to_sgid.to_s,
65
76
  eager: eager.presence,
77
+ broadcast_each: broadcast_each.presence,
66
78
  signed_controller: signed_controller
67
79
  })
68
80
  end
@@ -82,7 +94,10 @@ module Futurism
82
94
  require_relative "shims/deep_transform_values" unless options.respond_to? :deep_transform_values
83
95
 
84
96
  options.deep_transform_values do |value|
85
- value.is_a?(ActiveRecord::Base) && !value.new_record? ? value.to_global_id.to_s : value
97
+ next(value) unless value.respond_to?(:to_global_id)
98
+ next(value) if value.is_a?(ActiveRecord::Base) && value.new_record?
99
+
100
+ value.to_global_id.to_s
86
101
  end
87
102
  end
88
103
 
@@ -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.2.0.pre1"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module Futurism
2
- VERSION = "0.7.2"
2
+ VERSION = "1.1.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.2.0.pre1
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-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -126,16 +126,16 @@ dependencies:
126
126
  name: cable_ready
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - '='
130
130
  - !ruby/object:Gem::Version
131
- version: '4'
131
+ version: 5.0.0.pre3
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - '='
137
137
  - !ruby/object:Gem::Version
138
- version: '4'
138
+ version: 5.0.0.pre3
139
139
  description: Uses custom html elements with attached IntersectionObserver to automatically
140
140
  lazy load partials via websockets
141
141
  email:
@@ -167,7 +167,7 @@ files:
167
167
  - lib/futurism/version.rb~
168
168
  - lib/tasks/futurism_tasks.rake
169
169
  - lib/tasks/futurism_tasks.rake~
170
- homepage: https://github.com/julianrubisch/futurism
170
+ homepage: https://github.com/stimulusreflex/futurism
171
171
  licenses:
172
172
  - MIT
173
173
  metadata: {}
@@ -182,9 +182,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
182
182
  version: '0'
183
183
  required_rubygems_version: !ruby/object:Gem::Requirement
184
184
  requirements:
185
- - - ">="
185
+ - - ">"
186
186
  - !ruby/object:Gem::Version
187
- version: '0'
187
+ version: 1.3.1
188
188
  requirements: []
189
189
  rubygems_version: 3.1.4
190
190
  signing_key: