Skip to content

Commit 4a2401f

Browse files
committed
add final post in admin panel series
1 parent 9d827cd commit 4a2401f

2 files changed

+309
-1
lines changed

_posts/2012-08-07-rails-admin-panel-from-scratch-2.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ In the first part of this series on creating Rails administration panels, we cre
88

99
As a review, this simple blog-type application allows users to log in, then add, edit, and delete articles. We're working on a namespaced administration section for the site, accessible only to people with accounts, to accomplish this. So far the application has an admin dashboard set up, but no further functionality. That's what we'll tackle in this tutorial. Specifically, we're going to take a controller and views that were previously generated with Rails' `scaffold` generator, move them into the `admin` namespace, and make them work in our admin panel.
1010

11-
We'll also be using RSpec request specs to drive this process. If this is a new process for you, you might want to go through the work we did in part one, or, of course, pick up a copy of my book on learning how to test Rails apps with RSpec. And with that shameless plug out of the way, let's get on with our task. You can [follow along with the complete source on GitHub](https://github.com/ruralocity/admin_demo); the relevant stuff is located in the [scaffold branch](https://github.com/ruralocity/admin_demo/tree/scaffold).
11+
We'll also be using RSpec request specs to drive this process. If this is a new process for you, you might want to go through the work we did in part one, or, of course, [pick up a copy of my book](https://leanpub.com/everydayrailsrspec) on learning how to test Rails apps with RSpec. And with that shameless plug out of the way, let's get on with our task. You can [follow along with the complete source on GitHub](https://github.com/ruralocity/admin_demo); the relevant stuff is located in the [scaffold branch](https://github.com/ruralocity/admin_demo/tree/scaffold).
1212

1313
## A little spec refactoring
1414

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
---
2+
layout: post
3+
title: "Creating a Rails admin panel from scratch, part 3: Other resources"
4+
excerpt: "In the final part of this series, we'll create a fresh controller and views to manage an ActiveAdmin resource within our namespaced admin panel."
5+
---
6+
7+
Over the past couple of weeks I've been sharing a technique for building namespaced administration interfaces into Rails applications. So far in this series we've [created a basic administration dashboard](http://everydayrails.com/2012/07/31/rails-admin-panel-from-scratch.html) and [leveraged Rails scaffolds](http://everydayrails.com/2012/08/07/rails-admin-panel-from-scratch-2.html) to add some functionality to it. In this final post we'll add another round of functionality--this time, for a model that already exists in an application.
8+
9+
The incredibly simple blogging application includes a `User` model to allow authors to log in and create posts. However, so far the application doesn't yet have any functionality to manage those users. We'll add that now, once again following a test-driven approach with RSpec. (As a reminder-slash-shameless plug, if you're interested in a simple way to get going with RSpec and TDD, [check out my book](https://leanpub.com/everydayrailsrspec) on the subject to get started). The [application's source](https://github.com/ruralocity/admin_demo) can be seen in its entirety on GitHub; see the the [resources branch](https://github.com/ruralocity/admin_demo/tree/resource) for code specific to this post.
10+
11+
## Adding a user
12+
13+
Let's get going: We'll start with an interface for adding new users. Here's the request spec we'll make pass:
14+
15+
{% highlight ruby %}
16+
# spec/requests/admin_spec.rb
17+
18+
describe 'user management' do
19+
before :each do
20+
user = FactoryGirl.create(:user)
21+
sign_in user
22+
end
23+
24+
it "adds a user" do
25+
click_link 'Manage Users'
26+
current_path.should eq admin_users_path
27+
28+
expect{
29+
click_link 'New User'
30+
fill_in 'Email', with: '[email protected]'
31+
fill_in 'Password', with: 'secret'
32+
fill_in 'Password confirmation', with: 'secret'
33+
click_button 'Create User'
34+
}.to change(User, :count).by(1)
35+
36+
current_path.should eq admin_users_path
37+
page.should have_content '[email protected]'
38+
end
39+
end
40+
{% endhighlight %}
41+
42+
If you read the previous post in this series, this should look pretty familiar; we'll create a similar interface for user management now. Following test-driven development, our spec fails pretty quickly:
43+
44+
1) site administration user management adds a user
45+
Failure/Error: current_path.should eq admin_users_path
46+
NameError:
47+
undefined local variable or method `admin_users_path' for #<RSpec::Core::ExampleGroup::Nested_4::Nested_3:0x007f83c940d658>
48+
49+
Again, these failures should look pretty familiar by now. In this case we need to add a namespaced route for managing users:
50+
51+
{% highlight ruby %}
52+
# config/routes.rb
53+
54+
namespace :admin do
55+
get '', to: 'dashboard#index', as: '/'
56+
resources :articles
57+
resources :users
58+
end
59+
{% endhighlight %}
60+
61+
The next failure points to the fact that the dashboard we created back in the first post in this series is missing a complete link for managing users.
62+
63+
1) site administration user management adds a user
64+
Failure/Error: current_path.should eq admin_users_path
65+
66+
expected: "/admin/users"
67+
got: "/admin"
68+
69+
(compared using ==)
70+
71+
Easy enough to fix:
72+
73+
{% highlight erb %}
74+
<!-- app/views/admin/dashboard/index.html.erb -->
75+
76+
<h1>Administration</h1>
77+
78+
<ul>
79+
<li><%= link_to 'Manage Users', admin_users_path %></li>
80+
<li><%= link_to 'Manage Articles', admin_articles_path %></li>
81+
</ul>
82+
{% endhighlight %}
83+
84+
Another step forward, and another failed expectation:
85+
86+
1) site administration user management adds a user
87+
Failure/Error: click_link 'Manage Users'
88+
ActionController::RoutingError:
89+
uninitialized constant Admin::UsersController
90+
91+
Looks like we need to add a controller. Unlike the previous post, where we moved existing files to add admin functionality, this time we'll add it from scratch:
92+
93+
{% highlight bash %}
94+
rails generate controller admin/users index
95+
{% endhighlight %}
96+
97+
This creates the controller in `app/controllers/admin`, as well as an ERB template we'll get to next in `app/views/admin/users`. (It also adds the route ` get "users/index"` to `config/routes.rb`; we're not going to use that route so we can delete it.) In fact, the next failure points to something missing in this template:
98+
99+
1) site administration user management adds a user
100+
Failure/Error: click_link 'New User'
101+
Capybara::ElementNotFound:
102+
no link with title, id or text 'New User' found
103+
104+
The view template will eventually include the ability to list existing users, of course, but for now let's just do the minimum amount of work to make this expectation pass:
105+
106+
{% highlight erb %}
107+
<!-- app/views/admin/users/index.html.erb -->
108+
109+
<h1>Listing users</h1>
110+
111+
<%= link_to 'New User', new_admin_user_path %>
112+
{% endhighlight %}
113+
114+
And another failure:
115+
116+
Failures:
117+
118+
1) site administration user management adds a user
119+
Failure/Error: click_link 'New User'
120+
AbstractController::ActionNotFound:
121+
The action 'new' could not be found for Admin::UsersController
122+
123+
No problem--the controller we created a moment ago doesn't have a `new` method, but we can easily add that now.
124+
125+
{% highlight ruby %}
126+
# app/controllers/admin/users_controller.rb
127+
128+
class Admin::UsersController < ApplicationController
129+
def index
130+
end
131+
132+
def new
133+
end
134+
end
135+
{% endhighlight %}
136+
137+
The next failure suggests the method needs a corresponding view:
138+
139+
1) site administration user management adds a user
140+
Failure/Error: click_link 'New User'
141+
ActionView::MissingTemplate:
142+
Missing template admin/users/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in:
143+
* "/Users/asumner/Sites/Rails/admin_demo/app/views"
144+
145+
So let's go ahead and add that--just a blank file at `app/views/admin/users/new.html.erb` is all we need to push the process forward. Now RSpec will complain about not finding the actual form we've told it to expect:
146+
147+
1) site administration user management adds a user
148+
Failure/Error: fill_in 'Email', with: '[email protected]'
149+
Capybara::ElementNotFound:
150+
cannot fill in, no text field, text area or password field with id, name, or label 'Email' found
151+
152+
I'm just going to use the general style of form as Rails' scaffold generators would provide to place the form in the `new.html.erb` template we created a second ago:
153+
154+
{% highlight erb %}
155+
<!-- app/views/admin/users/new.html.erb -->
156+
157+
<h1>New user</h1>
158+
159+
<%= form_for([:admin,@user]) do |f| %>
160+
<% if @user.errors.any? %>
161+
<div id="error_explanation">
162+
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
163+
164+
<ul>
165+
<% @user.errors.full_messages.each do |msg| %>
166+
<li><%= msg %></li>
167+
<% end %>
168+
</ul>
169+
</div>
170+
<% end %>
171+
172+
<div class="field">
173+
<%= f.label :email %><br />
174+
<%= f.text_field :email %>
175+
</div>
176+
<div class="field">
177+
<%= f.label :password %><br />
178+
<%= f.password_field :password %>
179+
</div>
180+
<div class="field">
181+
<%= f.label :password_confirmation %><br />
182+
<%= f.password_field :password_confirmation %>
183+
</div>
184+
<div class="actions">
185+
<%= f.submit %>
186+
</div>
187+
<% end %>
188+
{% endhighlight %}
189+
190+
Which yields a fresh RSpec failure:
191+
192+
1) site administration user management adds a user
193+
Failure/Error: click_link 'New User'
194+
ActionView::Template::Error:
195+
undefined method `model_name' for NilClass:Class
196+
197+
We just need something to pass to `@user` in the form--this is easily remedied in the controller:
198+
199+
{% highlight ruby %}
200+
# app/controllers/admin/users_controller.rb
201+
202+
class Admin::UsersController < ApplicationController
203+
def index
204+
end
205+
206+
def new
207+
@user = User.new
208+
end
209+
end
210+
{% endhighlight %}
211+
212+
Now the spec is looking for a `create` method in the Users controller.
213+
214+
1) site administration user management adds a user
215+
Failure/Error: click_button 'Create User'
216+
AbstractController::ActionNotFound:
217+
The action 'create' could not be found for Admin::UsersController
218+
219+
Again, I'm just going to follow the style this method would have had it been generated by a Rails scaffold.
220+
221+
{% highlight ruby %}
222+
# app/controllers/admin/users_controller.rb
223+
224+
def create
225+
@user = User.new(params[:user])
226+
227+
respond_to do |format|
228+
if @user.save
229+
format.html { redirect_to admin_users_url, notice: 'User was successfully created.' }
230+
format.json { render json: @user, status: :created, location: [:admin,@user] }
231+
else
232+
format.html { render action: "new" }
233+
format.json { render json: @user.errors, status: :unprocessable_entity }
234+
end
235+
end
236+
end
237+
{% endhighlight %}
238+
239+
We're getting really close now, but we've got a couple more things to take care of. First, a new failed expectation:
240+
241+
1) site administration user management adds a user
242+
Failure/Error: page.should have_content '[email protected]'
243+
expected there to be content "[email protected]" in "AdminDemo\n\n Logged in as [email protected].\n Log Out\n\n Listing users\n\nNew User\n\n\n"
244+
245+
Let's go back to the `index` template to add this.
246+
247+
{% highlight erb %}
248+
<!-- app/views/admin/users/index.html.erb -->
249+
250+
<h1>Listing users</h1>
251+
252+
<table>
253+
<tr>
254+
<th>Email</th>
255+
</tr>
256+
257+
<% @users.each do |user| %>
258+
<tr>
259+
<td><%= user.email %></td>
260+
</tr>
261+
<% end %>
262+
</table>
263+
264+
<br />
265+
266+
<%= link_to 'New User', new_admin_user_path %>
267+
{% endhighlight %}
268+
269+
Oops, a fresh failure:
270+
271+
1) site administration user management adds a user
272+
Failure/Error: click_link 'Manage Users'
273+
ActionView::Template::Error:
274+
undefined method `each' for nil:NilClass
275+
276+
Remedied by a quick addition to the controller:
277+
278+
{% highlight ruby %}
279+
# app/controllers/admin/users_controller.rb
280+
281+
def index
282+
@users = User.all
283+
end
284+
{% endhighlight %}
285+
286+
And now the request spec should pass! Blog editors--anyone with an account, basically--can now add more users through the admin panel. As in the previous post, however, we've still got a little more to do. This spec tests the happy path: A known user logs in and enters a user correctly in the form. We've got some other cases to test and potential functionality to add:
287+
288+
1. What happens if someone accesses the form without logging in first?
289+
2. What happens if the password and password confirmation don't match?
290+
3. What happens if an incorrectly-formatted email address is entered?
291+
4. What happens if a duplicate email address is entered?
292+
293+
I'm not going to explicitly go through these here, but you can look at the source on GitHub for more details. Short answers:
294+
295+
1. Test this at the controller and make sure to `authorize` the new controller.
296+
2. The way we've implemented passwords uses the `has_secure_password` functionality built into Rails; it should therefore be adequately tested already without us adding another.
297+
3. Add a custom validation to the `User` model and test at that level. You could get pretty detailed with this if you wish--I included a couple of examples in the full source.
298+
4. This would also be tested at the model level (though since uniqueness validations are built into Rails, one might argue that it's not strictly necessary to test). In the interest of completion, it wouldn't hurt to add a controller-level test to make sure the controller (in this case, the `create` method) properly handles invalid form submissions, too.
299+
300+
## Next steps
301+
302+
That's about it for this exercise. Of course, you may need more functionality for your user management, such as editing existing accounts or suspending them from login. I'll leave that up to you to implement in your own applications.
303+
304+
The interface is also pretty spartan--you might want to spruce it up with something like Twitter Bootstrap, or add filtering functionality with [Ransack](http://rubygems.org/gems/ransack). Again, I'll leave that up to you--the point of this series has been to start with the basics and build up from there.
305+
306+
## Wrapping up
307+
308+
That will wrap up this series on creating administration interfaces for your Rails applications, without a soup-to-nuts solution like ActiveAdmin. We not only built an admin panel--we also looked at how to namespace Rails routes (pretty easily, as it turns out) and how to use RSpec request specs to drive the development. I hope you found this series useful.

0 commit comments

Comments
 (0)