DEV Community

Cover image for What happens when you submit an article?

What happens when you submit an article?

Antony Garand
Security enthusiast, FullStack developer, challenge solver
Updated on ・7 min read

Under the hood of (Part 1)

This article series will uncover the secrets of's source code, helping the world understand and improve this application.

The source code is available on github, and you get a cool badge for contributing!

Disclaimer: I don't know ruby, nor ruby on rails, so there might be parts of this post which are incorrect or lacking. Feel free to point these out and I'll do my best to correct them!


Submitting an article is easy, right?

All you need to do is press the SAVE POST button, and there we go!

There is much more complexity to it, and in this post I'll uncover the magic happening behind the scenes!

Application overview uses Ruby On Rails for its back-end, and Preact on the front-end.

The back-end hosts a REST api, and the front-end uses those to access and publish data.

The front-end is a Single Page Application, but is also Server Side Rendered.

This means that if you access directly, the server will generate all of the HTML for you, ready for you browser to display it.
Then, whenever the bundled preact scripts are loaded, we gain the SPA functionality: When trying to access a new page, it will be fetched by JavaScript, and preact will update the page content with the received html.

Showing the new article view

Alright, so you want to write an article.

First, you head up to

Ruby on rails check its route in /config/routes to find /new using the GET protocol.

This route tells it to load the articles controller, and the new method.

get "/new" => "articles#new"
get "/new/:template" => "articles#new"

get "/pod" => "podcast_episodes#index"
get "/readinglist" => "reading_list_items#index"

Enter fullscreen mode Exit fullscreen mode

This controller can be found under /app/controllers/articles_controller.rb.

Before loading the new method, few permissions check will be executed.
Those are declared on top of the controller, and includes method such as ensured you are logged in and preventing banned users from creating articles.

class ArticlesController < ApplicationController
  include ApplicationHelper
  before_action :authenticate_user!, except: %i[feed new]
  before_action :set_article, only: %i[edit update destroy]
  before_action :raise_banned, only: %i[new create update]
  before_action :set_cache_control_headers, only: %i[feed]
  after_action :verify_authorized
// ...
Enter fullscreen mode Exit fullscreen mode

Once those are done, the new method is called:

  def new
    @user = current_user
    @tag = Tag.find_by_name(params[:template])
    @article = if @tag&.submission_template.present? && @user
                 authorize Article
                             processed_html: "")
                 if params[:state] == "v2" || Rails.env.development?
                     body_markdown: "---\ntitle: \npublished: false\ndescription: \ntags: \n---\n\n",
                     processed_html: "",
Enter fullscreen mode Exit fullscreen mode

it is quite straightforward: It checks if you are using a template (Aka. using the path /new/:template), and loads either this template, or creates a generic Front Matter body.

The represents the New Article View, available under /app/views/articles/new.html.erb

<% title "New Article - DEV" %>

<% if user_signed_in? %>
  <% if params[:state] == "v2" || Rails.env.development? %>
    <%= javascript_pack_tag 'articleForm', defer: true %>
    <%= render 'articles/v2_form' %>
  <% else %>
    <%= render 'articles/markdown_form' %>
  <% end %>
<% else %>
  <%= render "devise/registrations/registration_form" %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

This loads the correct view based on our conditions, typically articles/markdown_form

<%= form_for(@article, html: {id:"article_markdown_form"}) do |f| %>
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>

      <% @article.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
<% end %>

<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

This form renders the HTML you usually see when accessing, we're finally there!
The generated HTML is used as body in the /app/views/layouts/application.html.erb at some point in Ruby On Rails's magic.

Saving an article

Alright, you've written your awesome article about how good Ben Halpern's website is, and you now wish to publish it for everyone to see!

You've set the published value to true, and you press this big blue SAVE POST button. What happens then?

Your HTML was loaded, Preact loaded, and it listens to the click event for the SAVE button.


We're now in the front-end code, under /app/javascript/article-form/articleForm.jsx.

The button itself is under elements/publishToggle.jsx, and our articleForm.jsx added an event listener for the click.


<button onClick={onPublish}>
  {published ? 'SAVE CHANGES' : 'PUBLISH' }
Enter fullscreen mode Exit fullscreen mode


  onChange={linkState(this, 'published')}
  // ...
Enter fullscreen mode Exit fullscreen mode


onPublish = e => {
  this.setState({submitting: true, published: true})
  let state = this.state;
  state['published'] = true;
  submitArticle(state, this.handleArticleError);
Enter fullscreen mode Exit fullscreen mode

The submitArticle function is imported from ./actions.

actions.js - submitArticle

export function submitArticle(payload, errorCb, failureCb) {
  const method = ? 'PUT' : 'POST'
  const url = ? '/api/articles/'+ : '/api/articles'
  fetch(url, {
    // ...
    body: JSON.stringify({
      article: payload,
  .then(response => response.json())
  .then(response => {
    if (response.current_state_path) {
    } else {

Enter fullscreen mode Exit fullscreen mode

Therefore, once you click the SAVE ARTICLE button, the following happens:

  • An article is created based on the current state variable
  • The article is sent to /api/articles
  • Once the save is complete, we're redirect to its new URL.

We can now start digging into the back-end!


We're now receiving an article from the front-end in the form of a JSON file, at the /api/articles route via a POST.


Once again, in the /config/routes.rb file, we need to search for our endpoint.

There is an api namespace which contains our articles resource.

A Ruby on Rails Resource maps few default CRUD verbs to their respective methods, so in our case the POST method will call the articles#create method.


namespace :api, defaults: { format: "json" } do
  scope module: :v0,
        constraints: 0, default: true) do
    resources :articles, only: %i[index show create update] do
      collection do
        get "/onboarding", to: "articles#onboarding"
    resources :comments
// ...
Enter fullscreen mode Exit fullscreen mode


We now are in the /app/controllers/articles_controller, under the create method:

def create
  authorize Article
  @user = current_user
  @article = ArticleCreationService.
    new(@user, article_params, job_opportunity_params).
Enter fullscreen mode Exit fullscreen mode


This method calls the ArticleCreationService, which will create our article!

def create!
  raise if"published_article_creation")
  article =
  article.user_id =
  article.show_comments = true
  if user.organization_id.present? && article_params[:publish_under_org].to_i == 1
    article.organization_id = user.organization_id
    if article.published
      Notification.send_all(article, "Published")
Enter fullscreen mode Exit fullscreen mode

This services creates a new instance of the Article model, and saves it.


With Ruby on Rails, our models are Active Records, and have a bit of magic attached to it.

While I won't dive into the database mapping part of the object, what I find interesting are the before methods, called when creating or saving an object.

before_validation :evaluate_markdown
before_validation :create_slug
before_create     :create_password
before_save       :set_all_dates
before_save       :calculate_base_scores
before_save       :set_caches
after_save :async_score_calc, if: :published
Enter fullscreen mode Exit fullscreen mode

The before_validation methods will be called before ensuring the object is valid.

The remaining methods should be quite explicit by their names.

The model will also perform many validations on its properties.

  validates :slug, presence: { if: :published? }, format: /\A[0-9a-z-]*\z/,
                   uniqueness: { scope: :user_id }
  validates :title, presence: true,
                    length: { maximum: 128 }
  validates :user_id, presence: true
  validates :feed_source_url, uniqueness: { allow_blank: true }
  validates :canonical_url,
            url: { allow_blank: true, no_local: true, schemes: ["https", "http"] },
uniqueness: { allow_blank: true }
Enter fullscreen mode Exit fullscreen mode


Phew, this article is now saved! That was a lot of work for a simple action.

As a quick recap, to view an article, we load the correct Controller, which loads a View and renders it to the page.

When trying to perform CRUD operations, we find the correct route based on our API Resource, which loads a Controller. This controller can interact with the data using Services, themselves using Models to interact with the database.

Now that the technical side is covered, I would like to get some feedback on this post.

I have few objectives with this serie:

  1. Help people navigate through big codebases and understand their architecture
  2. Lower the contribution entry-barrier for open-source projects such as this website.

This is why feedback is important.
Did it help you understand the source?
Perhaps there is something specific you would like to see?

Please tell me in a comment below, and I'll do my best to improve this serie!

Discussion (10)

ben profile image
Ben Halpern

This is a great post!

One element possibly glossed over in the current implementation is that we have a new version of the editor not yet the default in production, because it's a work in progress, but is the default in development.

The one you're describing is the new one. You can see this in prod by visiting It still has some features to be ironed out, but should be a slightly cleaner experience than the current one, though we plan to keep offering the current one in settings.

Just a detail, but you can see the code that decides this in the snippets.

It is so helpful to have the code audited and described in this way. I was just thinking about writing a similar post on some of the Rails internals.

harishkgarg profile image
Harish Garg

Hi Ben, Just looked at the new editor you guys are working on. It's very clean and nice. Looking forward to it's general release.

titoelfoly profile image

i got a question , i'm still a beginner learning web dev using spring mvc , is it worth to learn ruby to understand how the code work , or do i have to stick with spring , sorry my english kinda bad

antogarand profile image
Antony Garand Author

If you want to understand MVC, either frameworks both framework are fine.

If you want to understand's source, you can start reading it right now, and learn the parts of ruby which don't make sense.

I don't know ruby, but its syntax is explicit enough for anyone to read its source code in my opinion.

titoelfoly profile image

thank you that's really helpful answer

joshcheek profile image
Josh Cheek • Edited

Really enjoyed this post! Would love to read one about authentication. When I went to play with it, it looked like I was going to need to set up a GH app for OAuth, which felt like a lot of work, so I instead edited the seeded posts to see if my feature worked. But that will only take me so far, so at some point I'll probably need to figure out how to get authenticated in dev mode. If you're looking to do more like this (I assume so, since this is titled "Part 1"), then that would be a great topic!

rhymes profile image

Yep, you need to get the GitHub keys to authenticate locally (or the keys for Twitter authentication)

See for how to

rafalpienkowski profile image
Rafal Pienkowski

Great post. Can I borrow you to make documentation for my projects? With such nice pictures? :) I’m kidding. Well done.

jeancarlosn profile image
jeann • Edited

Waaoo nice job Brother!

aurelkurtula profile image
aurel kurtula

Looking into source code is in my todo list this post is great.