workarea-product_grid_content 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.eslintrc +25 -0
  4. data/.eslintrc.json +35 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  6. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/.github/workflows/ci.yml +61 -0
  9. data/.gitignore +20 -0
  10. data/.rubocop.yml +2 -0
  11. data/.scss-lint.yml +192 -0
  12. data/.stylelintrc.json +8 -0
  13. data/CHANGELOG.md +104 -0
  14. data/CODE_OF_CONDUCT.md +3 -0
  15. data/CONTRIBUTING.md +3 -0
  16. data/Gemfile +5 -0
  17. data/LICENSE.md +3 -0
  18. data/README.md +72 -0
  19. data/Rakefile +59 -0
  20. data/app/assets/images/workarea/admin/content_block_types/insights_grid_cell.svg +1 -0
  21. data/app/assets/images/workarea/admin/content_block_types/product_grid_cell.svg +1 -0
  22. data/app/assets/javascripts/workarea/storefront/product_grid_content/modules/match_product_summary_height.js +53 -0
  23. data/app/assets/stylesheets/workarea/product_grid_content/components/_content_block_product_grid_cell.scss +45 -0
  24. data/app/assets/stylesheets/workarea/product_grid_content/components/_insights_grid_cell_content_block.scss +10 -0
  25. data/app/assets/stylesheets/workarea/product_grid_content/components/_pagination_product_grid_content.scss +9 -0
  26. data/app/assets/stylesheets/workarea/product_grid_content/components/_product_grid_cell_content_block.scss +30 -0
  27. data/app/controllers/workarea/storefront/content_blocks_controller.decorator +46 -0
  28. data/app/view_models/workarea/storefront/category_view_model.decorator +11 -0
  29. data/app/view_models/workarea/storefront/content_blocks/grid_content.rb +40 -0
  30. data/app/view_models/workarea/storefront/content_blocks/insights_grid_cell_view_model.rb +25 -0
  31. data/app/view_models/workarea/storefront/content_blocks/product_grid_cell_view_model.rb +23 -0
  32. data/app/view_models/workarea/storefront/product_grid_content.rb +17 -0
  33. data/app/view_models/workarea/storefront/search_view_model.decorator +12 -0
  34. data/app/views/layouts/workarea/storefront/in_grid_content_preview.html.haml +55 -0
  35. data/app/views/workarea/storefront/content_blocks/_insights_grid_cell.html.haml +8 -0
  36. data/app/views/workarea/storefront/content_blocks/_product_grid_cell.html.haml +10 -0
  37. data/app/views/workarea/storefront/shared/_product_grid_content_cell.html.haml +3 -0
  38. data/app/views/workarea/storefront/style_guides/components/_product_grid_cell_content_block.html.haml +27 -0
  39. data/bin/rails +25 -0
  40. data/config/initializers/appends.rb +23 -0
  41. data/config/initializers/content_block_config.rb +17 -0
  42. data/config/initializers/content_block_types.rb +48 -0
  43. data/lib/workarea/product_grid_content/engine.rb +8 -0
  44. data/lib/workarea/product_grid_content/version.rb +5 -0
  45. data/lib/workarea/product_grid_content.rb +11 -0
  46. data/package.json +9 -0
  47. data/public/sample_grid_content.png +0 -0
  48. data/test/dummy/Rakefile +6 -0
  49. data/test/dummy/bin/bundle +3 -0
  50. data/test/dummy/bin/rails +4 -0
  51. data/test/dummy/bin/rake +4 -0
  52. data/test/dummy/bin/setup +28 -0
  53. data/test/dummy/bin/update +28 -0
  54. data/test/dummy/bin/yarn +11 -0
  55. data/test/dummy/config/application.rb +31 -0
  56. data/test/dummy/config/boot.rb +5 -0
  57. data/test/dummy/config/environment.rb +5 -0
  58. data/test/dummy/config/environments/development.rb +52 -0
  59. data/test/dummy/config/environments/production.rb +83 -0
  60. data/test/dummy/config/environments/test.rb +45 -0
  61. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  62. data/test/dummy/config/initializers/assets.rb +14 -0
  63. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  65. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  66. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  67. data/test/dummy/config/initializers/inflections.rb +16 -0
  68. data/test/dummy/config/initializers/mime_types.rb +4 -0
  69. data/test/dummy/config/initializers/workarea.rb +5 -0
  70. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  71. data/test/dummy/config/locales/en.yml +33 -0
  72. data/test/dummy/config/puma.rb +34 -0
  73. data/test/dummy/config/routes.rb +5 -0
  74. data/test/dummy/config/secrets.yml +32 -0
  75. data/test/dummy/config/spring.rb +6 -0
  76. data/test/dummy/config.ru +5 -0
  77. data/test/dummy/db/seeds.rb +2 -0
  78. data/test/dummy/lib/assets/.keep +0 -0
  79. data/test/dummy/log/.keep +0 -0
  80. data/test/dummy/package.json +5 -0
  81. data/test/system/workarea/admin/in_grid_content_system_test.rb +67 -0
  82. data/test/teaspoon_env.rb +6 -0
  83. data/test/test_helper.rb +10 -0
  84. data/test/view_models/workarea/storefront/category_view_model_test.decorator +31 -0
  85. data/test/view_models/workarea/storefront/content_blocks/insights_grid_cell_view_model_test.rb +63 -0
  86. data/test/view_models/workarea/storefront/content_blocks/product_grid_cell_view_model_test.rb +77 -0
  87. data/workarea-product_grid_content.gemspec +16 -0
  88. data/yarn.lock +3290 -0
  89. metadata +150 -0
@@ -0,0 +1,11 @@
1
+ module Workarea
2
+ decorate Storefront::CategoryViewModel, with: :product_grid_content do
3
+ decorated do
4
+ include Storefront::ProductGridContent
5
+ end
6
+
7
+ def product_grid_content_blocks
8
+ @product_grid_content_blocks ||= content_blocks_for(:in_grid)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ module Workarea
2
+ module Storefront
3
+ module ContentBlocks
4
+ module GridContent
5
+ def position
6
+ # Product grid index starts at 0.
7
+ # Offset position to make admin UI more intuitive
8
+ data[:position] - 1
9
+ end
10
+
11
+ def preview_position
12
+ return position unless position > Workarea.config.grid_cell_content_preview_cells
13
+ position % Workarea.config.grid_cell_content_preview_cells
14
+ end
15
+
16
+ def cell_width_class
17
+ Workarea.config.product_grid_cell_classes[data[:width].to_sym]
18
+ end
19
+
20
+ def cell_styles
21
+ styles = []
22
+ styles << "float: #{float_side};" if float?
23
+ styles.join(" ")
24
+ end
25
+
26
+ def float?
27
+ data[:height].to_i > 1 || data[:float] != "none"
28
+ end
29
+
30
+ def float_side
31
+ if data[:height].to_i > 1 && data[:float] == "none"
32
+ "left"
33
+ else
34
+ data[:float]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module Workarea
2
+ module Storefront
3
+ module ContentBlocks
4
+ class InsightsGridCellViewModel < ProductInsightsViewModel
5
+ include GridContent
6
+
7
+ def products
8
+ super.first(number_of_products)
9
+ end
10
+
11
+ def sub_grid_cell_class
12
+ if data[:width] == '2'
13
+ 'grid__cell--50-at-medium'
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def number_of_products
20
+ (data[:width].presence || 1).to_i * (data[:height].presence || 1).to_i
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Workarea
2
+ module Storefront
3
+ module ContentBlocks
4
+ class ProductGridCellViewModel < ContentBlockViewModel
5
+ include GridContent
6
+
7
+ def image
8
+ @image ||= find_asset(data[:asset])
9
+ end
10
+
11
+ def block_styles
12
+ styles = []
13
+ styles << background
14
+ styles.join(" ")
15
+ end
16
+
17
+ def background
18
+ "background: #{model.data[:background_color]} url(#{image.url}) no-repeat;"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ module Storefront
3
+ module ProductGridContent
4
+ def product_position_offset
5
+ page_number = options[:page].present? ? options[:page].to_i - 1 : 0
6
+
7
+ page_number * Workarea.config.per_page
8
+ end
9
+
10
+ def product_grid_content_for(position)
11
+ product_grid_content_blocks.select do |block|
12
+ block.position == (position + product_position_offset)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Workarea
2
+ decorate Storefront::SearchViewModel, with: :product_grid_content do
3
+ decorated do
4
+ include Storefront::ProductGridContent
5
+ end
6
+
7
+ def product_grid_content_blocks
8
+ return [] unless customization.persisted?
9
+ customization.content_blocks_for(:in_grid)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ - add_css(layout_content.css)
2
+ - add_javascript(layout_content.javascript)
3
+
4
+ - if @content.present?
5
+ - add_css(@content.css)
6
+ - add_javascript(@content.javascript)
7
+
8
+ !!!
9
+ /
10
+ Platform: Workarea Ecommerce
11
+ Web: http://workarea.com
12
+ Email: sales@workarea.com
13
+ Phone: 215-925-1800
14
+
15
+ %html{ lang: 'en' }
16
+ %head
17
+ %meta{ charset: 'utf-8' }
18
+ %title= page_title
19
+ = csrf_meta_tags
20
+ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
21
+ %meta{ property: 'locale', content: I18n.locale }
22
+ %meta{ property: 'environment', content: Rails.env }
23
+ %meta{ property: 'analytics', content: 'disable' }
24
+ = alternate_locales_tags
25
+ = favicon_tags
26
+
27
+ :css
28
+ @-ms-viewport { width: device-width; }
29
+ @viewport { width: device-width; }
30
+ = yield :head
31
+ = stylesheet_link_tag Workarea.config.asset_manifests.storefront_stylesheet, media: 'all'
32
+ = yield :css
33
+
34
+ = append_partials('storefront.document_head')
35
+ = javascript_include_tag Workarea.config.asset_manifests.storefront_javascript_head
36
+
37
+ %body{ data: { disable_admin_toolbar: '' }, class: ('content-preview-visibility' unless params[:disable_visibility_classes].presence) }
38
+
39
+ - if @content.present?
40
+ .clearfix{ data: { content_block: '' } }
41
+ .pagination
42
+ .grid
43
+ - (0...Workarea.config.grid_cell_content_preview_cells).each do |cell_pos|
44
+ - if cell_pos == @block.preview_position
45
+ = yield
46
+ - else
47
+ .grid__cell{ class: Workarea.config.product_grid_cell_classes[:'1'] }
48
+ .product-summary
49
+ = render 'workarea/storefront/products/summary', product: @product
50
+ - else
51
+ = yield
52
+
53
+ = javascript_include_tag Workarea.config.asset_manifests.storefront_javascript
54
+ = yield :javascript
55
+ = append_partials('storefront.javascript')
@@ -0,0 +1,8 @@
1
+ .grid__cell{ class: view_model.cell_width_class, style: view_model.cell_styles, data: { pagination_item: '' } }
2
+ .insights-grid-cell-content-block{ data: { match_product_summary_height: { rows: height }.to_json } }
3
+ - if view_model.products.present?
4
+ .grid
5
+ - view_model.products.each do |product|
6
+ .grid__cell{ class: view_model.sub_grid_cell_class }
7
+ .product-summary
8
+ = render 'workarea/storefront/products/summary', product: product
@@ -0,0 +1,10 @@
1
+ .grid__cell{ class: view_model.cell_width_class, style: view_model.cell_styles, data: { pagination_item: '' } }
2
+ .product-grid-cell-content-block{ style: view_model.block_styles, data: { match_product_summary_height: { rows: height }.to_json } }
3
+ - if link.present?
4
+ = link_to '', link, class: 'product-grid-cell-content-block__background-link'
5
+ - if heading.present? || link_text.present?
6
+ .product-grid-cell-content-block__content
7
+ - if heading.present?
8
+ %h2.product-grid-cell-content-block__heading= heading
9
+ - if link_text.present? && link.present?
10
+ %p.product-grid-cell-content-block__action= link_to link_text, link, class: link_style
@@ -0,0 +1,3 @@
1
+ - model = @category || @search
2
+ - if model.product_grid_content_for(position).present?
3
+ = render_content_blocks(model.product_grid_content_for(position))
@@ -0,0 +1,27 @@
1
+ #product-grid-cell-content-block.style-guide__subsection
2
+
3
+ %h3= link_to_style_guide('components', 'product_grid_cell_content_block')
4
+
5
+ %p A content block that displays an image with positionable content in the product grid.
6
+ %p This content block is designed for use inside the product grid.
7
+ %p Options for width and height allow the admin user to create blocks that are 1x1, 1x2, 2x1, and 2x2 cells in size.
8
+ %p When creating a block that is taller than 1 cell the block will automatically be floated to the left to prevent breaking the grid. This float can be set to 'right' using controls in the admin.
9
+
10
+ - [1, 2].each do |width|
11
+ - [1, 2].each do |height|
12
+ %p
13
+ Should display a <b>#{width} x #{height} cell</b>
14
+ - if height > 1
15
+ and should automatically float left
16
+
17
+ - cell_width_class = Workarea.config.product_grid_cell_classes[width.to_s.to_sym]
18
+ - cell_styles = height > 1 ? "height: 500px; float: left;" : "height: 250px;"
19
+ .grid{ class: ('clearfix' if height > 1) }
20
+ .grid__cell{ class: cell_width_class, style: cell_styles, data: { pagination_item: '' } }
21
+ .product-grid-cell-content-block{ data: { match_product_summary_height: { rows: height }.to_json } }
22
+ .product-grid-cell-content-block__content
23
+ %h2 Heading Text
24
+ %p.product-grid-cell-content-block__action= link_to 'Link Text', '#', class: 'button'
25
+ .product-grid-cell-content-block__media-container.product-grid-cell-content-block__media-container--maintain-aspect-ratio
26
+ = link_to '#', class: 'product-grid-cell-content-block__image-link' do
27
+ = image_tag 'workarea/storefront/style_guide_light_banner.png', alt: "image alt", class: 'product-grid-cell-content-block__image', style: "height: 100%"
data/bin/rails ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path("..", __dir__)
6
+ ENGINE_PATH = File.expand_path("../lib/workarea/product_grid_content/engine", __dir__)
7
+ APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
12
+
13
+ require "rails"
14
+ # Pick the frameworks you want:
15
+ require "active_model/railtie"
16
+ require "active_job/railtie"
17
+ # require "active_record/railtie"
18
+ # require "active_storage/engine"
19
+ require "action_controller/railtie"
20
+ require "action_mailer/railtie"
21
+ require "action_view/railtie"
22
+ # require "action_cable/engine"
23
+ require "sprockets/railtie"
24
+ require "rails/test_unit/railtie"
25
+ require 'rails/engine/commands'
@@ -0,0 +1,23 @@
1
+ module Workarea
2
+ Plugin.append_javascripts(
3
+ "storefront.modules",
4
+ "workarea/storefront/product_grid_content/modules/match_product_summary_height"
5
+ )
6
+
7
+ Plugin.append_stylesheets(
8
+ "storefront.components",
9
+ "workarea/product_grid_content/components/content_block_product_grid_cell",
10
+ "workarea/product_grid_content/components/product_grid_cell_content_block",
11
+ "workarea/product_grid_content/components/pagination_product_grid_content"
12
+ )
13
+
14
+ Plugin.append_partials(
15
+ "storefront.category_browse_grid_item",
16
+ "workarea/storefront/shared/product_grid_content_cell"
17
+ )
18
+
19
+ Plugin.append_partials(
20
+ "storefront.search_grid_item",
21
+ "workarea/storefront/shared/product_grid_content_cell"
22
+ )
23
+ end
@@ -0,0 +1,17 @@
1
+ Workarea.configure do |config|
2
+ config.content_areas = config.content_areas.merge(
3
+ "category" => %w(above_results in_grid below_results),
4
+ "customization" => %w(above_results in_grid)
5
+ )
6
+
7
+ config.product_grid_cell_classes = {
8
+ "1": "grid__cell--50 grid__cell--25-at-medium grid__cell--20-at-wide",
9
+ "2": "grid__cell--50-at-medium grid__cell--40-at-wide"
10
+ }
11
+
12
+ config.grid_cell_content_preview_cells = 10
13
+
14
+ config.product_grid_cell_content_block = {
15
+ color_presets: ["#000000", "#ffffff", "#999999", "#ff0000", "#00ff00", "#0000ff"]
16
+ }
17
+ end
@@ -0,0 +1,48 @@
1
+ Workarea::Content.define_block_types do
2
+ block_type "Product Grid Cell" do
3
+ description "Content block for your product grid"
4
+ view_model "Workarea::Storefront::ContentBlocks::ProductGridCellViewModel"
5
+
6
+ fieldset "Grid" do
7
+ field "Position", :integer, default: 1, min: 1, tooltip: "Where the content block will appear within the product grid"
8
+ field "Width", :options, values: [["1 cell", 1], ["2 cells", 2]], default: 1, tooltip: "Set the width of the content block. Use preview to ensure that a 2 cell width block is positioned so that it will wrap correctly at all breakpoints."
9
+ field "Height", :options, values: [["1 cell", 1], ["2 cells", 2]], default: 1, tooltip: "Note: When creating a 2 cell height the block must be floated left or right in order for the grid to flow around the content properly. If float is set to 'none' it will automatically float left."
10
+ field "Float", :options, values: ["none", "left", "right"], default: "none", tooltip: "Forces the content block to the left or right side of the row. This setting is intended for use with a 2 cell height block."
11
+ end
12
+
13
+ fieldset "Content" do
14
+ field "Asset", :asset, file_types: "image", default: find_asset_id_by_file_name("960x470_light.png"), html_data_attributes: { media_mode: ["resize", "crop", "switch"] }
15
+ field "Background Color", :color, default: "#000000", presets: Workarea.config.product_grid_cell_content_block[:color_presets]
16
+ field "Heading", :string, default: "Shop the look"
17
+ field "Link", :url, default: "/"
18
+ field "Link Text", :string, default: "Click Me!"
19
+ field "Link Style", :options, values: ["button", "text-button", "link"], default: "button"
20
+ end
21
+ end
22
+
23
+ block_type "Insights Grid Cell" do
24
+ description "Injust insights driven products to the product grid"
25
+ view_model "Workarea::Storefront::ContentBlocks::InsightsGridCellViewModel"
26
+
27
+ fieldset "Grid" do
28
+ field "Position", :integer, default: 1, min: 1, tooltip: "Where the content block will appear within the product grid"
29
+ field "Width", :options, values: [["1 cell", 1], ["2 cells", 2]], default: 1, tooltip: "Set the width of the content block. Use preview to ensure that a 2 cell width block is positioned so that it will wrap correctly at all breakpoints."
30
+ field "Height", :options, values: [["1 cell", 1], ["2 cells", 2]], default: 1, tooltip: "Note: When creating a 2 cell height the block must be floated left or right in order for the grid to flow around the content properly. If float is set to 'none' it will automatically float left."
31
+ field "Float", :options, values: ["none", "left", "right"], default: "none", tooltip: "Forces the content block to the left or right side of the row. This setting is intended for use with a 2 cell height block."
32
+ end
33
+
34
+ fieldset "Insights" do
35
+ field 'Type', :options, values: [
36
+ 'Cold Products',
37
+ 'Hot Products',
38
+ 'Most Discounted Products',
39
+ 'Non Sellers',
40
+ 'Products To Improve',
41
+ 'Promising Products',
42
+ 'Star Products',
43
+ 'Top Products',
44
+ 'Trending Products'
45
+ ], default: 'Top Products'
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ module Workarea
2
+ module ProductGridContent
3
+ class Engine < ::Rails::Engine
4
+ include Workarea::Plugin
5
+ isolate_namespace Workarea::ProductGridContent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Workarea
2
+ module ProductGridContent
3
+ VERSION = "1.3.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require "workarea"
2
+ require "workarea/storefront"
3
+ require "workarea/admin"
4
+
5
+ require "workarea/product_grid_content/engine"
6
+ require "workarea/product_grid_content/version"
7
+
8
+ module Workarea
9
+ module ProductGridContent
10
+ end
11
+ end
data/package.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "devDependencies": {
3
+ "eslint": "~5.16.0",
4
+ "eslint-config-recommended": "~4.0.0",
5
+ "stylelint": "~10.1.0",
6
+ "stylelint-config-recommended-scss": "~3.3.0",
7
+ "stylelint-scss": "~3.9.2"
8
+ }
9
+ }
Binary file
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative 'config/application'
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+ include FileUtils
4
+
5
+ # path to your application root.
6
+ APP_ROOT = File.expand_path('..', __dir__)
7
+
8
+ def system!(*args)
9
+ system(*args) || abort("\n== Command #{args} failed ==")
10
+ end
11
+
12
+ chdir APP_ROOT do
13
+ # This script is a starting point to setup your application.
14
+ # Add necessary setup steps to this file.
15
+
16
+ puts '== Installing dependencies =='
17
+ system! 'gem install bundler --conservative'
18
+ system('bundle check') || system!('bundle install')
19
+
20
+ # Install JavaScript dependencies if using Yarn
21
+ # system('bin/yarn')
22
+
23
+ puts "\n== Removing old logs and tempfiles =="
24
+ system! 'bin/rails log:clear tmp:clear'
25
+
26
+ puts "\n== Restarting application server =="
27
+ system! 'bin/rails restart'
28
+ end