rubyblok 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +151 -0
- data/LICENSE.md +21 -0
- data/README.md +227 -0
- data/Rakefile +4 -0
- data/lib/generators/rubyblok/install_generator.rb +16 -0
- data/lib/generators/rubyblok/migration_generator.rb +67 -0
- data/lib/generators/rubyblok/webhook_controller_generator.rb +29 -0
- data/lib/generators/templates/migration_create.rb.erb +13 -0
- data/lib/generators/templates/migration_update.rb.erb +9 -0
- data/lib/generators/templates/model.rb.erb +3 -0
- data/lib/generators/templates/rubyblok.rb +12 -0
- data/lib/generators/templates/webhook_controller.rb.erb +3 -0
- data/lib/rubyblok/configuration.rb +7 -0
- data/lib/rubyblok/helpers/storyblok_helper.rb +97 -0
- data/lib/rubyblok/mixins/model.rb +21 -0
- data/lib/rubyblok/mixins/webhook.rb +27 -0
- data/lib/rubyblok/railtie.rb +9 -0
- data/lib/rubyblok/services/get_storyblok_story.rb +30 -0
- data/lib/rubyblok/version.rb +5 -0
- data/lib/rubyblok.rb +30 -0
- data/rubyblok.gemspec +35 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '096e822e37774a75693902aff5fe81ccb971ed74d50e849a0b47fa49511642e6'
|
4
|
+
data.tar.gz: 28ac8e653f9ed908a14be4622883acf6ef29c642d96925c1e84766228a5a0f1d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: feae7213dc1c38278accc0cc36ec91b903c15ac1087fb598e51551bf87156cdfe0a4eaaef033e7e3ff4c4ecd50a8296b874390c5b93db741b0fe69bda2efa474
|
7
|
+
data.tar.gz: aa25681967a4c901c4c12c9146db6fd03d3f87ef39de957a47337f66a21cede5edd5b0350b61f9690530f376eb86d2f87d7cc17f2b7ad8c87d1895c95eeedfdf
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
- rubocop-rails
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
NewCops: enable
|
7
|
+
TargetRubyVersion: 3.1.X
|
8
|
+
|
9
|
+
Capybara/VisibilityMatcher:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Documentation:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Layout/HashAlignment:
|
16
|
+
Enabled: false
|
17
|
+
Layout/LineLength:
|
18
|
+
Max: 120
|
19
|
+
Layout/MultilineMethodCallIndentation:
|
20
|
+
EnforcedStyle: aligned
|
21
|
+
Lint/AmbiguousBlockAssociation:
|
22
|
+
Exclude:
|
23
|
+
- spec/**/*
|
24
|
+
Lint/IneffectiveAccessModifier:
|
25
|
+
Enabled: false
|
26
|
+
Lint/MissingSuper:
|
27
|
+
Enabled: false
|
28
|
+
Lint/RedundantSplatExpansion:
|
29
|
+
Enabled: false
|
30
|
+
Lint/UriEscapeUnescape:
|
31
|
+
Enabled: false
|
32
|
+
Metrics/BlockLength:
|
33
|
+
Enabled: false
|
34
|
+
Metrics/ClassLength:
|
35
|
+
Max: 500
|
36
|
+
Metrics/ModuleLength:
|
37
|
+
Max: 250
|
38
|
+
Exclude:
|
39
|
+
- spec/**/*
|
40
|
+
Naming/AccessorMethodName:
|
41
|
+
Enabled: false
|
42
|
+
Naming/BlockForwarding:
|
43
|
+
Enabled: false
|
44
|
+
Naming/InclusiveLanguage:
|
45
|
+
Enabled: false
|
46
|
+
Naming/FileName:
|
47
|
+
Enabled: false
|
48
|
+
Naming/MemoizedInstanceVariableName:
|
49
|
+
Enabled: false
|
50
|
+
Naming/PredicateName:
|
51
|
+
Enabled: false
|
52
|
+
Naming/VariableNumber:
|
53
|
+
Enabled: false
|
54
|
+
Style/ClassAndModuleChildren:
|
55
|
+
Enabled: false
|
56
|
+
Style/Documentation:
|
57
|
+
Enabled: false
|
58
|
+
Style/ExponentialNotation:
|
59
|
+
Enabled: false
|
60
|
+
Style/FetchEnvVar:
|
61
|
+
Enabled: true
|
62
|
+
Exclude:
|
63
|
+
- spec/**/*
|
64
|
+
Style/FormatString:
|
65
|
+
Enabled: false
|
66
|
+
Style/FormatStringToken:
|
67
|
+
Enabled: false
|
68
|
+
Style/FrozenStringLiteralComment:
|
69
|
+
Enabled: false
|
70
|
+
Style/HashSyntax:
|
71
|
+
EnforcedStyle: ruby19
|
72
|
+
Style/InPatternThen:
|
73
|
+
Enabled: false
|
74
|
+
Style/MultilineInPatternThen:
|
75
|
+
Enabled: false
|
76
|
+
Style/MapToHash:
|
77
|
+
Enabled: false
|
78
|
+
Style/NumericLiteralPrefix:
|
79
|
+
Enabled: false
|
80
|
+
Style/NumericPredicate:
|
81
|
+
Enabled: false
|
82
|
+
Style/OpenStructUse:
|
83
|
+
Enabled: false
|
84
|
+
Style/SafeNavigation:
|
85
|
+
Enabled: false
|
86
|
+
Style/StringLiterals:
|
87
|
+
EnforcedStyle: double_quotes
|
88
|
+
Style/TrailingCommaInArrayLiteral:
|
89
|
+
EnforcedStyleForMultiline: comma
|
90
|
+
Style/TrailingCommaInHashLiteral:
|
91
|
+
EnforcedStyleForMultiline: comma
|
92
|
+
|
93
|
+
RSpec/AnyInstance:
|
94
|
+
Enabled: false
|
95
|
+
RSpec/ChangeByZero:
|
96
|
+
Enabled: false
|
97
|
+
RSpec/ContextWording:
|
98
|
+
Enabled: false
|
99
|
+
RSpec/DescribedClass:
|
100
|
+
Enabled: false
|
101
|
+
RSpec/DescribeClass:
|
102
|
+
Enabled: false
|
103
|
+
RSpec/ExampleLength:
|
104
|
+
Enabled: false
|
105
|
+
RSpec/ExampleWording:
|
106
|
+
Enabled: false
|
107
|
+
RSpec/ExpectInHook:
|
108
|
+
Enabled: false
|
109
|
+
RSpec/HooksBeforeExamples:
|
110
|
+
Enabled: false
|
111
|
+
RSpec/ImplicitBlockExpectation:
|
112
|
+
Enabled: false
|
113
|
+
RSpec/InstanceVariable:
|
114
|
+
Exclude:
|
115
|
+
- spec/views/**/*
|
116
|
+
RSpec/LetBeforeExamples:
|
117
|
+
Enabled: false
|
118
|
+
RSpec/LetSetup:
|
119
|
+
Enabled: false
|
120
|
+
RSpec/MessageChain:
|
121
|
+
Enabled: false
|
122
|
+
RSpec/MessageSpies:
|
123
|
+
Enabled: false
|
124
|
+
RSpec/MultipleDescribes:
|
125
|
+
Enabled: false
|
126
|
+
RSpec/MultipleExpectations:
|
127
|
+
Enabled: false
|
128
|
+
RSpec/MultipleMemoizedHelpers:
|
129
|
+
Enabled: false
|
130
|
+
RSpec/NamedSubject:
|
131
|
+
Enabled: false
|
132
|
+
RSpec/NestedGroups:
|
133
|
+
Enabled: false
|
134
|
+
RSpec/RepeatedDescription:
|
135
|
+
Enabled: false
|
136
|
+
RSpec/RepeatedExampleGroupBody:
|
137
|
+
Enabled: false
|
138
|
+
RSpec/RepeatedExampleGroupDescription:
|
139
|
+
Enabled: false
|
140
|
+
RSpec/StubbedMock:
|
141
|
+
Enabled: false
|
142
|
+
RSpec/VerifiedDoubles:
|
143
|
+
Enabled: false
|
144
|
+
RSpec/VerifiedDoubleReference:
|
145
|
+
Enabled: false
|
146
|
+
RSpec/FilePath:
|
147
|
+
Enabled: false
|
148
|
+
RSpec/SpecFilePathFormat:
|
149
|
+
Enabled: false
|
150
|
+
Style/FetchEnvVar:
|
151
|
+
Enabled: false
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 100Starlings
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
# Rubyblok
|
2
|
+
## Introduction
|
3
|
+
Rubyblok is a versatile Ruby gem designed for a Content Management System (CMS) integration.
|
4
|
+
This gem offers an easy way to integrate a visual CMS - Storyblok - into your Rails app, providing you with quality-of-life features.
|
5
|
+
|
6
|
+
This integration allows you to edit your content online, preview it in real-time, and publish with just the click of a button.
|
7
|
+
|
8
|
+
In addition, Rubyblok provides an abstraction layer and stores all your content locally, reducing data usage and enhancing performance. This enables new functionalities that leverage the local data, such as global content search, sitemaps, and listings. Ultimately, this setup increases resilience by eliminating dependency on external data sources.
|
9
|
+
|
10
|
+
## Table of Contents
|
11
|
+
1. [Installation](#installation)
|
12
|
+
2. [Getting Started](#getting-started)
|
13
|
+
3. [Rubyblok tags](#rubyblok-tags)
|
14
|
+
4. [How to Run Tests](#how-to-run-tests)
|
15
|
+
5. [Guide for Contributing](#guide-for-contributing)
|
16
|
+
6. [How to Contact Us](#how-to-contact-us)
|
17
|
+
7. [License](#license)
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
Rubyblok 1.0 works with Rails 6.0 onwards. Run:
|
21
|
+
```
|
22
|
+
bundle add rubyblok
|
23
|
+
```
|
24
|
+
|
25
|
+
### Storyblok account and variables
|
26
|
+
|
27
|
+
[Click here](https://app.storyblok.com/?_gl=1*196uoul*_gcl_au*MTg1NjA5NjA0MS4xNzA5MDY5ODk3#!/signup) to create a free acount at Storyblok, the CMS platform where you will have access to the visual and real-time content editing.
|
28
|
+
|
29
|
+
Create a new Space, in _Spaces > Add Space_.
|
30
|
+
|
31
|
+
Get your Storyblok API token in your account, at _Storyblok Space > Settings > Access tokens_ page. Copy the "Preview" access level key.
|
32
|
+
|
33
|
+
Add the key to your `STORYBLOK_API_TOKEN` in your env file, like this example:
|
34
|
+
|
35
|
+
```
|
36
|
+
STORYBLOK_API_TOKEN=<your API token>
|
37
|
+
```
|
38
|
+
You will also need to add the variables below to your env file:
|
39
|
+
```
|
40
|
+
STORYBLOK_VERSION=draft
|
41
|
+
STORYBLOK_WEBHOOK_SECRET=''
|
42
|
+
```
|
43
|
+
|
44
|
+
## Getting Started
|
45
|
+
|
46
|
+
### Your first Rubyblok page
|
47
|
+
Let's get started with Rubyblok by creating our first page.
|
48
|
+
|
49
|
+
First, you need to run the install generator, which will create the initializer for you:
|
50
|
+
```bash
|
51
|
+
rails g rubyblok:install
|
52
|
+
```
|
53
|
+
|
54
|
+
Now let's generate and run a migration to create the `pages` table and the `Page` model:
|
55
|
+
```bash
|
56
|
+
rails g rubyblok:migration PAGE
|
57
|
+
|
58
|
+
rails db:migrate
|
59
|
+
```
|
60
|
+
|
61
|
+
Then, generate the webhook controller:
|
62
|
+
```bash
|
63
|
+
rails g rubyblok:webhook_controller STORYBLOK_WEBHOOK
|
64
|
+
```
|
65
|
+
It also adds this line to your `routes.rb` file:
|
66
|
+
```
|
67
|
+
resources :storyblok_webhook, only: :create
|
68
|
+
```
|
69
|
+
The Storyblok webhook is responsible for updating and deleting content in our local database in case of changes of content in Storyblok.
|
70
|
+
|
71
|
+
Finally, generate the home controller:
|
72
|
+
```bash
|
73
|
+
rails g controller home_controller
|
74
|
+
```
|
75
|
+
Add the following code to your home controller:
|
76
|
+
```
|
77
|
+
def index
|
78
|
+
response.headers['X-FRAME-OPTIONS'] = 'ALLOWALL'
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Add this code to your app/views/home/index.html.erb file:
|
83
|
+
```
|
84
|
+
<%= rubyblok_story_tag('home') %>
|
85
|
+
```
|
86
|
+
Configure your `routes.rb` file to call the home controller. For example, adding this line:
|
87
|
+
```
|
88
|
+
root to: 'home#index'
|
89
|
+
```
|
90
|
+
|
91
|
+
Create a `shared/storyblok` directory in the `views` directory, this directory is going to store the partials that render Storyblok components.
|
92
|
+
You can change the folder settings at the `rubyblok.rb` file as needed:
|
93
|
+
```
|
94
|
+
config.component_path = "shared/storyblok"
|
95
|
+
```
|
96
|
+
|
97
|
+
Inside the `views/shared/storyblok` folder, create a file named `_page.html.erb` with the following code:
|
98
|
+
```
|
99
|
+
<%= rubyblok_blocks_tag(blok.body) %>
|
100
|
+
```
|
101
|
+
|
102
|
+
And then create another file for the hero section block `_hero_section.html.erb` (more explanation on that later):
|
103
|
+
```
|
104
|
+
<section>
|
105
|
+
<div>
|
106
|
+
<%= rubyblok_content_tag(blok.headline) %>
|
107
|
+
<%= rubyblok_content_tag(blok.subheadline) %>
|
108
|
+
</div>
|
109
|
+
</section>
|
110
|
+
```
|
111
|
+
|
112
|
+
### Creating your page at Storyblok
|
113
|
+
1. Once you're logged in, access your new space in the "My Spaces" section
|
114
|
+
2. Go to the "Content" section
|
115
|
+
3. Click the CTA "Create new" > Story
|
116
|
+
4. Name your story "Home", so it connects to our previous code. The content type is "Page".
|
117
|
+
5. Open your new story to start editing.
|
118
|
+
6. On the right side, you can add new blocks to your page. Create a new block by clicking the "+ Add Block" button.
|
119
|
+
7. This will open the Insert block section, then create the new "hero_section" block by typing its name in the search input.
|
120
|
+
8. Click the "Create new hero_section" CTA
|
121
|
+
9. Add the "headline" and "subheadline" text fields to the new Hero Section and save.
|
122
|
+
10. In your new Hero Section block, add any text you want to it.
|
123
|
+
11. Click the Publish button in the right top corner.
|
124
|
+
|
125
|
+
Now you have your first demo page and block created. Start your rails server and you will be able to see it in your application.
|
126
|
+
|
127
|
+
### Activate the visual editor
|
128
|
+
Here are the steps to configure the visual editor at Storyblok. This allows you to see a preview of your changes in the Storyblok interface as you edit and save.
|
129
|
+
|
130
|
+
At Storyblok, select your Space and go to _Settings > Visual Editor_.
|
131
|
+
|
132
|
+
In the "Location (default environment)" input, add your localhost address:
|
133
|
+
```
|
134
|
+
https://localhost:3333
|
135
|
+
```
|
136
|
+
|
137
|
+
Then we have to create a local proxy. For that, first create a PEM certificate for your `localhost`:
|
138
|
+
|
139
|
+
```bash
|
140
|
+
brew install mkcert
|
141
|
+
mkcert -install
|
142
|
+
mkcert localhost
|
143
|
+
```
|
144
|
+
|
145
|
+
This will create the `localhost-key.pem` and `localhost.pem` files.
|
146
|
+
|
147
|
+
To run the proxy, use the `local-ssl-proxy` tool:
|
148
|
+
|
149
|
+
```bash
|
150
|
+
npm install local-ssl-proxy -g
|
151
|
+
local-ssl-proxy --source 3333 --target 3000 --cert localhost.pem --key localhost-key.pem
|
152
|
+
```
|
153
|
+
|
154
|
+
This will start a proxy server.
|
155
|
+
|
156
|
+
By doing this initial setup, you are able to see your first Storyblok page inside your app and edit its content in the Storyblok admin interface 🎉
|
157
|
+
|
158
|
+
|
159
|
+
## Rubyblok tags
|
160
|
+
|
161
|
+
### rubyblok_story_tag
|
162
|
+
Use this tag to render stories:
|
163
|
+
```
|
164
|
+
# Slug: full_slug of the storyblok story
|
165
|
+
<%= rubyblok_story_tag(slug) %>
|
166
|
+
```
|
167
|
+
The name of the storyblok blok should match the rails partial, ie the `header` storyblok blok should have a corresponding `_header.html.erb` partial in the `config.component_path` directory. The partial is called with a `blok` local variable which contains the storyblok blok properties.
|
168
|
+
|
169
|
+
### rubyblok_content_tag
|
170
|
+
It renders content of Text, TextArea, Markdown or Richtext storyblok fields.
|
171
|
+
```
|
172
|
+
<%= rubyblok_content_tag(content) %>
|
173
|
+
```
|
174
|
+
|
175
|
+
Optionally, you can use the `rubyblok_markdown_tag` or `rubyblok_richtext_tag` tags for rendering specific content.
|
176
|
+
```
|
177
|
+
# Markdown text
|
178
|
+
<%= rubyblok_markdown_tag("this is a **mark** down") %>
|
179
|
+
# Output: "<p>this is a <strong>mark</strong> down</p>"
|
180
|
+
|
181
|
+
# Richtext
|
182
|
+
text = {
|
183
|
+
"type" => "doc",
|
184
|
+
"content" => [
|
185
|
+
{ "type" => "paragraph",
|
186
|
+
"content" => [{ "text" => "this is a richtext", "type" => "text" }]
|
187
|
+
}
|
188
|
+
]
|
189
|
+
}
|
190
|
+
|
191
|
+
<%= rubyblok_richtext_tag text > %>
|
192
|
+
# Output: "<p>this is a richtext</p>"
|
193
|
+
```
|
194
|
+
|
195
|
+
### rubyblok_blocks_tag
|
196
|
+
Use this tag to render more than one component:
|
197
|
+
```
|
198
|
+
<%= rubyblok_blocks_tag(blok.bloks) %>
|
199
|
+
```
|
200
|
+
|
201
|
+
### Updating content manually at the caching layer
|
202
|
+
|
203
|
+
You can do the following in case you need to update the caching layer with some content that already exists in Storyblok:
|
204
|
+
```
|
205
|
+
# Slug: full_slug of the storyblok story
|
206
|
+
storyblok_story_content = Rubyblok::Services::GetStoryblokStory.call(slug: slug)
|
207
|
+
<MODEL_NAME>.find_or_initialize_by(storyblok_story_slug: page)
|
208
|
+
.update(storyblok_story_content:, storyblok_story_id: storyblok_story_content["id"])
|
209
|
+
```
|
210
|
+
|
211
|
+
## How to Run Tests
|
212
|
+
|
213
|
+
You can run unit tests for RubyBlok with the following command:
|
214
|
+
```
|
215
|
+
bundle exec rspec
|
216
|
+
```
|
217
|
+
|
218
|
+
## Guide for Contributing
|
219
|
+
Contributions are made to this repository via Issues and Pull Requests (PRs).
|
220
|
+
Issues should be used to report bugs, request a new feature, or to discuss potential changes before a PR is created.
|
221
|
+
|
222
|
+
## How to Contact Us
|
223
|
+
For any inquiries, reach out to us at: info@rubyblok.com
|
224
|
+
|
225
|
+
## License
|
226
|
+
|
227
|
+
RubyBlok is released under the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/active_record"
|
3
|
+
|
4
|
+
module Rubyblok
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
source_root File.expand_path("../templates", __dir__)
|
8
|
+
|
9
|
+
desc "Generates an initializer file."
|
10
|
+
|
11
|
+
def copy_initializer
|
12
|
+
template("rubyblok.rb", "config/initializers/rubyblok.rb")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/active_record"
|
3
|
+
|
4
|
+
module Rubyblok
|
5
|
+
module Generators
|
6
|
+
class MigrationGenerator < ::Rails::Generators::NamedBase
|
7
|
+
include ::Rails::Generators::Migration
|
8
|
+
source_root File.expand_path("../templates", __dir__)
|
9
|
+
desc "Installs Rubyblok migration and model files."
|
10
|
+
|
11
|
+
def install
|
12
|
+
if table_exist?
|
13
|
+
migration_template("migration_update.rb.erb", "db/migrate/update_rubyblok_#{plural_file_name}.rb",
|
14
|
+
migration_version:)
|
15
|
+
else
|
16
|
+
migration_template("migration_create.rb.erb", "db/migrate/create_rubyblok_#{plural_file_name}.rb",
|
17
|
+
migration_version:)
|
18
|
+
end
|
19
|
+
|
20
|
+
create_or_update_model
|
21
|
+
add_model_name_to_config
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.next_migration_number(dirname)
|
25
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def migration_version
|
31
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
32
|
+
end
|
33
|
+
|
34
|
+
def table_exist?
|
35
|
+
ActiveRecord::Base.connection.table_exists?(plural_file_name.to_sym)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_or_update_model
|
39
|
+
if table_exist?
|
40
|
+
add_mixins_to_existing_model
|
41
|
+
else
|
42
|
+
generate_new_model_from_template
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_mixins_to_existing_model
|
47
|
+
model_path = "app/models/#{file_name}.rb"
|
48
|
+
mixins_code = "\n include Rubyblok::Mixins::Model\n"
|
49
|
+
|
50
|
+
insert_into_file model_path, after: "class #{class_name} < ApplicationRecord" do
|
51
|
+
mixins_code
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_new_model_from_template
|
56
|
+
template("model.rb.erb", "app/models/#{file_name}.rb")
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_model_name_to_config
|
60
|
+
model_path = "config/initializers/rubyblok.rb"
|
61
|
+
insert_into_file model_path, after: 'config.model_name = "' do
|
62
|
+
class_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/active_record"
|
3
|
+
|
4
|
+
module Rubyblok
|
5
|
+
module Generators
|
6
|
+
class WebhookControllerGenerator < ::Rails::Generators::NamedBase
|
7
|
+
source_root File.expand_path("../templates", __dir__)
|
8
|
+
|
9
|
+
desc "Generates a webhook controller and a route file."
|
10
|
+
|
11
|
+
def copy_initializer
|
12
|
+
template("webhook_controller.rb.erb", "app/controllers/#{file_name}_controller.rb")
|
13
|
+
end
|
14
|
+
|
15
|
+
def routes_config
|
16
|
+
destination_path = "config/routes.rb"
|
17
|
+
insert_into_file destination_path, after: "Rails.application.routes.draw do" do
|
18
|
+
routes_content
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def routes_content
|
25
|
+
"\n resources :#{file_name}, only: :create"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateRubyblok<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :<%= table_name %> do |t|
|
4
|
+
t.string :storyblok_story_id
|
5
|
+
t.string :storyblok_story_slug
|
6
|
+
t.string :storyblok_story_content
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :<%= table_name %>, :storyblok_story_slug, unique: true
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class UpdateRubyblok<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
add_column :<%= table_name %>, :storyblok_story_id, :string
|
4
|
+
add_column :<%= table_name %>, :storyblok_story_slug, :string
|
5
|
+
add_column :<%= table_name %>, :storyblok_story_content, :string
|
6
|
+
|
7
|
+
add_index :<%= table_name %>, :storyblok_story_slug, unique: true
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rubyblok.configure do |config|
|
4
|
+
config.cached = true
|
5
|
+
|
6
|
+
config.api_token = ENV["STORYBLOK_API_TOKEN"]
|
7
|
+
config.version = ENV["STORYBLOK_VERSION"]
|
8
|
+
config.webhook_secret = ENV["STORYBLOK_WEBHOOK_SECRET"]
|
9
|
+
|
10
|
+
config.model_name = ""
|
11
|
+
config.component_path = "shared/storyblok"
|
12
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module StoryblokHelper
|
2
|
+
def rubyblok_content_tag(content)
|
3
|
+
return if content.blank?
|
4
|
+
|
5
|
+
if content.is_a?(String)
|
6
|
+
rubyblok_markdown_tag(content)
|
7
|
+
else
|
8
|
+
rubyblok_richtext_tag(content)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def rubyblok_story_tag(slug)
|
13
|
+
content = get_story(slug)
|
14
|
+
rubyblok_component_tag(partial: content.component, blok: content)
|
15
|
+
end
|
16
|
+
|
17
|
+
def rubyblok_component_tag(blok:, partial: blok.component)
|
18
|
+
render_partial(partial:, locals: { blok: }).prepend(rubyblok_editable_tag(blok).to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
# rubocop:disable Rails/OutputSafety
|
22
|
+
def rubyblok_markdown_tag(content)
|
23
|
+
markdown_renderer.render(content).html_safe
|
24
|
+
end
|
25
|
+
|
26
|
+
def rubyblok_richtext_tag(content)
|
27
|
+
rich_text_renderer.render(content).html_safe
|
28
|
+
end
|
29
|
+
# rubocop:enable Rails/OutputSafety
|
30
|
+
|
31
|
+
def rubyblok_blocks_tag(bloks)
|
32
|
+
template =
|
33
|
+
%{<% bloks.each do |blok| %>
|
34
|
+
<%= rubyblok_component_tag(blok:) %>
|
35
|
+
<% end %>}
|
36
|
+
|
37
|
+
render_inline_partial template:, locals: { bloks: }
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_story(slug)
|
41
|
+
if use_cache?
|
42
|
+
get_story_via_cache(slug)["content"].to_dot
|
43
|
+
else
|
44
|
+
get_story_via_api(slug)["content"].to_dot
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get_story_via_api(slug)
|
51
|
+
Rubyblok::Services::GetStoryblokStory.call(slug:)
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_story_via_cache(slug)
|
55
|
+
Rubyblok.configuration.model_name.classify.constantize.fetch_content(slug)
|
56
|
+
end
|
57
|
+
|
58
|
+
def rich_text_renderer # rubocop:disable Metrics/MethodLength
|
59
|
+
ctx = {}
|
60
|
+
path = component_path
|
61
|
+
@rich_text_renderer ||=
|
62
|
+
Storyblok::Richtext::HtmlRenderer.new.tap do |html_renderer|
|
63
|
+
html_renderer.set_component_resolver(lambda { |component, data|
|
64
|
+
ApplicationController.render(
|
65
|
+
partial: "#{path}/#{component}",
|
66
|
+
locals: ctx.merge(blok: data)
|
67
|
+
)
|
68
|
+
})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def markdown_renderer
|
73
|
+
@markdown_renderer ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
|
74
|
+
end
|
75
|
+
|
76
|
+
# rubocop:disable Rails/OutputSafety
|
77
|
+
def rubyblok_editable_tag(component)
|
78
|
+
component["_editable"]&.html_safe
|
79
|
+
end
|
80
|
+
# rubocop:enable Rails/OutputSafety
|
81
|
+
|
82
|
+
def component_path
|
83
|
+
Rubyblok.configuration.component_path
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_partial(partial:, locals:)
|
87
|
+
ApplicationController.render partial: "#{component_path}/#{partial}", locals:
|
88
|
+
end
|
89
|
+
|
90
|
+
def render_inline_partial(template:, locals:)
|
91
|
+
ApplicationController.render inline: template, locals:
|
92
|
+
end
|
93
|
+
|
94
|
+
def use_cache?
|
95
|
+
Rubyblok.configuration.cached
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rubyblok
|
2
|
+
module Mixins
|
3
|
+
module Model
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
validates :storyblok_story_id, presence: true
|
8
|
+
validates :storyblok_story_slug, presence: true
|
9
|
+
validates :storyblok_story_content, presence: true
|
10
|
+
|
11
|
+
serialize :storyblok_story_content, coder: JSON
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def fetch_content(slug)
|
16
|
+
find_by(storyblok_story_slug: slug)&.storyblok_story_content
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rubyblok
|
2
|
+
module Mixins
|
3
|
+
module Webhook
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
skip_before_action :verify_authenticity_token, only: [:create]
|
8
|
+
|
9
|
+
def create
|
10
|
+
payload = JSON.parse(request.raw_post)
|
11
|
+
|
12
|
+
storyblok_story_content = Rubyblok::Services::GetStoryblokStory.call(slug: payload["story_id"])
|
13
|
+
model.find_or_initialize_by(storyblok_story_id: payload["story_id"])
|
14
|
+
.update(storyblok_story_content:, storyblok_story_slug: storyblok_story_content["full_slug"])
|
15
|
+
|
16
|
+
render json: { success: true }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def model
|
22
|
+
Rubyblok.configuration.model_name.classify.constantize
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rubyblok
|
2
|
+
module Services
|
3
|
+
class GetStoryblokStory
|
4
|
+
def self.call(slug:)
|
5
|
+
new(slug:).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(slug:)
|
9
|
+
@slug = slug
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
get_story
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def storyblok_client(version: "draft") # rubocop:disable Lint/UnusedMethodArgument
|
19
|
+
Storyblok::Client.new(
|
20
|
+
token: Rubyblok.configuration.api_token,
|
21
|
+
version: Rubyblok.configuration.version
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_story
|
26
|
+
storyblok_client.story(@slug)["data"]["story"]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/rubyblok.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hash_dot"
|
4
|
+
require "rails"
|
5
|
+
require "action_controller"
|
6
|
+
|
7
|
+
require_relative "rubyblok/helpers/storyblok_helper"
|
8
|
+
require_relative "rubyblok/version"
|
9
|
+
require_relative "rubyblok/configuration"
|
10
|
+
require_relative "rubyblok/services/get_storyblok_story"
|
11
|
+
require_relative "rubyblok/railtie" if defined?(Rails)
|
12
|
+
require_relative "rubyblok/mixins/model"
|
13
|
+
require_relative "generators/rubyblok/migration_generator"
|
14
|
+
require_relative "generators/rubyblok/install_generator"
|
15
|
+
|
16
|
+
require_relative "rubyblok/mixins/webhook"
|
17
|
+
require_relative "generators/rubyblok/webhook_controller_generator"
|
18
|
+
|
19
|
+
module Rubyblok
|
20
|
+
def self.configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.configure
|
25
|
+
yield(configuration)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Error < StandardError; end
|
29
|
+
# Your code goes here...
|
30
|
+
end
|
data/rubyblok.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/rubyblok/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rubyblok"
|
7
|
+
spec.version = Rubyblok::VERSION
|
8
|
+
spec.license = "MIT"
|
9
|
+
spec.authors = ["100 Starlings"]
|
10
|
+
spec.email = ["rubyblok@100starlings.com"]
|
11
|
+
|
12
|
+
spec.summary = "Simple Storyblok CMS integration for Rails"
|
13
|
+
spec.homepage = "http://www.rubyblok.com"
|
14
|
+
spec.required_ruby_version = ">= 3.1.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/100Starlings/rubyblok"
|
18
|
+
|
19
|
+
spec.files = Dir.chdir(__dir__) do
|
20
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
21
|
+
(File.expand_path(f) == __FILE__) ||
|
22
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_dependency "hash_dot", "~> 2.5"
|
30
|
+
spec.add_dependency "railties", "~> 7.1"
|
31
|
+
spec.add_dependency "redcarpet", "~> 3.6.0"
|
32
|
+
spec.add_dependency "storyblok", "~> 3.2.0"
|
33
|
+
|
34
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubyblok
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- 100 Starlings
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-03-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hash_dot
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: railties
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redcarpet
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.6.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.6.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: storyblok
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.2.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.2.0
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- rubyblok@100starlings.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".rspec"
|
77
|
+
- ".rubocop.yml"
|
78
|
+
- LICENSE.md
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/generators/rubyblok/install_generator.rb
|
82
|
+
- lib/generators/rubyblok/migration_generator.rb
|
83
|
+
- lib/generators/rubyblok/webhook_controller_generator.rb
|
84
|
+
- lib/generators/templates/migration_create.rb.erb
|
85
|
+
- lib/generators/templates/migration_update.rb.erb
|
86
|
+
- lib/generators/templates/model.rb.erb
|
87
|
+
- lib/generators/templates/rubyblok.rb
|
88
|
+
- lib/generators/templates/webhook_controller.rb.erb
|
89
|
+
- lib/rubyblok.rb
|
90
|
+
- lib/rubyblok/configuration.rb
|
91
|
+
- lib/rubyblok/helpers/storyblok_helper.rb
|
92
|
+
- lib/rubyblok/mixins/model.rb
|
93
|
+
- lib/rubyblok/mixins/webhook.rb
|
94
|
+
- lib/rubyblok/railtie.rb
|
95
|
+
- lib/rubyblok/services/get_storyblok_story.rb
|
96
|
+
- lib/rubyblok/version.rb
|
97
|
+
- rubyblok.gemspec
|
98
|
+
homepage: http://www.rubyblok.com
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata:
|
102
|
+
homepage_uri: http://www.rubyblok.com
|
103
|
+
source_code_uri: https://github.com/100Starlings/rubyblok
|
104
|
+
rubygems_mfa_required: 'true'
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 3.1.0
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubygems_version: 3.5.7
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Simple Storyblok CMS integration for Rails
|
124
|
+
test_files: []
|