breezy 0.9.0 → 0.14.0
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 +5 -5
- data/lib/breezy.rb +10 -15
- data/lib/breezy/helpers.rb +541 -11
- data/lib/breezy/redirection.rb +30 -0
- data/lib/breezy/xhr_headers.rb +0 -10
- data/lib/generators/rails/breezy_generator.rb +13 -4
- data/lib/generators/rails/templates/_form.json.props +13 -0
- data/lib/generators/rails/templates/controller.rb.tt +0 -3
- data/lib/generators/rails/templates/edit.json.props +14 -0
- data/lib/generators/rails/templates/index.json.props +13 -0
- data/lib/generators/rails/templates/new.json.props +15 -0
- data/lib/generators/rails/templates/{show.js.props → show.json.props} +0 -2
- data/lib/generators/rails/templates/web/base.jsx +3 -4
- data/lib/generators/rails/templates/web/edit.html.erb +11 -0
- data/lib/generators/rails/templates/web/edit.jsx +22 -27
- data/lib/generators/rails/templates/web/index.html.erb +10 -0
- data/lib/generators/rails/templates/web/index.jsx +13 -9
- data/lib/generators/rails/templates/web/new.html.erb +11 -0
- data/lib/generators/rails/templates/web/new.jsx +19 -24
- data/lib/generators/rails/templates/web/show.html.erb +12 -0
- data/lib/generators/rails/templates/web/show.jsx +3 -3
- data/lib/install/templates/web/action_creators.js +14 -0
- data/lib/install/templates/web/actions.js +3 -0
- data/lib/install/templates/web/application.js +30 -5
- data/lib/install/templates/web/application.json.props +25 -0
- data/lib/install/templates/web/initializer.rb +1 -6
- data/lib/install/templates/web/reducer.js +28 -0
- data/lib/install/web.rb +27 -40
- data/lib/tasks/install.rake +11 -7
- data/test/helpers_test.rb +23 -0
- data/test/render_test.rb +25 -92
- data/test/test_helper.rb +2 -2
- metadata +32 -30
- data/app/views/breezy/response.html.erb +0 -0
- data/lib/breezy/configuration.rb +0 -35
- data/lib/breezy/render.rb +0 -37
- data/lib/generators/rails/templates/edit.js.props +0 -16
- data/lib/generators/rails/templates/index.js.props +0 -14
- data/lib/generators/rails/templates/new.js.props +0 -4
- data/lib/generators/rails/templates/web/form.jsx +0 -31
- data/lib/install/templates/web/babelrc +0 -33
- data/test/breezy_test.rb +0 -125
- data/test/configuration_test.rb +0 -36
@@ -1,18 +1,21 @@
|
|
1
1
|
import React from 'react'
|
2
2
|
import {combineReducers, createStore, applyMiddleware, compose} from 'redux'
|
3
|
+
import reduceReducers from 'reduce-reducers'
|
3
4
|
import thunk from 'redux-thunk'
|
4
5
|
import { Provider } from 'react-redux'
|
5
6
|
import { render } from 'react-dom'
|
6
|
-
import
|
7
|
+
import { createBrowserHistory } from 'history'
|
7
8
|
import Breezy from '@jho406/breezy'
|
8
9
|
import Nav from '@jho406/breezy/dist/NavComponent'
|
10
|
+
import ujsHandlers from '@jho406/breezy/dist/utils/ujs'
|
11
|
+
import applicationReducer from './reducer'
|
9
12
|
|
10
13
|
// Mapping between your props template to Component
|
11
14
|
// e.g {'posts/new': PostNew}
|
12
|
-
const
|
15
|
+
const identifierToComponentMapping = {
|
13
16
|
}
|
14
17
|
|
15
|
-
const history =
|
18
|
+
const history = createBrowserHistory({})
|
16
19
|
const initialPage = window.BREEZY_INITIAL_PAGE_STATE
|
17
20
|
const baseUrl = ''
|
18
21
|
|
@@ -27,20 +30,30 @@ const {reducer, initialState, initialPageKey, connect} = Breezy.start({
|
|
27
30
|
|
28
31
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
29
32
|
|
33
|
+
const {
|
34
|
+
breezy: breezyReducer,
|
35
|
+
pages: pagesReducer,
|
36
|
+
} = reducer
|
37
|
+
|
30
38
|
const store = createStore(
|
31
39
|
combineReducers({
|
32
|
-
|
40
|
+
breezy: breezyReducer,
|
41
|
+
pages: reduceReducers(pagesReducer, applicationReducer),
|
33
42
|
}),
|
34
43
|
initialState,
|
35
44
|
composeEnhancers(applyMiddleware(thunk))
|
36
45
|
)
|
37
46
|
|
47
|
+
const navigatorRef = React.createRef()
|
48
|
+
|
38
49
|
connect(store)
|
39
50
|
|
40
51
|
class App extends React.Component {
|
41
52
|
render() {
|
42
53
|
return <Provider store={store}>
|
43
54
|
<Nav
|
55
|
+
store={store}
|
56
|
+
ref={navigatorRef}
|
44
57
|
mapping={this.props.mapping}
|
45
58
|
history={history}
|
46
59
|
initialPageKey={initialPageKey}
|
@@ -50,5 +63,17 @@ class App extends React.Component {
|
|
50
63
|
}
|
51
64
|
|
52
65
|
document.addEventListener("DOMContentLoaded", function() {
|
53
|
-
|
66
|
+
const appEl = document.getElementById('app')
|
67
|
+
if (appEl) {
|
68
|
+
const {onClick, onSubmit} = ujsHandlers({
|
69
|
+
navigatorRef,
|
70
|
+
store,
|
71
|
+
ujsAttributePrefix: 'data-bz'
|
72
|
+
})
|
73
|
+
|
74
|
+
appEl.addEventListener('click', onClick)
|
75
|
+
appEl.addEventListener('submit', onSubmit)
|
76
|
+
|
77
|
+
render(<App mapping={identifierToComponentMapping}/>, appEl)
|
78
|
+
}
|
54
79
|
})
|
@@ -0,0 +1,25 @@
|
|
1
|
+
path = param_to_search_path(params[:bzq])
|
2
|
+
|
3
|
+
json.data(search: path) do
|
4
|
+
yield json
|
5
|
+
end
|
6
|
+
|
7
|
+
json.component_identifier local_assigns[:virtual_path_of_template]
|
8
|
+
json.defers json.deferred!
|
9
|
+
json.fragments json.fragments!
|
10
|
+
json.assets [
|
11
|
+
asset_pack_path('application.js'),
|
12
|
+
asset_path('application.css')
|
13
|
+
]
|
14
|
+
|
15
|
+
if protect_against_forgery?
|
16
|
+
json.csrf_token form_authenticity_token
|
17
|
+
end
|
18
|
+
|
19
|
+
if path
|
20
|
+
json.action 'graft'
|
21
|
+
json.path search_path_to_camelized_param(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
json.flash flash.to_h
|
25
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
// Example:
|
2
|
+
//
|
3
|
+
// import {
|
4
|
+
// CLEAR_FORM_ERRORS
|
5
|
+
// } from './actions'
|
6
|
+
// import produce from "immer"
|
7
|
+
//
|
8
|
+
// export default function (state = {}, action) {
|
9
|
+
// switch(action.type) {
|
10
|
+
// case CLEAR_FORM_ERRORS: {
|
11
|
+
// const {pageKey} = action.payload
|
12
|
+
//
|
13
|
+
// return produce(state, draft => {
|
14
|
+
// const currentPage = draft[pageKey]
|
15
|
+
// delete currentPage.errors
|
16
|
+
// })
|
17
|
+
// }
|
18
|
+
// default:
|
19
|
+
// return state
|
20
|
+
// }
|
21
|
+
// }
|
22
|
+
|
23
|
+
export default function (state = {}, action) {
|
24
|
+
switch(action.type) {
|
25
|
+
default:
|
26
|
+
return state
|
27
|
+
}
|
28
|
+
}
|
data/lib/install/web.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
require "webpacker/configuration"
|
2
2
|
|
3
|
-
|
3
|
+
babel_config = Rails.root.join("babel.config.js")
|
4
4
|
|
5
5
|
def append_js_tags
|
6
6
|
app_html = 'app/views/layouts/application.html.erb'
|
7
7
|
js_tag = <<-JS_TAG
|
8
|
-
|
9
|
-
<script type="text/javascript">
|
10
|
-
window.BREEZY_INITIAL_PAGE_STATE=<%= breezy_snippet %>;
|
11
|
-
</script>
|
8
|
+
<%= yield :initial_state %>
|
12
9
|
JS_TAG
|
13
10
|
|
14
11
|
inject_into_file app_html, after: '<head>' do
|
@@ -24,60 +21,49 @@ def add_member_methods
|
|
24
21
|
inject_into_file "app/models/application_record.rb", after: "class ApplicationRecord < ActiveRecord::Base\n" do
|
25
22
|
<<-RUBY
|
26
23
|
def self.member_at(index)
|
27
|
-
offset(index).limit(1)
|
24
|
+
offset(index).limit(1).first
|
28
25
|
end
|
29
26
|
|
30
27
|
def self.member_by(attr, value)
|
31
|
-
find_by(Hash[attr,
|
28
|
+
find_by(Hash[attr, value])
|
32
29
|
end
|
33
|
-
|
34
30
|
RUBY
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
react_babelrc["plugins"] ||= []
|
43
|
-
|
44
|
-
if !react_babelrc["presets"].include?("react")
|
45
|
-
react_babelrc["presets"].push("react")
|
46
|
-
say "Copying react preset to your .babelrc file"
|
47
|
-
|
48
|
-
File.open(babelrc, "w") do |f|
|
49
|
-
f.puts JSON.pretty_generate(react_babelrc)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
if !react_babelrc["plugins"].any?{|plugin| Array(plugin).include?("module-resolver")}
|
54
|
-
react_babelrc["plugins"].push(["module-resolver", {
|
34
|
+
say "Copying module-resolver preset to your babel.config.js"
|
35
|
+
resolver_snippet = <<~JAVASCRIPT
|
36
|
+
[
|
37
|
+
require('babel-plugin-module-resolver').default, {
|
55
38
|
"root": ["./app"],
|
56
39
|
"alias": {
|
57
40
|
"views": "./app/views",
|
58
41
|
"components": "./app/components",
|
59
|
-
"
|
42
|
+
"javascript": "./app/javascript"
|
60
43
|
}
|
61
|
-
}
|
44
|
+
}
|
45
|
+
],
|
46
|
+
JAVASCRIPT
|
47
|
+
insert_into_file "babel.config.js", resolver_snippet, after: /plugins: \[\n/
|
62
48
|
|
63
|
-
|
49
|
+
say "Copying application.js file to #{Webpacker.config.source_entry_path}"
|
50
|
+
copy_file "#{__dir__}/templates/web/application.js", "#{Webpacker.config.source_entry_path}/application.js"
|
64
51
|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
52
|
+
say "Copying reducer.js file to #{Webpacker.config.source_entry_path}"
|
53
|
+
copy_file "#{__dir__}/templates/web/reducer.js", "#{Webpacker.config.source_entry_path}/reducer.js"
|
69
54
|
|
70
|
-
|
71
|
-
|
72
|
-
copy_file "#{__dir__}/templates/web/babelrc", ".babelrc"
|
73
|
-
end
|
55
|
+
say "Copying action_creators.js file to #{Webpacker.config.source_entry_path}"
|
56
|
+
copy_file "#{__dir__}/templates/web/action_creators.js", "#{Webpacker.config.source_entry_path}/action_creators.js"
|
74
57
|
|
75
|
-
say "Copying
|
76
|
-
copy_file "#{__dir__}/templates/web/
|
58
|
+
say "Copying actions.js file to #{Webpacker.config.source_entry_path}"
|
59
|
+
copy_file "#{__dir__}/templates/web/actions.js", "#{Webpacker.config.source_entry_path}/actions.js"
|
77
60
|
|
78
61
|
say "Copying Breezy initializer"
|
79
62
|
copy_file "#{__dir__}/templates/web/initializer.rb", "config/initializers/breezy.rb"
|
80
63
|
|
64
|
+
say "Copying application.json.props"
|
65
|
+
copy_file "#{__dir__}/templates/web/application.json.props", "app/views/layouts/application.json.props"
|
66
|
+
|
81
67
|
say "Appending js tags to your application.html.erb"
|
82
68
|
append_js_tags
|
83
69
|
|
@@ -85,9 +71,10 @@ say "Adding required member methods to ApplicationRecord"
|
|
85
71
|
add_member_methods
|
86
72
|
|
87
73
|
say "Installing React, Redux, and Breezy"
|
88
|
-
run "yarn add babel-plugin-module-resolver babel-preset-react
|
74
|
+
run "yarn add babel-plugin-module-resolver babel-preset-react history prop-types react-redux redux-thunk redux reduce-reducers react react-dom immer @jho406/breezy --save"
|
89
75
|
|
90
|
-
say "Updating webpack
|
76
|
+
say "Updating webpack config to include .jsx file extension and resolved_paths"
|
91
77
|
insert_into_file Webpacker.config.config_path, " - .jsx\n", after: /extensions:\n/
|
78
|
+
insert_into_file Webpacker.config.config_path, "'app/views', 'app/components'", after: /resolved_paths: \[/
|
92
79
|
|
93
80
|
say "Webpacker now supports breezy.js 🎉", :green
|
data/lib/tasks/install.rake
CHANGED
@@ -10,6 +10,16 @@ namespace :breezy do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
desc "Verifies if any version of react is in package.json"
|
14
|
+
task :verify_react do
|
15
|
+
package_json = JSON.parse(File.read(Rails.root.join("package.json")))
|
16
|
+
|
17
|
+
if package_json['dependencies']['react'].nil?
|
18
|
+
$stderr.puts "React not installed. Did you run `rails webpacker:install:react`?"
|
19
|
+
$stderr.puts "Exiting!" && exit!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
13
23
|
desc "Verifies webpacker has been installed"
|
14
24
|
task "verify_webpacker" do
|
15
25
|
begin
|
@@ -23,16 +33,10 @@ namespace :breezy do
|
|
23
33
|
|
24
34
|
namespace :install do
|
25
35
|
desc "Install everything needed for breezy web"
|
26
|
-
task 'web' => ["breezy:verify_webpacker", "webpacker:verify_install"] do
|
36
|
+
task 'web' => ["breezy:verify_webpacker", "webpacker:verify_install", "breezy:verify_react"] do
|
27
37
|
template = File.expand_path("../install/web.rb", __dir__)
|
28
38
|
exec "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{template}"
|
29
39
|
end
|
30
|
-
|
31
|
-
desc "Install everything needed for breezy mobile"
|
32
|
-
task 'mobile' => ["breezy:verify_yarn"] do
|
33
|
-
template = File.expand_path("../install/mobile.rb", __dir__)
|
34
|
-
exec "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{template}"
|
35
|
-
end
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class HelpersTest < ActiveSupport::TestCase
|
4
|
+
include Breezy::Helpers
|
5
|
+
|
6
|
+
test 'clean_bzq returns nil if qry is nil' do
|
7
|
+
qry = nil
|
8
|
+
|
9
|
+
assert_nil param_to_search_path(qry)
|
10
|
+
end
|
11
|
+
|
12
|
+
test 'clean_bzq returns a refined qry' do
|
13
|
+
qry = 'foo...bar/?)()-'
|
14
|
+
|
15
|
+
assert_equal param_to_search_path(qry), ['foo', 'bar']
|
16
|
+
end
|
17
|
+
|
18
|
+
test 'camelize_path' do
|
19
|
+
path = ['foo_bar', 'foo_bar=1', 'foo_baz_roo']
|
20
|
+
|
21
|
+
assert_equal search_path_to_camelized_param(path), 'fooBar.fooBar=1.fooBazRoo'
|
22
|
+
end
|
23
|
+
end
|
data/test/render_test.rb
CHANGED
@@ -4,14 +4,14 @@ class RenderController < TestController
|
|
4
4
|
require 'action_view/testing/resolvers'
|
5
5
|
|
6
6
|
append_view_path(ActionView::FixtureResolver.new(
|
7
|
-
'render/
|
8
|
-
'render/
|
9
|
-
'
|
10
|
-
'
|
7
|
+
'render/simple_render_with_breezy.json.props' => 'json.author "john smith"',
|
8
|
+
'render/simple_render_with_breezy_with_bad_layout.json.props' => 'json.author "john smith"',
|
9
|
+
'layouts/application.json.props' => 'json.data {yield json}',
|
10
|
+
'layouts/does_not_exist.html.erb' => '',
|
11
11
|
'layouts/application.html.erb' => <<~HTML
|
12
12
|
<html>
|
13
13
|
<head>
|
14
|
-
<script><%=
|
14
|
+
<script><%= @initial_state.strip.html_safe %></script>
|
15
15
|
</head>
|
16
16
|
<body><%=yield%></body>
|
17
17
|
</html>
|
@@ -20,21 +20,18 @@ class RenderController < TestController
|
|
20
20
|
|
21
21
|
layout 'application'
|
22
22
|
|
23
|
-
before_action :use_breezy, only: [:simple_render_with_breezy, :implied_render_with_breezy]
|
24
|
-
|
25
23
|
def render_action
|
26
24
|
render :action
|
27
25
|
end
|
28
26
|
|
29
27
|
def simple_render_with_breezy
|
30
|
-
|
28
|
+
@initial_state = render_to_string(formats: [:json], layout: true)
|
29
|
+
render inline: '', layout: true
|
31
30
|
end
|
32
31
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
def render_action_with_breezy_false
|
37
|
-
render :action
|
32
|
+
def simple_render_with_breezy_with_bad_layout
|
33
|
+
@initial_state = render_to_string(formats: [:json], layout: 'does_not_exist')
|
34
|
+
render inline: '', layout: true
|
38
35
|
end
|
39
36
|
|
40
37
|
def form_authenticity_token
|
@@ -47,102 +44,38 @@ class RenderTest < ActionController::TestCase
|
|
47
44
|
|
48
45
|
|
49
46
|
setup do
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
test "render action via get" do
|
60
|
-
get :render_action
|
61
|
-
assert_normal_render 'john smith'
|
47
|
+
if Rails.version >= '6'
|
48
|
+
# In rails 6, the fixture orders the templates based on their appearance in the handler
|
49
|
+
# This doesn't happen IRL, so I'm going to explicitly set the handler here.
|
50
|
+
#
|
51
|
+
# Note that the original is the following
|
52
|
+
# @controller.lookup_context.handlers = [:raw, :breezy, :erb, :js, :html, :builder, :ruby]
|
53
|
+
@controller.lookup_context.handlers = [:props, :erb]
|
54
|
+
end
|
62
55
|
end
|
63
56
|
|
64
57
|
test "simple render with breezy" do
|
65
58
|
get :simple_render_with_breezy
|
66
|
-
assert_breezy_html({author: "john smith"}, screen: 'render/action')
|
67
|
-
end
|
68
|
-
|
69
|
-
test "implied render with breezy" do
|
70
|
-
get :implied_render_with_breezy
|
71
|
-
assert_breezy_html({author: "john smith"}, screen: 'render/implied_render_with_breezy')
|
72
|
-
end
|
73
|
-
|
74
|
-
test "simple render with breezy via get js" do
|
75
|
-
@request.accept = 'application/javascript'
|
76
|
-
get :simple_render_with_breezy
|
77
|
-
assert_breezy_js({author: "john smith"})
|
78
|
-
end
|
79
|
-
|
80
|
-
test "render action via xhr and get js" do
|
81
|
-
@request.accept = 'application/javascript'
|
82
|
-
get :simple_render_with_breezy, xhr: true
|
83
|
-
assert_breezy_js({author: "john smith"})
|
84
|
-
end
|
85
|
-
|
86
|
-
test "render with breezy false" do
|
87
|
-
get :render_action_with_breezy_false
|
88
|
-
assert_normal_render("john smith")
|
89
|
-
end
|
90
|
-
|
91
|
-
test "render with breezy false via xhr get" do
|
92
|
-
@request.accept = 'text/html'
|
93
|
-
get :render_action_with_breezy_false, xhr: true
|
94
|
-
assert_normal_render("john smith")
|
95
|
-
end
|
96
|
-
|
97
|
-
test "render action via xhr and put" do
|
98
|
-
@request.accept = 'text/html'
|
99
|
-
put :render_action, xhr: true
|
100
|
-
assert_normal_render 'john smith'
|
101
|
-
end
|
102
59
|
|
103
|
-
private
|
104
|
-
|
105
|
-
def assert_breezy_html(content, opts={})
|
106
60
|
assert_response 200
|
107
|
-
|
108
61
|
rendered = <<~HTML
|
109
62
|
<html>
|
110
63
|
<head>
|
111
|
-
<script>
|
64
|
+
<script>{"data":{"author":"john smith"}}</script>
|
112
65
|
</head>
|
113
66
|
<body></body>
|
114
67
|
</html>
|
115
68
|
HTML
|
116
69
|
|
117
70
|
assert_equal rendered, @response.body
|
118
|
-
assert_equal 'text/html', @response.
|
119
|
-
end
|
120
|
-
|
121
|
-
def assert_breezy_js(content)
|
122
|
-
assert_response 200
|
123
|
-
assert_equal '(function(){var joints={};var cache={};var defers=[];return ({"data":' + content.to_json + ',"screen":"render/action","csrf_token":"secret","assets":["/app.js"],"joints":joints,"defers":defers});})()', @response.body
|
124
|
-
assert_equal 'text/javascript', @response.content_type
|
125
|
-
end
|
126
|
-
|
127
|
-
def assert_breezy_replace_js(content)
|
128
|
-
assert_response 200
|
129
|
-
assert_equal 'Breezy.replace((function(){return ({"data":' + content.to_json + ',"csrf_token":"secret","assets":["/app.js"]});})());', @response.body
|
130
|
-
assert_equal 'text/javascript', @response.content_type
|
71
|
+
assert_equal 'text/html', @response.media_type
|
131
72
|
end
|
132
73
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
<html>
|
138
|
-
<head>
|
139
|
-
<script></script>
|
140
|
-
</head>
|
141
|
-
<body>#{content}</body>
|
142
|
-
</html>
|
143
|
-
HTML
|
74
|
+
test "simple render when the layout doesn't exist" do
|
75
|
+
err = assert_raise ActionView::MissingTemplate do |e|
|
76
|
+
get :simple_render_with_breezy_with_bad_layout
|
77
|
+
end
|
144
78
|
|
145
|
-
assert_equal
|
146
|
-
assert_equal 'text/html', @response.content_type
|
79
|
+
assert_equal(true, err.message.starts_with?('Missing template layouts/does_not_exist with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:props, :erb]}.'))
|
147
80
|
end
|
148
81
|
end
|