futurism 0.8.0 → 1.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75dd023300659792f3dd9e114727b0003204ed6b2e9e990413fcc1ca82971fba
4
- data.tar.gz: b73280cb140625612fc5e7188e301f98e5be0eb19e7359c7786fae8b4a8f4b8c
3
+ metadata.gz: 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: