Mini-Chat with Rails

Share this article

Comments - Icon

Comments are everywhere. You’ve probably implemented some kind of commenting system in some of your projects (if not then you may be interested in my “Nested Comments with Rails” article).

But, what if we wanted to make a fully asynchronous commenting system where the user does not experience any page reload while sending or receiving new comments? What tools could we use to achieve this task? What problems might we face and how can they be solved?

In this two-part article series I am going to show you how to create a fully-functional mini-chat on Rails in six iterations. We will discuss the following topics:

  • Authentication via multiple social networks (OAuth 2, OmniAuth)
  • Asynchronous comments posting
  • Instant loading of the new comments without the need to reload the page (we will discuss two possible solutions: AJAX polling and web sockets with Faye)

We are also going to discuss some caveats that you should be aware of with AJAX polling and Faye’s security.

The source code is available at GitHub.

Check out the working demo at http://sitepoint-minichat.herokuapp.com/.

Some Planning

Rails 4.1.5 will be used for this demo but the same solution can be implemented with Rails 3.

Okay, before starting our journey, let’s write down the requirements for our mini-chat app:

  • Only authenticated users should be able to read and post comments.
  • Users should be able to authenticate via Facebook or Twitter.
  • Each comment should be provided with user’s name, avatar, and a link to his profile in social network.
  • Comments should be posted and received instantly (well, nearly instantly), without any page reloads.
  • Empty and very long comments are not permitted.
  • When users enters the chat, they should be able to read some comments that were posted previously. Old comments should be removed automatically.

Great, the requirements are formulated and it is time to proceed to the first (and probably the largest) iteration – hold on tight!

Authentication

Start off by creating a new Rails app without the default testing suite:

$ rails new mini_chat -T

Before digging into the code, stop for a moment and think about the authentication system we are going to implement for this app. Since we want our users to be able to sign in via Facebook and Twitter, the OAuth 2 protocol will be used. You’ve probably heard about OAuth2, but if not – don’t worry – I will explain the basics.

The main idea behind OAuth2 is that the user enters his credentials on the platform’s website itself (Twitter or Facebook in our case). This way the user never exposes his password to our app; instead, the website is only given some information about the user’s account (name, surname, profile URL, avatar, etc.) and a key pair (or a single key, depending on the platform) that is used to issue API calls (for example, to post a message on Facebook on behalf of the user).

The key idea is that the user always knows what actions the third-party resource will be able to perform and if he does not trust this website, he can reject the authentication. Moreover, the key pair issued to the resource cannot be used to perform any action – only those actions that the resource initially requested are allowed (this is called a scope). This key pair has a limited lifetime and the user may also revoke access for any previously authenticated resource manually at any time.

When the authentication phase starts, a third-party website forwards a special application key used to identify the website. An array of actions that the resource wants to perform is also sent. The user is prompted with a dialog explaining that this website wants him to authenticate and asks for the permission to perform a list of actions. If the user agrees, he is redirected back to the site with information about the user that has just authenticated, as well as the key pair to issue API calls.

At first, this may seem complex. In reality, we can easily implement this scenario in a Rails app.

First of all we need a model and a table to store information about the authenticating user. Actually, we don’t even need API keys because we are not going to issue any API calls. For this demo let’s store the following information in the users table:

  • provider (string) – the name of the social network that was used for authentication. In our case it would be either “facebook” or “twitter”.
  • name (string) – the user’s name (optionally with a surname, but that depends on what the user has entered while creating his profile).
  • profile_url (string) – a link to the user’s profile.
  • avatar_url (string) – a link to the user’s avatar.
  • uid (string) – user’s unique identifier on the social network. We will see why it is needed a bit later.

Go on and create the required migration:

$ rails g model User name:string avatar_url:string provider:string profile_url:string uid:string

Now open migration’s file and alter it a bit by adding these lines at the end of the change method:

xxx_create_users.rb

[...]
def change
  [...]
  add_index :users, :uid
  add_index :users, :provider
  add_index :users, [:uid, :provider], unique: true
end
[...]

As you see, we create three indicies here on uid and provider columns, as well as the clustered index on those columns and ask it to enforce uniqueness. The idea behind this is that there can’t be two different users with the same social network name and unique identifier.

Apply the migration:

$ rake db:migrate

At this moment, we can move on to the authentication itself. Luckily, there are gems that will make our life much easier – these are omniauth-facebook by Mark Dodwell and omniauth-twitter by Arun Agrawal. What is this “omniauth” suffix about?

OmniAuth is a gem created by Intridea, Inc. aimed at standardizing multi-provider authentication for web applications. OmniAuth allows you to use as many different authentication strategies as you need allowing your users to authenticate via OpenID, Facebook, Twitter, Google, etc. There are a number of strategies to choose from but you can create your own. For this app, we are going to use two strategies: Twitter and Facebook, packed into those two gems.

Drop these gems into Gemfile

Gemfile

[...]
gem 'omniauth-facebook'
gem 'omniauth-twitter'

and run

$ bundle install

Now we have to register our website as an app in Facebook and Twitter. Visit https://developers.facebook.com/apps and create a new app here. Give it a name in the Settings section (I’ve called my “SitePoint Mini-Chat Demo”). Don’t forget that this name will be visible to authenticating user! Next, click “Add platform” and choose “Website”. Fill in “Site URL” (in my case I’ve entered “http://sitepoint-minichat.herokuapp.com/”) and “App Domains” (I’ve entered “sitepoint-minichat.herokuapp.com”). Keep in mind, this domain must be derived from the URL, otherwise your settings won’t be saved. Also provide a “Contact E-mail” and click “Save”. Navigate to “Status & Review” and toggle “Do you want to make this app and all its live features available to the general public?” switch to “Yes”.

For a real app, spend some time adjusting the other app’s settings (like picture, description, tagline, etc.) We are going to head to the “Dashboard” section. Here, note the “App ID” and “App Secret” fields (the latter is masked). Click “Show” to reveal the Secret and enter your password – these are the keys that we will need to set up authentication.

Create a new file called omniauth.rb in config/initializers and paste the following code:

config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'],
           scope: 'public_profile', display: 'page', image_size: 'square'
end

To store the key pair I am using Heroku environmental variables. As you’ve probably guessed FACEBOOK_KEY is the “App ID” and FACEBOOK_SECRET is the “App Secret”.

The scope is the list of permissions that our app will request. Since we won’t access any Facebook API functions, the only requested permission is the public_profile that means “retrieve user’s basic data”. Read more about Facebook’s permissions here.

display is another parameter sent to Facebook while authenticating that controls how the authentication page will look like. If, for example, you are opening the authentication page in a small popup, then you can provide popup as the display value. Read more here.

The last option is image_size, which controls the size of the user’s avatar that we want to receive. square means a “50x50px image”.

Now we need to do the same for Twitter. Navigate to https://apps.twitter.com/ and create a new app. Give it a name, a description, and a URL. Also, enter the callback URL in the following format: “apps’s_URL/auth/twitter/callback”. This format is defined by OmniAuth and is the resource the user will be redirected to after successful authentication. We will take care of that in a moment.

Next, accept the terms and create the app. You may want to spend some time by adjusting other settings of your app, like Organization name, Icon, etc. Also, notice in the Details pane you can change the Access level of the app. We will keep the default “Read-only” setting.

Open API Keys – this is where the API key and API secret reside. Return to your omniauth.rb file and add some more code:

config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'], image_size: 'normal'
  [...]

Paste your keys directly into this file or use some other method to store them (which is recommended – they should not be visible for everyone). The image_size option supplies the user’s avatar as a 48x48px image. For a full list of available options, check documentation.

OK, we’ve finished with probably the most tedious task of this demo and are ready to proceed. Remember the callback URL? We need to create the corresponding route now:

config/routes.rb

[...]
get '/auth/:provider/callback', to: 'sessions#create'
get '/auth/failure', to: 'sessions#auth_fail'
get '/sign_out', to: 'sessions#destroy', as: :sign_out
[...]

I’ve also created sign_out and authentication failure routes.

On to the controller:

sessions_controller.rb

class SessionsController < ApplicationController
  def create
    user = User.from_omniauth(request.env['omniauth.auth'])
    cookies[:user_id] = user.id
    flash[:success] = "Hello, #{user.name}!"
    redirect_to root_url
  end

  def destroy
    cookies.delete(:user_id)
    flash[:success] = "See you!"
    redirect_to root_url
  end
  
  def auth_fail
    render text: "You've tried to authenticate via #{params[:strategy]}, but the following error occurred: #{params[:message]}", status: 500
  end
end

In the create action, call a to-be-created method from_omniauth and pass request.env['omniauth.auth'] as an argument. The request.env['omniauth.auth'] hash contains all the information about the authenticated user (it is called the “authentication hash”).

After the user has been created, store its id in the cookie so that we can check if she is authenticated. Then, set a welcoming message and redirect her to the root_url (also non-existent yet).

The destroy action is even simpler. Just delete the user_id cookie, set the goodbye message, and redirect to the main page.

If you want to have a look at the authentication hash, you may replace create method’s code with this line:

render text: request.env['omniauth.auth'].to_yaml

then navigate to “your_app’s_url/auth/twitter/” (or “auth/facebook”) and observe the results.

We need the User.from_omniauth method. In this method, the user should be either created or found (by her unique id and social network name) and updated:

models/user.rb

class User < ActiveRecord::Base
  class << self
    def from_omniauth(auth)
      provider = auth.provider
      uid = auth.uid
      info = auth.info.symbolize_keys!
      user = User.find_or_initialize_by(uid: uid, provider: provider)
      user.name = info.name
      user.avatar_url = info.image
      user.profile_url = info.urls.send(provider.capitalize.to_sym)
      user.save!
      user
    end
  end
 end

Here I use the symbolize_keys! method to call methods like info.name. As you can see, we fetch all the required user’s information, then save the record, and return the user as a result.

You may notice the line info.urls.send(provider.capitalize.to_sym). Since we are working with multiple providers here and URLs are normally returned in the following way:

:urls => { :Twitter => "https://twitter.com/johnqpublic" }

We just need to know the social network name to fetch user’s profile address. Check out an example authentication hash here.

At this point we are 95 % finished with the authentication. If you ever need to add more authentication options, like Open ID or Google, that can be done easily. Just modify the initializers/omniauth.rb file by adding another provider and test the authentication hash (some social networks may present a bit different hash from the ones that we’ve just seen).

The last step here is to present our users with the links to authenticate. Before doing this, we need a root route and a corresponding view, so let’s do that in the next iteration.

Comments

In this iteration, we need to switch to the comments – we are building a messaging app after all. Let’s call our model Comment and have it contain the following fields (apart from the default id, created_at, updated_at):

  • body (text) – the actual message.
  • user_id (integer) – foreign key pointing to the user who posted this message.

As you see, this is a really simple model. Create and apply the migration:

$ rails g model Comment body:text user:references
$ rake db:migrate

Don’t forget to open user.rb and add this line:

models/user.rb

[...]
has_many :comments, dependent: :delete_all
[...]

Great, at this point we are ready to add some routes:

config/routes.rb

[...]
resources :comments, only: [:new, :create]
root to: 'comments#new'
[...]

We are only going to need new and create actions for this demo. We are not going to build a backend to manage comments, but you may add this functionality on your own.

The corresponding controller (the methods will be populated later):

comments_controller.rb

class CommentsController < ApplicationController
  def new
    @comment = Comment.new
  end

  def create
  end
end

Before creating the view, hook up Bootstrap to help us with styling:

Gemfile

[...]
gem 'bootstrap-sass'
[...]

application.css.scss

@import 'bootstrap';
@import 'bootstrap/theme';

Also modify your layout to take advantage of Bootstrap’s styles:

layouts/application.html.erb

[...]
<body>
  <div class="navbar navbar-default navbar-static-top">
    <div class="container">
      <div class="navbar-header">
        <%= link_to 'Mini-chat', root_path, class: 'navbar-brand' %>
      </div>
    </div>
  </div>

  <div class="container">
    <% flash.each do |key, value| %>
      <div class="alert alert-<%= key %>">
        <%= value %>
      </div>
    <% end %>

    <%= yield %>
  </div>
</body>
[...]

Okay, now we can move on to the sole view:

comments/new.html.erb

<div class="jumbotron">
  <h1>Welcome to our mini-chat!</h1>
  <p class="lead">Please sign in via one of the social networks to start chatting</p>
  <%= link_to '', '/auth/twitter', class: 'social twitter' %>
  <%= link_to '', '/auth/facebook', class: 'social facebook' %>
</div>

This is the place where we present users with the authentication links. The auth part is defined by OmniAuth, ollowed by the provider’s name (facebook or twitter in our case). Let’s also style these links a bit. Probably the easiest way to achieve this is using the social networks’ icons that are recognized by virtually any Internet user. To download the official icons, visit these resources: https://www.facebookbrand.com/ and https://about.twitter.com/press/brand-assets. I’ve already created a sprite image (with the help of http://csssprites.com/) that you can use.

Now apply some styling:

application.css.scss

[...]
.social {
  background: image-url('logos.png') no-repeat transparent;
  display: inline-block;
  &.twitter {
    width: 50px;
    height: 42px;
    background-position: 0 -52px;
  }

  &.facebook, &.facebook:active {
    width: 50px;
    height: 50px;
    background-position: 0 0;
  }
}

Note that I am using image-url here because, otherwise, my background image will not be rendered on Heroku.

Before moving on to the commenting form, we need to check if the user is authenticated. Traditionally, this method is called current_user:

application_controller.rb

def current_user
  @current_user ||= User.find_by(id: cookies[:user_id]) if cookies[:user_id]
end

helper_method :current_user

With this method present, we can present an authenticated user with a “Sign Out” link:

layouts/application.html.erb

[...]
<div class="navbar navbar-default navbar-static-top">
  <div class="container">
    <div class="navbar-header">
      <%= link_to 'Mini-chat', root_path, class: 'navbar-brand' %>
    </div>

    <% if current_user %>
      <ul class="nav navbar-nav navbar-right">
        <li><%= image_tag current_user.avatar_url %></li>
        <li><%= link_to 'Sign out', sign_out_path %></li>
      </ul>
    <% end %>
  </div>
</div>
[...]

Returning to the view:

comments/new.html.erb

<% if current_user %>
  <div class="page-header">
    <h1>Join the discussion!</h1>
  </div>

  <%= render 'form' %>
<% else %>
  <%= render 'welcome' %>
<% end %>

I’ve extracted the welcome message along with the authentication links to a separate partial:

comments/_welcome.html.erb

<div class="jumbotron">
  <h1>Welcome to our mini-chat!</h1>
  <p class="lead">Please sign in via one of the social networks to start chatting</p>
  <%= link_to '', '/auth/twitter', class: 'btn social twitter' %>
  <%= link_to '', '/auth/facebook', class: 'btn social facebook' %>
</div>

Now, create the form partial:

comments/_form.html.erb

<div class="well">
  <%= form_for @comment do |f| %>
    <div class="form-group">
      <%= f.label :body, 'Enter your comment:' %>
      <%= f.text_area :body, rows: 3, class: 'form-control', required: true, maxlength: 2000 %>
      <small class="label label-warning">Cannot be blank or contain more than 2000 symbols.</small>
    </div>

    <%= f.submit 'Post', class: 'btn btn-primary btn-lg' %>
  <% end %>
</div>

Nothing fancy going on here – a single textarea for entering a message and a “Post” button. We also need a validation to prevent users from sending empty or very long messages:

models/comment.rb

[...]
validates :body, presence: true, length: {maximum: 2000}
[...]

Later, you may also add some client-side validation (for example, with the help of jQuery Validate plugin).

The last thing to do here is to flesh out the create action:

comments_controller.rb

[...]
def create
  if current_user
    @comment = current_user.comments.build(comment_params)
    if @comment.save
      flash[:success] = 'Your comment was successfully posted!'
    else
      flash[:error] = 'Your comment cannot be saved.'
    end
  end
  redirect_to root_url
end

private

def comment_params
  params.require(:comment).permit(:body)
end
[...]

Our users now can post their comments. However, they won’t see them. Let’s fix that!

comments_controller.rb

[...]
def new
  @comment = Comment.new
  @comments = Comment.order('created_at DESC')
end
[...]

comments/new.html.erb

<% if current_user %>
  <div class="page-header">
    <h1>Join the discussion!</h1>
  </div>

  <%= render 'form' %>
  <div class="panel panel-default" id="comments">
    <div class="panel-body">
      <ul class="media-list">
        <%= render @comments %>
      </ul>
    </div>
  </div>
<% else %>
  <%= render 'welcome' %>
<% end %>

I am using yet another partial that will be rendered for each comment in the @comments array:

comments/_comment.html.erb

<li class="media comment">
  <%= link_to image_tag(comment.user.avatar_url, alt: comment.user.name, class: "media-object"),
              comment.user.profile_url, target: '_blank', class: 'pull-left' %>
  <div class="media-body">
    <h4 class="media-heading"><%= link_to comment.user.name, comment.user.profile_url, target: '_blank' %> says
      <small class="text-muted">[at <%= comment.created_at.strftime('%-d %B %Y, %H:%M:%S') %>]</small></h4>
    <p><%= comment.body %></p>
  </div>
</li>

Lastly, a bit of styling:

application.css.scss

[...]
#comments {
  max-height: 500px;
  overflow: auto;
}

At this point, fire up the server, authenticate via one of the social networks, and try to post some comments. We are ready to move on to the next iteration!

Removing Excessive Comments

We do not want to present users with all the messages that were posted since the creation of the app. In this iteration, we are going to create a scheduled task to remove excessive comments (obviously, this problem can be solved in many ways).

I am going to use Heroku Scheduler add-on here but you may employ another solution, like Rufus Scheduler.

This add-on can be installed with the following command (please note that your app should be already created on Heroku):

$ heroku addons:add scheduler:standard

Next create the actual task:

lib/tasks/scheduler.rake

desc "This task is called by the Heroku scheduler add-on"

task remove_excessive_comments: :environment do
  puts "Removing excessive comments now..."
  Comment.remove_excessive!
end

and the remove_excessive! method:

models/comment.rb

[...]
class << self
  def remove_excessive!
    if all.count > 100
      order('created_at ASC').limit(all.count - 50).destroy_all
    end
  end
end
[...]

The numbers here are arbitrary, so adjust them as you like. The general idea here is, if there are many comments posted, delete all except for the last 50.

You may test this task by running

$ heroku run rake remove_excessive_comments

When you are ready run

$ heroku addons:open scheduler

and create a new task here (enter rake remove_excessive_comments in the “Task” field) with the required frequency.

You may also implement infinite scrolling to gradually load older comments as the user scrolls down – check out my “Infinite Scrolling in Rails: The Basics” article to learn more.

Adding a Bit of AJAX

This will be the last iteration for today. Let’s add a bit of AJAX to make the comments posting process asynchronous. This way, the page won’t be reloaded every time the user has posted his comment.

If you are using Turbolinks I recommend adding jquery-turbolinks gem to bring back default page load events (because initially Turbolinks has its own events).

Gemfile

[...]
gem 'jquery-turbolinks'
[...]

application.js

[...]
//= require jquery.turbolinks
//= require turbolinks

To equip our form with AJAX, only one modification is needed:

comments/_form.html.erb

<%= form_for @comment, remote: true do |f| %>

The controller’s create method should also be modified to handle AJAX requests:

comments_controller.rb

[...]
def create
  respond_to do |format|
    if current_user
      @comment = current_user.comments.build(comment_params)
      if @comment.save
        flash.now[:success] = 'Your comment was successfully posted!'
      else
        flash.now[:error] = 'Your comment cannot be saved.'
      end
      format.html {redirect_to root_url}
      format.js
    else
      format.html {redirect_to root_url}
      format.js {render nothing: true}
    end
  end
end
[...]

The respond_to method is used here to respond with the correct format: either HTML (if the user has disabled JavaScript in his browser) or JS. A new view is also needs to be created:

comments/create.js.erb

<% if @comment.new_record? %>
  alert('Your comment cannot be saved.');
<% else %>
  $('#comments').find('.media-list').prepend('<%= j render @comment %>');
  $('#comment_body').val('');
<% end %>

We are using a if @comment.new_record? condition to check if the comment was saved. If yes – prepend a new comment to the list of comments (by rendering the _comment.html.erb partial) and clear the textarea. You may extend this functionality a bit by alerting (or displaying in some other way) the list of errors that were found if the comment was not saved.

Go on and try out the new AJAX-powered form. Indeed, it works nicely, but there is still a lot to be done. The main problem is that the user still needs to reload the page to see what others have posted, however in a real chat, she would see the new comments as soon as they are added. So this is our task for next time!

Conclusion

We’ve created a messaging app allowing our users to sign in via one of the social networks and post their comments. We’ve also added a scheduler task to remove excessive comments.

In the next part, we are going to talk about the two possible ways of displaying new comments for the user: AJAX polling and web sockets. We will discuss which solution is better suited for our needs, as well as pinpoint some gotchas.

Frequently Asked Questions about Mini Chat Rails

How can I install Mini Chat Rails on my website?

Installing Mini Chat Rails on your website involves a few steps. First, you need to have Ruby on Rails installed on your system. If you don’t have it, you can download it from the official Ruby on Rails website. Once you have Ruby on Rails installed, you can install Mini Chat Rails by adding it to your Gemfile. Open your Gemfile in a text editor and add the following line: gem ‘mini_chat_rails’. Save the file and run the command ‘bundle install’ in your terminal. This will install Mini Chat Rails and its dependencies. After the installation, you can use Mini Chat Rails in your application by including it in your application’s JavaScript and CSS files.

What are the main features of Mini Chat Rails?

Mini Chat Rails is a powerful chat application built with Ruby on Rails. It offers several features that make it a great choice for adding chat functionality to your website. Some of its main features include real-time messaging, user authentication, message history, and support for multiple chat rooms. It also provides a clean and intuitive user interface that makes it easy for users to navigate and use the chat application.

How does Mini Chat Rails compare to other chat applications?

Mini Chat Rails stands out from other chat applications in several ways. First, it’s built with Ruby on Rails, a popular and powerful web development framework. This means it’s highly customizable and can be easily integrated into any Rails application. Second, it offers real-time messaging, which is a must-have feature for any modern chat application. Third, it provides user authentication, ensuring that only authorized users can access the chat. Lastly, it supports multiple chat rooms, allowing users to participate in different conversations at the same time.

Can I customize the look and feel of Mini Chat Rails?

Yes, you can customize the look and feel of Mini Chat Rails. It’s built with Ruby on Rails, which means you have full control over its appearance. You can change the colors, fonts, and layout to match your website’s design. You can also add custom CSS to further customize its appearance. To do this, simply include your custom CSS in your application’s CSS file.

Is Mini Chat Rails secure?

Yes, Mini Chat Rails is secure. It uses user authentication to ensure that only authorized users can access the chat. This means that users need to sign in before they can use the chat. In addition, all messages sent through Mini Chat Rails are encrypted, ensuring that they can’t be intercepted and read by unauthorized users.

How can I add multiple chat rooms in Mini Chat Rails?

Adding multiple chat rooms in Mini Chat Rails is straightforward. You can create a new chat room by calling the ‘create’ method on the ‘ChatRoom’ model. You can then add users to the chat room by calling the ‘add_user’ method on the ‘ChatRoom’ instance. Users can switch between chat rooms by clicking on the chat room name in the chat room list.

Can I use Mini Chat Rails on a non-Rails website?

Mini Chat Rails is designed to be used with Ruby on Rails applications. However, it’s possible to use it on a non-Rails website with some modifications. You would need to create a Rails API that handles the chat functionality and then use JavaScript to interact with this API from your non-Rails website.

Does Mini Chat Rails support file sharing?

Currently, Mini Chat Rails does not support file sharing. It’s primarily designed for text-based messaging. However, you can extend its functionality to support file sharing by adding a file upload feature. This would involve modifying the ‘Message’ model to include a file attachment and updating the chat interface to allow users to select and send files.

Can I use Mini Chat Rails for group chats?

Yes, you can use Mini Chat Rails for group chats. It supports multiple chat rooms, which can be used for group chats. Each chat room can have multiple users, and users can switch between chat rooms as needed. This makes it a great choice for group chats, whether they’re for work, school, or socializing.

How can I troubleshoot issues with Mini Chat Rails?

If you’re having issues with Mini Chat Rails, there are several steps you can take to troubleshoot them. First, check the Ruby on Rails logs for any error messages. These logs can provide valuable information about what’s causing the issue. Second, make sure you’re using the latest version of Mini Chat Rails. If you’re using an older version, updating to the latest version may resolve the issue. Finally, if you’re still having trouble, you can reach out to the Mini Chat Rails community for help. There are many experienced Rails developers who can provide assistance and advice.

Ilya Bodrov-KrukowskiIlya Bodrov-Krukowski
View Author

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week