Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"

gem "redcarpet"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ GEM
rdoc (6.14.1)
erb
psych (>= 4.0.0)
redcarpet (3.6.1)
redis-client (0.24.0)
connection_pool
regexp_parser (2.10.0)
Expand Down Expand Up @@ -590,6 +591,7 @@ DEPENDENCIES
propshaft
puma (>= 5.0)
rails (~> 8.0.2)
redcarpet
rubocop-rails-omakase
selenium-webdriver
sentry-rails
Expand Down
5 changes: 5 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
module ApplicationHelper
def markdown(text)
renderer = Redcarpet::Render::HTML.new(escape_html: true)
markdown = Redcarpet::Markdown.new(renderer, fenced_code_blocks: true)
markdown.render(text.to_s).html_safe
end
Comment on lines +2 to +6
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for the record, Since this is under our control, we can leave it as it is, but we are going to render HTML tags and test it with the following record:

Puzzle.create!(question: "test `<script>alert(1)</script>`", answer: :ruby, explanation: "loose <img src=x onerror=alert(1)>", state: :pending)
Image

This is a safer way to do it:

Suggested change
def markdown(text)
RedcarpetCompat.new(text, :fenced_code).to_html.html_safe
end
def markdown(text)
r = Redcarpet::Render::HTML.new(escape_html: true)
Redcarpet::Markdown.new(r, fenced_code_blocks: true).render(text.to_s).html_safe
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

end
4 changes: 2 additions & 2 deletions app/views/puzzles/_puzzles_table.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
<tbody>
<% puzzles.each do |puzzle| %>
<tr id="<%= dom_id(puzzle) %>">
<td><%= simple_format(puzzle.question) %></td>
<td><%= markdown(puzzle.question) %></td>
<td><%= puzzle.answer %></td>
<td><%= puzzle.explanation %></td>
<td><%= markdown(puzzle.explanation) %></td>
<td>
<% if puzzle.link.present? %>
<%= link_to 'View', puzzle.link, target: '_blank' %>
Expand Down
4 changes: 2 additions & 2 deletions app/views/puzzles/update.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<turbo-stream action="replace" target="<%= dom_id(@puzzle) %>">
<template>
<tr id="<%= dom_id(@puzzle) %>">
<td><%= simple_format(@puzzle.question) %></td>
<td><%= markdown(@puzzle.question) %></td>
<td><%= @puzzle.answer %></td>
<td><%= @puzzle.explanation %></td>
<td><%= markdown(@puzzle.explanation) %></td>
<td>
<% if @puzzle.link.present? %>
<%= link_to 'View', @puzzle.link, target: '_blank' %>
Expand Down
29 changes: 17 additions & 12 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,85 +6,90 @@
# rates > 80 do not. Entries without `state` default to :pending.
puzzles = [
{
question: "Ruby or Rails provided this method? Array.new(5) { |i| i * 2 }",
question: "Ruby or Rails provided this method? `Array.new(5) { |i| i * 2 }`",
answer: :ruby,
explanation: "`Array.new` is a core Ruby method that creates a new array with a specified size and optional block for initialization. This is part of Ruby’s core library."
},
{
question: "Ruby or Rails provided this method? File.open('log.txt', 'w') { |file| file.write('Hello, World!') }",
question: "Ruby or Rails provided this method? `File.open('log.txt', 'w') { |file| file.write('Hello, World!') }`",
answer: :ruby,
explanation: "`File.open` is a core Ruby method for opening files. It’s part of Ruby's standard library for file handling, not part of Rails."
},
{
question: "Ruby or Rails provided this method? render json: @user",
question: "Ruby or Rails provided this method? `render json: @user`",
answer: :rails,
explanation: "`render json:` is a Rails method used to render a JSON response from a controller action."
},
{
question: "Ruby or Rails provided this method? link_to 'Home', root_path",
question: "Ruby or Rails provided this method? `link_to 'Home', root_path`",
answer: :rails,
explanation: "`link_to` is a Rails helper method that generates HTML links. `root_path` is a Rails path helper."
},
{
question: "Ruby or Rails provided this method? params[:id]",
question: "Ruby or Rails provided this method? `params[:id]`",
answer: :rails,
explanation: "`params[:id]` is used in Rails to fetch query parameters or URL parameters in controller actions."
},
{
question: "Ruby or Rails provided this method? before_action :authenticate_user!",
question: "Ruby or Rails provided this method? `before_action :authenticate_user!`",
answer: :rails,
explanation: "`before_action` is a Rails callback defined in `ActionController::Callbacks`. It runs specified methods before controller actions.",
state: :archived,
sent_at: 7.days.ago,
success_rate: 20
},
{
question: "Ruby or Rails provided this method? 42.times { puts 'hello' }",
question: "Ruby or Rails provided this method? `42.times { puts 'hello' }`",
answer: :ruby,
explanation: "`Integer#times` is a core Ruby method that iterates a block a specified number of times.",
state: :archived,
sent_at: 6.days.ago,
success_rate: 40
},
{
question: "Ruby or Rails provided this method? User.where(active: true).order(:name)",
question: "Ruby or Rails provided this method? `User.where(active: true).order(:name)`",
answer: :rails,
explanation: "`where` and `order` are ActiveRecord query methods provided by Rails to build SQL queries.",
state: :archived,
sent_at: 5.days.ago,
success_rate: 50
},
{
question: "Ruby or Rails provided this method? 'hello world'.split(' ')",
question: "Ruby or Rails provided this method? `'hello world'.split(' ')`",
answer: :ruby,
explanation: "`String#split` is a core Ruby method that divides a string into an array based on a delimiter.",
state: :archived,
sent_at: 4.days.ago,
success_rate: 70
},
{
question: "Ruby or Rails provided this method? flash[:notice] = 'Saved!'",
question: "Ruby or Rails provided this method? `flash[:notice] = 'Saved!'`",
answer: :rails,
explanation: "`flash` is a Rails feature provided by `ActionDispatch::Flash` for passing messages between requests.",
state: :archived,
sent_at: 3.days.ago,
success_rate: 80
},
{
question: "Ruby or Rails provided this method? [1, 2, 3].reduce(:+)",
question: "Ruby or Rails provided this method? `[1, 2, 3].reduce(:+)`",
answer: :ruby,
explanation: "`Enumerable#reduce` (also `inject`) is a core Ruby method that combines elements using a binary operation.",
state: :archived,
sent_at: 2.days.ago,
success_rate: 90
},
{
question: "Ruby or Rails provided this method? validates :email, presence: true, uniqueness: true",
question: "Ruby or Rails provided this method? `validates :email, presence: true, uniqueness: true`",
answer: :rails,
explanation: "`validates` is an ActiveModel/ActiveRecord method from Rails that adds validation rules to models.",
state: :archived,
sent_at: 1.day.ago,
success_rate: 100
},
{
question: "Ruby or Rails provided this method?\n\n```\nclass SomeClass\n def method\n whatever\n end\nend\n```",
answer: :rails,
explanation: "Not really important, just to show it renders blocks"
}
]

Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/puzzles.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
one:
question: "Ruby or Rails provided this method? render json: @user"
question: "Ruby or Rails provided this method? `render json: @user`"
answer: "rails"
explanation: "This is a test puzzle"
link: "https://example.com"
state: "approved"
suggested_by: "test_user"

two:
question: "Ruby or Rails provided this method? puts 'Hello, World!'"
question: "Ruby or Rails provided this method? `puts 'Hello, World!'`"
answer: "ruby"
explanation: "This is a test puzzle 2"
link: ""
Expand Down
27 changes: 27 additions & 0 deletions test/helpers/markdown_helper_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require "test_helper"

class MarkdownHelperTest < ActionView::TestCase
include ApplicationHelper

test "renders `code` with code tag" do
puzzle_content = "Some content with `some code` in it"
as_html = markdown(puzzle_content)

assert_includes as_html, "<code>some code</code>"
end

test "renders ```code``` with code block" do
puzzle_content = <<~CONTENT
Some content with

```
class ACodeBlock
end
```
in it
CONTENT
as_html = markdown(puzzle_content)

assert_includes as_html, "<pre><code>class ACodeBlock"
end
end