rubyblok 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ --require spec_helper
2
+ --order random
3
+ --profile 10
4
+ --color
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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -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,3 @@
1
+ class <%= class_name %> < ApplicationRecord
2
+ include Rubyblok::Mixins::Model
3
+ 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,3 @@
1
+ class <%= class_name %>Controller < ApplicationController
2
+ include Rubyblok::Mixins::Webhook
3
+ end
@@ -0,0 +1,7 @@
1
+ module Rubyblok
2
+ class Configuration
3
+ attr_accessor :api_token, :version, :component_path, :webhook_secret, :model_name, :cached
4
+
5
+ attr_writer :property
6
+ end
7
+ 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,9 @@
1
+ require_relative "helpers/storyblok_helper"
2
+
3
+ module Rubyblok
4
+ class Railtie < Rails::Railtie
5
+ initializer "rubyblok.storyblok_helper" do
6
+ ActiveSupport.on_load(:action_view) { include StoryblokHelper }
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyblok
4
+ VERSION = "1.0.0"
5
+ 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: []