rubyblok 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -137
  3. data/CHANGELOG.md +13 -0
  4. data/README.md +130 -41
  5. data/Rakefile +1 -1
  6. data/lib/generators/rubyblok/hello_world_generator.rb +12 -10
  7. data/lib/generators/rubyblok/image_cache_generator.rb +77 -0
  8. data/lib/generators/rubyblok/install_generator.rb +7 -5
  9. data/lib/generators/rubyblok/migration_generator.rb +10 -8
  10. data/lib/generators/rubyblok/sitemap_config_generator.rb +40 -0
  11. data/lib/generators/rubyblok/webhook_controller_generator.rb +9 -7
  12. data/lib/generators/templates/carrier_wave_config.rb.erb +13 -0
  13. data/lib/generators/templates/image_cache_model.rb.erb +3 -0
  14. data/lib/generators/templates/image_cache_uploader.rb.erb +12 -0
  15. data/lib/generators/templates/migration_create_image_cache.rb.erb +12 -0
  16. data/lib/generators/templates/migration_update_image_cache.rb.erb +8 -0
  17. data/lib/generators/templates/rubyblok.rb +27 -6
  18. data/lib/generators/templates/sitemap.rb.erb +14 -0
  19. data/lib/rubyblok/configuration.rb +4 -3
  20. data/lib/rubyblok/helpers/storyblok_helper.rb +56 -17
  21. data/lib/rubyblok/mixins/model.rb +13 -1
  22. data/lib/rubyblok/mixins/model_cache_class.rb +15 -0
  23. data/lib/rubyblok/mixins/webhook.rb +32 -9
  24. data/lib/rubyblok/railtie.rb +8 -2
  25. data/lib/rubyblok/services/get_storyblok_story.rb +5 -3
  26. data/lib/rubyblok/services/replace_storyblok_url.rb +64 -0
  27. data/lib/rubyblok/version.rb +1 -1
  28. data/lib/rubyblok.rb +18 -15
  29. data/lib/tasks/rubyblok/create_or_update_content.rake +36 -0
  30. data/rubyblok.gemspec +17 -17
  31. metadata +13 -2
@@ -1,20 +1,22 @@
1
- require "rails/generators"
2
- require "rails/generators/active_record"
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
3
5
 
4
6
  module Rubyblok
5
7
  module Generators
6
8
  class WebhookControllerGenerator < ::Rails::Generators::NamedBase
7
- source_root File.expand_path("../templates", __dir__)
9
+ source_root File.expand_path('../templates', __dir__)
8
10
 
9
- desc "Generates a webhook controller and a route file."
11
+ desc 'Generates a webhook controller and a route file.'
10
12
 
11
13
  def copy_initializer
12
- template("webhook_controller.rb.erb", "app/controllers/#{file_name}_controller.rb")
14
+ template('webhook_controller.rb.erb', "app/controllers/#{file_name}_controller.rb")
13
15
  end
14
16
 
15
17
  def routes_config
16
- destination_path = "config/routes.rb"
17
- insert_into_file destination_path, after: "Rails.application.routes.draw do" do
18
+ destination_path = 'config/routes.rb'
19
+ insert_into_file destination_path, after: 'Rails.application.routes.draw do' do
18
20
  routes_content
19
21
  end
20
22
  end
@@ -0,0 +1,13 @@
1
+ CarrierWave.configure do |config|
2
+ config.fog_credentials = {
3
+ provider: 'AWS',
4
+ aws_access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID', nil),
5
+ aws_secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil),
6
+ region: ENV.fetch('AWS_DEFAULT_REGION', nil),
7
+ }
8
+
9
+ config.asset_host = ENV.fetch('AWS_CLOUDFRONT_UPLOADS_HOST', nil)
10
+ config.fog_directory = ENV.fetch('AWS_UPLOADS_DIRECTORY', nil)
11
+ config.fog_public = true
12
+ config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" }
13
+ end
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < ApplicationRecord
2
+ mount_uploader :image, <%= class_name %>Uploader
3
+ end
@@ -0,0 +1,12 @@
1
+ class <%= class_name %>Uploader < CarrierWave::Uploader::Base
2
+ storage :fog
3
+
4
+ def store_dir
5
+ model_name = model.model_name.singular
6
+ "uploads/#{model_name}/#{mounted_as}/#{model.id}"
7
+ end
8
+
9
+ def extension_allowlist
10
+ %w[gif jpeg jpg png svg mp4]
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
+ t.string :original_image_url
5
+ t.string :image
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :<%= table_name %>, :original_image_url, unique: true
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ class Update<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ add_column :<%= table_name %>, :original_image_url, :string
4
+ add_column :<%= table_name %>, :image, :string
5
+
6
+ add_index :<%= table_name %>, :storyblok_story_slug, unique: true
7
+ end
8
+ end
@@ -1,12 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rubyblok.configure do |config|
4
- config.cached = true
4
+ # Storyblok API token
5
+ config.api_token = ENV.fetch('STORYBLOK_API_TOKEN', nil)
5
6
 
6
- config.api_token = ENV["STORYBLOK_API_TOKEN"]
7
- config.version = ENV["STORYBLOK_VERSION"]
8
- config.webhook_secret = ENV["STORYBLOK_WEBHOOK_SECRET"]
7
+ # Storyblok story version (draft or published)
8
+ config.version = ENV.fetch('STORYBLOK_VERSION', 'draft')
9
9
 
10
- config.model_name = ""
11
- config.component_path = "shared/storyblok"
10
+ # Name of the model that stores Storyblok stories
11
+ config.model_name = ''
12
+
13
+ # Directory contains rubyblok partials (relative to app/views)
14
+ config.component_path = 'shared/storyblok'
15
+
16
+ # Feature flag that enables cached mode
17
+ config.cached = ActiveModel::Type::Boolean.new.cast(ENV.fetch('STORYBLOK_CACHED', false))
18
+
19
+ # Feature flag that enables auto update of local cache
20
+ config.auto_update = ActiveModel::Type::Boolean.new.cast(ENV.fetch('STORYBLOK_AUTOUPDATE', false))
21
+
22
+ # Feature flag that enables caching storyblok images
23
+ config.use_cdn_images = ActiveModel::Type::Boolean.new.cast(ENV.fetch('STORYBLOK_CDN_IMAGES', false))
24
+
25
+ # Name of the model that stores Storyblok cached images
26
+ config.image_model_name = ''
27
+
28
+ # Storyblok webhook token
29
+ config.webhook_secret = ENV.fetch('STORYBLOK_WEBHOOK_SECRET', nil)
30
+
31
+ # Feature flag that enables caching view fragments
32
+ config.cache_views = ActiveModel::Type::Boolean.new.cast(ENV.fetch('STORYBLOK_CACHE_VIEWS', false))
12
33
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sitemap_generator'
4
+
5
+ # TODO: Configure your hostname here
6
+ SitemapGenerator::Sitemap.default_host = ''
7
+
8
+ if Rubyblok.configuration.cached
9
+ SitemapGenerator::Sitemap.create do
10
+ model = Rubyblok.configuration.model_name.classify.constantize
11
+ pages = model.all.select { |page| page.storyblok_story_content.dig('content', 'no_index') == false }
12
+ pages.each { |page| add(page.storyblok_story_slug, lastmod: page.updated_at) }
13
+ end
14
+ end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rubyblok
2
4
  class Configuration
3
- attr_accessor :api_token, :version, :component_path, :webhook_secret, :model_name, :cached
4
-
5
- attr_writer :property
5
+ attr_accessor :api_token, :auto_update, :cached, :cache_views, :component_path, :image_model_name,
6
+ :model_name, :use_cdn_images, :version, :webhook_secret
6
7
  end
7
8
  end
@@ -1,4 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubyblok/mixins/model_cache_class'
4
+
1
5
  module StoryblokHelper
6
+ include Rubyblok::Mixins::ModelCacheClass
7
+
2
8
  def rubyblok_content_tag(content)
3
9
  return if content.blank?
4
10
 
@@ -10,14 +16,29 @@ module StoryblokHelper
10
16
  end
11
17
 
12
18
  def rubyblok_story_tag(slug)
13
- content = get_story(slug)
14
- rubyblok_component_tag(partial: content.component, blok: content)
19
+ story = get_story(slug)
20
+ content = story['content'].to_dot
21
+
22
+ template = <<-ERB
23
+ <% cache_key = "rubyblok/#{story['id']}-#{Digest::SHA1.hexdigest(story.to_json)}" %>
24
+ <% cache_if use_cache, cache_key do %>
25
+ <%= rubyblok_component_tag(partial: content.component, blok: content) %>
26
+ <% end %>
27
+ ERB
28
+
29
+ render_inline_partial template:, locals: { content:, use_cache: Rubyblok.configuration.cache_views }
15
30
  end
16
31
 
17
32
  def rubyblok_component_tag(blok:, partial: blok.component)
33
+ return if invisible_on_production?(blok)
34
+
18
35
  render_partial(partial:, locals: { blok: }).prepend(rubyblok_editable_tag(blok).to_s)
19
36
  end
20
37
 
38
+ def invisible_on_production?(blok)
39
+ Rails.env.production? && blok.fetch('invisible_on_production', false)
40
+ end
41
+
21
42
  # rubocop:disable Rails/OutputSafety
22
43
  def rubyblok_markdown_tag(content)
23
44
  markdown_renderer.render(content).html_safe
@@ -37,34 +58,44 @@ module StoryblokHelper
37
58
  render_inline_partial template:, locals: { bloks: }
38
59
  end
39
60
 
61
+ def get_story_content(slug)
62
+ get_story(slug)['content'].to_dot
63
+ end
64
+
65
+ private
66
+
40
67
  def get_story(slug)
41
- if use_cache?
42
- get_story_via_cache(slug)["content"].to_dot
68
+ return get_story_via_api(slug) unless cached?
69
+
70
+ if update_storyblok?
71
+ get_story_via_api(slug, save: true)
43
72
  else
44
- get_story_via_api(slug)["content"].to_dot
73
+ get_story_via_cache(slug)
45
74
  end
46
75
  end
47
76
 
48
- private
49
-
50
- def get_story_via_api(slug)
51
- Rubyblok::Services::GetStoryblokStory.call(slug:)
77
+ def get_story_via_api(slug, save: false)
78
+ story = Rubyblok::Services::GetStoryblokStory.call(slug:)
79
+ replace_storyblok_url(story).tap do |storyblok_story|
80
+ model_cache_class.find_or_create(storyblok_story) if save
81
+ end
52
82
  end
53
83
 
54
84
  def get_story_via_cache(slug)
55
- Rubyblok.configuration.model_name.classify.constantize.fetch_content(slug)
85
+ model_cache_class.fetch_content(slug)
56
86
  end
57
87
 
58
- def rich_text_renderer # rubocop:disable Metrics/MethodLength
88
+ def replace_storyblok_url(story)
89
+ Rubyblok::Services::ReplaceStoryblokUrl.call(story:)
90
+ end
91
+
92
+ def rich_text_renderer
59
93
  ctx = {}
60
94
  path = component_path
61
95
  @rich_text_renderer ||=
62
96
  Storyblok::Richtext::HtmlRenderer.new.tap do |html_renderer|
63
97
  html_renderer.set_component_resolver(lambda { |component, data|
64
- ApplicationController.render(
65
- partial: "#{path}/#{component}",
66
- locals: ctx.merge(blok: data)
67
- )
98
+ ApplicationController.render(partial: "#{path}/#{component}", locals: ctx.merge(blok: data))
68
99
  })
69
100
  end
70
101
  end
@@ -75,7 +106,7 @@ module StoryblokHelper
75
106
 
76
107
  # rubocop:disable Rails/OutputSafety
77
108
  def rubyblok_editable_tag(component)
78
- component["_editable"]&.html_safe
109
+ component['_editable']&.html_safe
79
110
  end
80
111
  # rubocop:enable Rails/OutputSafety
81
112
 
@@ -91,7 +122,15 @@ module StoryblokHelper
91
122
  ApplicationController.render inline: template, locals:
92
123
  end
93
124
 
94
- def use_cache?
125
+ def cached?
95
126
  Rubyblok.configuration.cached
96
127
  end
128
+
129
+ def auto_update?
130
+ Rubyblok.configuration.auto_update
131
+ end
132
+
133
+ def update_storyblok?
134
+ auto_update? || params[:storyblok] == 'update'
135
+ end
97
136
  end
@@ -1,4 +1,6 @@
1
- require "active_support/concern"
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
2
4
 
3
5
  module Rubyblok
4
6
  module Mixins
@@ -17,6 +19,16 @@ module Rubyblok
17
19
  def fetch_content(slug)
18
20
  find_by(storyblok_story_slug: slug)&.storyblok_story_content
19
21
  end
22
+
23
+ def find_or_create(story)
24
+ return if story.blank?
25
+
26
+ find_or_initialize_by(storyblok_story_id: story['id']).tap do |model|
27
+ model.storyblok_story_content = story
28
+ model.storyblok_story_slug = story['full_slug']
29
+ model.save
30
+ end
31
+ end
20
32
  end
21
33
  end
22
34
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module Rubyblok
6
+ module Mixins
7
+ module ModelCacheClass
8
+ extend ActiveSupport::Concern
9
+
10
+ def model_cache_class
11
+ Rubyblok.configuration.model_name.classify.constantize
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,27 +1,50 @@
1
- require "active_support/concern"
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
2
4
 
3
5
  module Rubyblok
4
6
  module Mixins
5
7
  module Webhook
6
8
  extend ActiveSupport::Concern
9
+ include Rubyblok::Mixins::ModelCacheClass
7
10
 
8
- included do
11
+ included do # rubocop:disable Metrics/BlockLength
9
12
  skip_before_action :verify_authenticity_token, only: [:create]
10
13
 
11
14
  def create
12
- payload = JSON.parse(request.raw_post)
15
+ verify_signature(request.raw_post) if webhook_secret.present?
13
16
 
14
- storyblok_story_content = Rubyblok::Services::GetStoryblokStory.call(slug: payload["story_id"])
15
- model.find_or_initialize_by(storyblok_story_id: payload["story_id"])
16
- .update(storyblok_story_content:, storyblok_story_slug: storyblok_story_content["full_slug"])
17
+ payload = JSON.parse(request.raw_post)
18
+ case payload['action']
19
+ when 'published'
20
+ update_story(payload)
21
+ when 'unpublished', 'deleted'
22
+ model_cache_class.find_by(storyblok_story_id: payload['story_id'])&.destroy!
23
+ end
17
24
 
18
- render json: { success: true }
25
+ render(json: { success: true })
19
26
  end
20
27
 
21
28
  private
22
29
 
23
- def model
24
- Rubyblok.configuration.model_name.classify.constantize
30
+ def update_story(payload)
31
+ storyblok_story = Rubyblok::Services::GetStoryblokStory.call(slug: payload['story_id'])
32
+ storyblok_story = Rubyblok::Services::ReplaceStoryblokUrl.call(story: storyblok_story)
33
+ model_cache_class.find_or_create(storyblok_story)
34
+ end
35
+
36
+ def verify_signature(payload_body)
37
+ signature =
38
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), webhook_secret, payload_body)
39
+ raise StandardError, 'Signature check failed!' unless verified?(signature)
40
+ end
41
+
42
+ def verified?(signature)
43
+ Rack::Utils.secure_compare(signature, request.env['HTTP_WEBHOOK_SIGNATURE'])
44
+ end
45
+
46
+ def webhook_secret
47
+ Rubyblok.configuration.webhook_secret
25
48
  end
26
49
  end
27
50
  end
@@ -1,9 +1,15 @@
1
- require_relative "helpers/storyblok_helper"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers/storyblok_helper'
2
4
 
3
5
  module Rubyblok
4
6
  class Railtie < Rails::Railtie
5
- initializer "rubyblok.storyblok_helper" do
7
+ initializer 'rubyblok.storyblok_helper' do
6
8
  ActiveSupport.on_load(:action_view) { include StoryblokHelper }
7
9
  end
10
+
11
+ rake_tasks do
12
+ load 'tasks/rubyblok/create_or_update_content.rake'
13
+ end
8
14
  end
9
15
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rubyblok
2
4
  module Services
3
5
  class GetStoryblokStory
@@ -15,15 +17,15 @@ module Rubyblok
15
17
 
16
18
  private
17
19
 
18
- def storyblok_client(version: "draft") # rubocop:disable Lint/UnusedMethodArgument
20
+ def storyblok_client
19
21
  Storyblok::Client.new(
20
22
  token: Rubyblok.configuration.api_token,
21
23
  version: Rubyblok.configuration.version
22
24
  )
23
25
  end
24
26
 
25
- def get_story
26
- storyblok_client.story(@slug)["data"]["story"]
27
+ def get_story # rubocop:disable Naming/AccessorMethodName
28
+ storyblok_client.story(@slug)['data']['story']
27
29
  end
28
30
  end
29
31
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyblok
4
+ module Services
5
+ class ReplaceStoryblokUrl
6
+ attr_reader :story
7
+
8
+ def self.call(story:)
9
+ new(story:).call
10
+ end
11
+
12
+ def initialize(story:)
13
+ @story = story
14
+ end
15
+
16
+ def call
17
+ return story unless use_cdn_images?
18
+
19
+ image_fields = find_image_fields(story['content'])
20
+ image_fields.each do |image_field|
21
+ replace_url(image_field)
22
+ end
23
+ story
24
+ end
25
+
26
+ private
27
+
28
+ # It finds image fields based on their "fieldtype"=>"asset" attribute.
29
+ # Please note that it caches only images added as an 'Asset' field type in Storyblok.
30
+ def find_image_fields(content)
31
+ return [content] if image_field?(content)
32
+
33
+ content.values.each_with_object([]) do |field_value, image_fields|
34
+ case field_value
35
+ when Array
36
+ field_value.each { |component| image_fields << find_image_fields(component) }
37
+ when Hash
38
+ image_fields << find_image_fields(field_value)
39
+ end
40
+ end.flatten
41
+ end
42
+
43
+ def image_field?(field)
44
+ field['fieldtype'] == 'asset'
45
+ end
46
+
47
+ def replace_url(image_field)
48
+ url = image_field['filename']
49
+ image =
50
+ image_model.create_with(remote_image_url: url).find_or_create_by(original_image_url: url)
51
+
52
+ image_field['filename'] = image.image.url if image.persisted?
53
+ end
54
+
55
+ def image_model
56
+ Rubyblok.configuration.image_model_name.classify.constantize
57
+ end
58
+
59
+ def use_cdn_images?
60
+ Rubyblok.configuration.use_cdn_images
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubyblok
4
- VERSION = "1.1.0"
4
+ VERSION = '1.3.0'
5
5
  end
data/lib/rubyblok.rb CHANGED
@@ -1,20 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "storyblok"
4
- require "redcarpet"
5
- require "hash_dot"
3
+ require 'storyblok'
4
+ require 'redcarpet'
5
+ require 'hash_dot'
6
6
 
7
- require_relative "rubyblok/helpers/storyblok_helper"
8
- require_relative "rubyblok/version"
9
- require_relative "rubyblok/configuration"
10
- require_relative "rubyblok/railtie" if defined?(Rails)
11
- require_relative "rubyblok/services/get_storyblok_story"
12
- require_relative "rubyblok/mixins/model"
13
- require_relative "rubyblok/mixins/webhook"
14
- require_relative "generators/rubyblok/migration_generator"
15
- require_relative "generators/rubyblok/install_generator"
16
- require_relative "generators/rubyblok/webhook_controller_generator"
17
- require_relative "generators/rubyblok/hello_world_generator"
7
+ require_relative 'rubyblok/helpers/storyblok_helper'
8
+ require_relative 'rubyblok/version'
9
+ require_relative 'rubyblok/configuration'
10
+ require_relative 'rubyblok/railtie' if defined?(Rails::Railtie)
11
+ require_relative 'rubyblok/services/get_storyblok_story'
12
+ require_relative 'rubyblok/services/replace_storyblok_url'
13
+ require_relative 'rubyblok/mixins/model'
14
+ require_relative 'rubyblok/mixins/model_cache_class'
15
+ require_relative 'rubyblok/mixins/webhook'
16
+ require_relative 'generators/rubyblok/migration_generator'
17
+ require_relative 'generators/rubyblok/install_generator'
18
+ require_relative 'generators/rubyblok/webhook_controller_generator'
19
+ require_relative 'generators/rubyblok/hello_world_generator'
20
+ require_relative 'generators/rubyblok/sitemap_config_generator'
21
+ require_relative 'generators/rubyblok/image_cache_generator'
18
22
 
19
23
  module Rubyblok
20
24
  def self.configuration
@@ -26,5 +30,4 @@ module Rubyblok
26
30
  end
27
31
 
28
32
  class Error < StandardError; end
29
- # Your code goes here...
30
33
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ PER_PAGE = 50
4
+
5
+ namespace :rubyblok do
6
+ desc 'Create or update cached Storyblok stories'
7
+ task :create_or_update_content, [:version] => [:environment] do |_task, args|
8
+ include Rubyblok::Mixins::ModelCacheClass
9
+
10
+ args.with_defaults(version: Rubyblok.configuration.version)
11
+
12
+ raise ArgumentError, "Invalid version \"#{args[:version]}\"" unless %w[draft published].include?(args[:version])
13
+
14
+ client = storyblok_client(args[:version])
15
+ page = 1
16
+
17
+ loop do
18
+ stories = get_stories(client:, page:)
19
+ break if stories.empty?
20
+
21
+ stories.each { |story| model_cache_class.find_or_create(story) }
22
+
23
+ page += 1
24
+
25
+ sleep 1
26
+ end
27
+ end
28
+
29
+ def storyblok_client(version)
30
+ Storyblok::Client.new(token: Rubyblok.configuration.api_token, version:)
31
+ end
32
+
33
+ def get_stories(client:, page:)
34
+ client.stories(page:, per_page: PER_PAGE).dig('data', 'stories')
35
+ end
36
+ end
data/rubyblok.gemspec CHANGED
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/rubyblok/version"
3
+ require_relative 'lib/rubyblok/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "rubyblok"
6
+ spec.name = 'rubyblok'
7
7
  spec.version = Rubyblok::VERSION
8
- spec.license = "MIT"
9
- spec.authors = ["100 Starlings"]
10
- spec.email = ["rubyblok@100starlings.com"]
8
+ spec.license = 'MIT'
9
+ spec.authors = ['100 Starlings']
10
+ spec.email = ['rubyblok@100starlings.com']
11
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"
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
15
 
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/100Starlings/rubyblok"
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/100Starlings/rubyblok'
18
18
 
19
19
  spec.files = Dir.chdir(__dir__) do
20
20
  `git ls-files -z`.split("\x0").reject do |f|
@@ -22,14 +22,14 @@ Gem::Specification.new do |spec|
22
22
  f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
23
23
  end
24
24
  end
25
- spec.bindir = "exe"
25
+ spec.bindir = 'exe'
26
26
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
- spec.require_paths = ["lib"]
27
+ spec.require_paths = ['lib']
28
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"
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
33
 
34
- spec.metadata["rubygems_mfa_required"] = "true"
34
+ spec.metadata['rubygems_mfa_required'] = 'true'
35
35
  end