Advertisement
  1. Code
  2. Ruby

Non-ActiveRecord Models in Rails 4

Scroll to top
Read Time: 9 min

ActiveRecord comes with a powerful set of validators and other features for attributes of a persisted data model. On other hand, forms are one of the oldest and most important building blocks of today’s web applications—an essential interface for user input. Of the two form helpers that Rails provides, "form_for" also assumes that you’re working with some kind of persisted object. So it can take full benefit of all active record features, i.e. validations.

This is all great for persisted objects with database-backed representations. But what happens when you need a complex form that does not reflect a persistent record of some sort?

In this tutorial I'll be talking about the possible solutions for this problem and how to implement one in Rails 4 (Active Models).

In this tutorial, we'll build an application with a form where a user can add some feedback which is then saved into a database. This application will also have validations and views, exactly the way you create them for a database-backed model, but then we'll go through some of the changes in the model to make it tableless. And all the features must be working as they are, without making any further changes. There are no Update, Delete or Find actions for feedback.

First Things First

For this tutorial, I assume that you have a basic understating of the Rails framework and can easily create or generate basic controllers, models, and views. I assume you also know a bit about how routes and validations work. At the time of writing this tutorial, I was using Rails 4.2.5 and SQLite 3.8.10.2.

Introduction

There can be many situations when you have a class that you want to function like a typical ActiveRecord model, but you don't want to persist the data into the database. For example, you may have a contact form or something more complex like a complaint or feedback form. 

In those situations, to solve this problem one approach would be to use the form_tag helper method, which provides you with custom form fields that do what you need without having to bind them with any model at all.

This works well, but form_tag can quickly become tedious to write and maintain if handling more than a few fields because of the need to handle naming its numerous attributes and their validations on your own. Soon your controller will end up dealing with a lot of logic and tons of form params, which is likely not an ideal solution.

A cleaner, more flexible approach would be if we could somehow use the same form_for with a model and all the validations and other perks they come with, but without needing to have database-backed representations of its attributes.

Rails offers exactly this kind of solution: the Active Model—which is just like a normal Model but without the tables. It provides exactly the same easy way of validation and almost all the other goodies shipped with ActiveRecord. It helps you keep the application structure consistent, because you’re using models to represent objects in your app anyway, routes are available for free, and form building is as easy as it was before with form_for.

Let's Create a New Application First

In this step we'll generate a dummy application to play around with during this tutorial.

Step 1: Building

Start up your terminal and type these commands to create a new application:

1
# Create a basic Rails App

2
rails new tableless
3
cd tableless
4
5
# Create a Controller with only new, create & success Actions

6
rails generate controller feedbacks new create success --skip-routes
7
8
# Create a Model

9
rails generate model feedback name:string email:string address:string message:text suggestion:text

This is how your Directory Structure will look.

Directory StructureDirectory StructureDirectory Structure

Step 2: Editing

Here I'll provide the snippets of code for all the files you need to fill. The code is pretty self-explanatory. You can either download this app from the GitHub repository linked to this post or follow my steps to create one by yourself.

→ /config/routes.rb

1
resources :feedbacks, :only => [:new, :create]
2
get 'feedbacks/success' => 'feedbacks#success', as: :success

→ /app/views/feedbacks/success.html.erb

1
<h1 id="notice"><%= notice %></h1>
2
<br>
3
<%= link_to 'Submit New Feedback', new_feedback_path %>

→ /app/views/feedbacks/new.html.erb 

1
<h1>New Feedback</h1>
2
3
4
<%= form_for(@feedback) do |f| %>
5
 <% if @feedback.errors.any? %>
6
   <div id="error_explanation">
7
     <h2><%= pluralize(@feedback.errors.count, "error") %> prohibited this feedback from being saved:</h2>
8
     <ul>
9
     <% @feedback.errors.full_messages.each do |message| %>
10
       <li><%= message %></li>
11
     <% end %>
12
     </ul>
13
   </div>
14
 <% end %>
15
16
17
 <div class="field">
18
   <%= f.label :name %><br>
19
   <%= f.text_field :name %>
20
 </div>
21
22
 <div class="field">
23
   <%= f.label :email %><br>
24
   <%= f.text_field :email %>
25
 </div>
26
27
 <div class="field">
28
   <%= f.label :address %><br>
29
   <%= f.text_field :address %>
30
 </div>
31
32
 <div class="field">
33
   <%= f.label :message %><br>
34
   <%= f.text_area :message %>
35
 </div>
36
37
 <div class="field">
38
   <%= f.label :suggestion %><br>
39
   <%= f.text_area :suggestion %>
40
 </div>
41
42
 <div class="actions">
43
   <%= f.submit %>
44
 </div>
45
46
<% end %>
47
48
<%= link_to 'Back', feedbacks_path %>

→ /app/controllers/feedbacks_controller.rb

1
class FeedbacksController < ApplicationController
2
3
def new
4
    @feedback = Feedback.new
5
end
6
7
def create
8
	@feedback = Feedback.new(feedback_params)
9
	respond_to do |format|
10
		if @feedback.save
11
			format.html { redirect_to success_path, notice: 'Feedback was successfully submitted.' }
12
		else
13
			format.html { render :new }
14
		end
15
	end
16
 end
17
18
 def success
19
 end
20
21
private
22
23
   def feedback_params
24
     params.require(:feedback).permit(:name, :email, :address, :message, :suggestion)
25
   end
26
27
end

→ /app/models/feedbacks.rb

1
class Feedback < ActiveRecord::Base
2
3
    # fields validation for the database.
4
	validates :name, presence: true
5
	validates :email, presence: true, length: {in:5..255}
6
	validates :address, presence: true
7
	validates :message, presence: true
8
	validates :suggestion, presence: true
9
10
end

Step 3: Deploying

To deploy it on your local server, first you need to run the following commands to create the database in your system.

1
cd tableless/
2
rake db:migrate

If you have followed the tutorial this far, the command above should create an sqlite3 database by default. To change it, you can jump to database.ymlfor the sake of this tutorial, I’ll go with sqlite3.

Now run rails s in your terminal and you should see something similar to this.

console logconsole logconsole log

And with this you should be successfully running a dummy application.

Step 4: Testing

Now it's time to test what we just created. Hit this route in your browser to check if everything works well: http://localhost:3000/feedbacks/new

The Feedback PageThe Feedback PageThe Feedback Page

You should see a form like above. Now press the submit button without filling out any field to check if the validations are working fine.

The Feedback ErrorsThe Feedback ErrorsThe Feedback Errors

Great. You should see six validation errors, as above. Now we can try filling in proper values and submit the form.

A successful feedbackA successful feedbackA successful feedback

You should see something similar on your screen. Let's check the database for the record we just entered. 

Open up your Terminal, go to your project directory, and type the commands below.

  • rails db to start the database client in your console.
  • SQLite> .tables to list all the tables in your database (DB is selected by default).
  • SQLite> .headers on to display the Column Names in your results.
  • SQLite> select * from feedbacks; to see all the feedback in the database.
Viewing all of the feedback in the databaseViewing all of the feedback in the databaseViewing all of the feedback in the database

And here we can see the feedback was successfully saved in the database. If you look into the logs, you can also find the INSERT query.

Viewing the database logViewing the database logViewing the database log

And with this our testing is over. Now that everything seems to be working fine, let's jump in to the solution.

The Solution

Step 1: Implementation 

To implement Active Model, the first thing you need to do is remove the feedback model's inheritance to < ActiveRecord::Base as we don’t want this model to have a database back-end anymore. 

As soon as we do this, our form will no longer work, as the validators are provided by ActiveRecord. But adding include ActiveModel::Model on the next line should restore everything.

Your model should look like this now.

1
class Feedback 
2
    include ActiveModel::Model
3
    

The second thing is to add attr_accessor to generate the getters and setters for all the attributes, like this.

1
attr_accessor :name, :email, :address, :message, :suggestion

Now the end result of the model should look like this.

1
class Feedback 
2
3
    include ActiveModel::Model
4
    attr_accessor :name, :email, :address, :message, :suggestion
5
6
    # fields validation for the database.
7
    validates :name, presence: true
8
    validates :email, presence: true, length: {in:5..255}
9
	validates :address, presence: true
10
	validates :message, presence: true
11
	validates :suggestion, presence: true
12
13
end

Fixing the model isn’t enough to get our app to behave as we want. The controller still expects to save the received data object into the database in the create method. @feedback.save won’t work as we don’t have a database back end to save the new feedback anymore.

We can fix this issue by changing @feedback.save into @feedback.valid? since we are only performing the validations in our models now, and based on this success event you can perform any preferred task within this block of code, i.e. send notifications, send email or log events, etc.

1
class FeedbacksController < ApplicationController
2
3
def create
4
    @feedback = Feedback.new(feedback_params)
5
	respond_to do |format|
6
		if @feedback.valid?
7
            
8
            # Something interesting can be done here 
9
            # - send notifications
10
            # - send email
11
            # - log events
12
            
13
            format.html { redirect_to success_path, notice: 'Feedback was successfully submitted.' }
14
		
15
        else
16
			
17
            format.html { render :new }
18
		
19
        end
20
	end
21
 end

Step 2: Testing

Let's redo the tests we performed earlier.

Hit the route http://localhost:3000/feedbacks/new and submit the form without filling out any fields. All the validations should work like earlier.

Testing feedback submissionTesting feedback submissionTesting feedback submission

Great. Now we can try by submitting the form with valid values.

Submitting the form with valid valuesSubmitting the form with valid valuesSubmitting the form with valid values

And here you go—the same success message.

A successful feedback submissionA successful feedback submissionA successful feedback submission

Now the one last thing that we need to check is the database. 

For that, open up your Terminal, go to your project directory, and type the commands below.

  • rails db to start the database client in your console.
  • SQLite> .tables to list all the tables in your database (DB is selected by default).
  • SQLite> .headers on to display the Column Names in your results.
  • SQLite> select * from feedbacks to see all the feedback in the database.

And this time, since our model is not backed with any database table, you won't find the newly submitted values in the table.

No values in the databaseNo values in the databaseNo values in the database

If you check your console logs, we also don't see the INSERT query anymore.

The console logsThe console logsThe console logs

Conclusion

So with this we are done with the ActiveModel, and we saw how easy it is to create a table-less model. ActiveModel is into heavy improvements so you can expect some changes in coming versions of Rails. 

We just used the validations and attribute assignments in this tutorial in order to keep things simple and clear. But take a look in the directory that contains the code for ActiveModel on GitHub.

ActiveModel code on GitHubActiveModel code on GitHubActiveModel code on GitHub

We can see from the list that ActiveModel also includes classes for attribute methods, serialization, callbacks, and dirty tracking, among other things. This way you can keep an eye on upcoming features and also get familiar with others.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.