Tuesday, March 25, 2008

Found a nice ActiveRecord Tutorial

Wish I had found this a couple of months ago, it would have moved me along a lot. I still learned a good deal from it though.
Check it out at:


Rails Envy

-Ralph

Monday, March 24, 2008

Legacy MySQL Database - TinyInt

Apparently Rails typecasts MySQL tinyint's as booleans. I was working on a Rails app for a legacy database today when I realized that I couldn't pull the value out of my 'terms' column. All it would read is a true or false, and not the actual value. After a bit of research, I discovered before_type_cast accessor. So instead of using report.terms, in order to get the value out, I had to use report.terms_before_type_cast.


Short post, but I'm sure someone will have the same problem at some point and will find it helpful.



Later.

-Ralph

Friday, March 21, 2008

Tutorial: Beginning AJAX with Rails 2.0

Today, I'm going to go over a quick tutorial of some basic AJAX functions. Rails has a great implementation with AJAX right out of the box, with the Prototype library. It also includes the Scriptaculous library with some special effects. We are just going to do some basic writing text into the site without page reloading. Lets get started.


Set up the application:


 
rails ajaxtest -d mysql
cd ajaxtest
./script/generate model Foobar foo:string bar:string
./script/generate Controller Foobars

This is our first project that doesn't use scaffolding - I decided against it because scaffolding would have generated all kinds of views we aren't going to need for this. Notice that when you created the model, it automatically generated your DB migration file. Now, lets open up app/controllers/foobars_controller.rb and do this:



class FoobarsController < ApplicationController
def index
@foobars = Foobar.find(:all)

respond_to do |format|
format.html # index.html.erb
end
end

end

Next, create app/views/foobars/index.html.erb:



<h3>Foobars!</h3>
<table>
<tr><th>Foo</th><th>Bar</th></tr>
<% for foobar in @foobars do %>
<tr>
<td><%= foobar.foo %></td>
<td><%= foobar.bar %></td>
</tr>
<% end %>
</table>

Now lets add our routing. In config/routes.rb add:


  map.resources :foobars

In your console, lets get our db created and foobars table setup:



rake db:create
rake db:migrate

And one last thing. We need to populate our table with a few foobars. This is a good time to show you the rails interactive console:


./script/console

From here, we can give commands directly to our project. Lets try one:


Foobar.find(:all)


>> Foobar.find(:all)
=> []

So it returned an empty array. The interactive console is great for playing around with bits of code to see what they do or even testing out bits of code. Lets try another command:



>> myfoobar = Foobar.new
=> #<Foobar id: nil, foo: nil, bar: nil, created_at: nil, updated_at: nil>
>>

Creates a new foobar object...



>> myfoobar.foo = "This is a test."
=> "This is a test."
>> myfoobar.bar = "This is only a test."
=> "This is only a test."
>>

We can set the values of foo and bar for our new foobar, and...



>> myfoobar.save
=> true
>>

We can save it to the database. Lets try looking it up with find...



>> Foobar.find(:all)
=> [#<Foobar id: 1, foo: "This is a test.", bar: "This is only a test.", created_at: "2008-03-21 18:24:46", updated_at: "2008-03-21 18:24:46">]
>>

Great - so far so good. Now lets create a few more foobars:



myfoobar = Foobar.new
myfoobar.foo = "Foo of foobar #2"
myfoobar.bar = "Bar of foobar #2"
myfoobar.save

myfoobar = Foobar.new
myfoobar.foo = "Foo of foobar #3"
myfoobar.bar = "Bar of foobar #3"
myfoobar.save

myfoobar = Foobar.new
myfoobar.foo = "Foo of foobar #4"
myfoobar.bar = "Bar of foobar #4"
myfoobar.save

And done. You can type exit to leave the interactive console. Start the server, browse to localhost:PORT/foobars



Tablespoon of AJAX


First thing, I'm going to tell you that if you don't have Firebug yet, go ahead and get it. It is a plugin for Firefox(you are using firefox, right? nothing against you Safari/Opera lovers..if you are using IE you have no excuses, get Firefox and get Firebug...now). Firebug is a great tool for design as well as for AJAX. The inspect mode will highlight divs under your mouse and show the corresponding html/css code in the panel below. You can also take a look at Javascript AJAX responses and more quickly debug your code. Lets create app/views/layouts/application.html.erb:



<html>
<head>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= yield :layout %>
</body>
</html>

The include tag will include (among other things) the prototype library that you need to make the AJAX calls. Now, lets head back to app/views/foobars/index.html.erb and set it up for our AJAX. Change it to this:



<h3>Foobars!</h3>
<table>
<tr>
<th>Foo</th>
<th>Bar</th>
<th>Which?</th>
<th>Links</th>
</tr>
<% for foobar in @foobars do %>
<% idstring = "foobar-" + foobar.id.to_s %>
<tr>
<td><%= foobar.foo %></td>
<td><%= foobar.bar %></td>
<td id="<%= idstring %>"></td>
<td>
<%= link_to_remote "Foo", :url =>{ :action => :set, :updatewith => foobar.foo },
:update => idstring %>
  
<%= link_to_remote "Bar", :url =>{ :action => :set, :updatewith => foobar.bar },
:update => idstring %>
</td>
</tr>
<% end %>
</table>

We have 2 more columns in our table now - Which? will be updated by AJAX with either the foo or the bar of our current Foobar depending on the link clicked from the Links column. In the Links column, we have 2 link_to_remote 's. link_to_remote generates all the javascript calls to send an AJAX request back to our controller on the server. The first argument is what will be displayed for the link. In :url, we define the action to send the request to (:set), as well as the string we want to pass into the function (:updatewith). Last, :update will be the name of the div we will be updating. You can also add another parameter, :position. For example, :position => :before would insert whatever you are sending before the td tag, :after would be after it, :top would insert it inside the td, above anything else, and :bottom inserts it inside the td, at the bottom. If you do not specify a position(like we are doing), it will simply over-write what is inside the div. One more thing to do; lets open up app/controllers/foobars_controller.rb and add in our :set action:



def set
render :text => params[:updatewith]
end

Go back to the page and try clicking the links



Update: I found this link to be very helpful with different ways of using render with AJAX.



And we are done! Thanks for visiting, and see you next time.



Wednesday, March 12, 2008

Using a 'blob' with ActiveScaffold

So I'm using ActiveScaffold to create an admin interface for a site, using a legacy database. It has a table called 'Events', one of the column names is event, of type medium blob, which will only contain text. ActiveScaffold will display the contents of the blob field correctly, but when you go to create a new Event or edit an existing one, it will not give you a text field to enter anything in. Here is the fix:


In app/helpers, I created a file event_helper.rb and put this code in it:



module EventHelper

def event_form_column(record, input_name)
text_field :record, :event, :name => input_name
end

end

And everything worked fine.

Begin Rescue Else End

So today I was working on a project. This project will be an admin interface for an application that has already been running for a while(which was written in PHP). So I am having to use a legacy database(tutorial coming soon for making admin interface for a legacy DB!) Anyway, I have an employee, and a department. The employee references which department he is in by the ID. However, some employees have an ID that is not used in the department table(department got deleted at some point, never existed, who knows?).


In my employee model, I am making a function to take the department id from the employee tables(thisdept), and look up the number in the department table to get the name of the department, all pretty standard stuff.


Here is what I tried first:



def deptname
dept = Department.find(thisdept)
dept.name
end



ActiveRecord::RecordNotFound in Employees#index

Showing vendor/plugins/active_scaffold/frontends/default/views/_list_record.rhtml where line #10 raised:

Couldn't find Department with ID=13


So we are going to need something along the lines of a try - catch. Ruby's equivalent is the begin-rescue-else-end. Begin is the code that might throw an error. Rescue is the code to excute if there is an error. Else is executed if there is no error. You can also add an ensure block, which will be run after everything, regardless. Here is my fix:




def deptname
begin
dept = Department.find(thisdept)
rescue
"No Department"
else
dept.name
end
end

Works like a charm! See you next time.

Monday, March 10, 2008

Rails Forum - Restful Authentication(Part 3 of 3)

So here is part 3 of 3 for my Rails 2.0 Forum Tutorial for Beginners. Today we are going to put authentication on top of our application, using the restful_authentication and acts_as_state_machine plugins.

NOTE: If you don't want to do Parts 1 and 2, you can download the code for them here, and use this as a stand-alone tutorial. Please note that our Topic and Reply models both belongs_to a user.

I got a lot of this stuff from RubyPlus's RESTful Authentication tutorial. Let's get started!


cd myforum

ruby script/plugin install http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk
ruby script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication
ruby script/generate authenticated user sessions --include-activation --stateful


To break down that last command - authenticated is part of the restful_authentication. user is the model to be generated for the user. sessions will be the controller to handle logging in/out, --include-activation will help with creating the stuff needed to mail an activation link to the user's email, and --stateful is for support for acts_as_state_machine plugin.


Now that our files are generated, let's head to /config/routes.rb and make a couple of changes. Notice that the map.resources :users and map.resources :session lines have already been generated by the generate authenticated call. The version of restful_authentication used in the tutorial on RubyPlus did not automatically generate them.


So in routes.rb:



#CHANGE THIS LINE
map.resources :users

#TO THIS
map.resources :users, :member => { :suspend => :put,
:unsuspend => :put,
:purge => :delete }

#AND THEN ADD THE FOLLOWING LINES
map.activate '/activate/:activation_code', :controller => 'users',
:action => 'activate'
map.signup '/signup', :controller => 'users', :action => 'new'
map.login '/login', :controller => 'sessions', :action => 'new'
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
map.forgot_password '/forgot_password', :controller => 'users',
:action => 'forgot_password'
map.reset_password '/reset_password/:id', :controller => 'users',
:action => 'reset_password'

The :member => part sets up some restful actions for the users controller. All the lines that we added sets up named routes for user and session controller/action pairs.


Now, open up /config/environment.rb:



#BELOW THE LINE
Rails::Initializer.run do |config|

#ADD THIS
config.active_record.observers = :user_observer

The observer watches the user model and will be used to send an EMail whenever a new user is created. Next, lets open up /db/migrate/004_create_users.rb and do this.



#OLD 004_create_users.rb
class CreateUsers < ActiveRecord::Migration
def self.up
create_table "users", :force => true do |t|
t.column :login, :string
t.column :email, :string
t.column :crypted_password, :string, :limit => 40
t.column :salt, :string, :limit => 40
t.column :created_at, :datetime
t.column :updated_at, :datetime
t.column :remember_token, :string
t.column :remember_token_expires_at, :datetime
t.column :activation_code, :string, :limit => 40
t.column :activated_at, :datetime
t.column :state, :string, :null => :no, :default => 'passive'
t.column :deleted_at, :datetime
end
end

def self.down
drop_table "users"
end
end

#NEW 004_create_users.rb
class CreateUsers < ActiveRecord::Migration
def self.up
create_table "users", :force => true do |t|
t.string :login, :email, :remember_token
t.string :crypted_password, :limit => 40
t.string :password_reset_code, :limit => 40
t.string :salt, :limit => 40
t.string :activation_code, :limit => 40
t.datetime :remember_token_expires_at, :activated_at, :deleted_at
t.string :state, :null => :no, :default => 'passive'

t.timestamps
end
end

def self.down
drop_table "users"
end
end


There we are just cleaning up the code a little bit. We did add a :password_reset_code column - will be used if a user forgets their password(more on that later). Now just run the migration to create our new tables...


rake db:migrate

Next, open up app/controllers/sessions_controller and copy the line include AuthenticatedSystem to /app/controllers/application.rb. Also, in users_controller, remove the same line. This will make sure that the restful_authentication functions are included on every page.


Now, remove these lines from users_controller in the create function:



self.current_user = @user
redirect_back_or_default('/')

We removed the first line because we do not want to log the user in when they register, we want to verify their EMail. The second line was removed so that Rails would look for create.html.erb after running the create action. Now, create a file /app/views/users/create.html.erb and put something like this in it:



<fieldset>
<legend>Your forum account</legend>

<p>
An EMail has been sent to <%= @user.email %> with instructions to activate
your account.
<li>
If your email is not valid, you must <%= link_to "signup", signup_path %>
again and provide a valid email address.
</li>
<li>
If you don't recieve an email, check your bulk or trash folder, as your spam
filter may have inadvertantly caught the registration email.
</li>
</p>
</fieldset>

Also, create the files app/views/users/change_password.html.erb, app/views/users/forgot_password.html.erb, app/views/users/reset_password.html.erb:



#change_password.html.erb

<% form_for :action => 'change_password' do |f| %>
<fieldset>
<dl>
<dt><label for="old_password" class="block">Old Password</label></dt>
<dd><%= password_field_tag 'old_password', @old_password, :size => 45, :class => 'text' %></dd>

<dt><label for="password" class="block">New Password</label></dt>
<dd><%= password_field_tag 'password', {}, :size => 45, :class => 'text' %>
<div><small>Between 4 and 40 characters</small></div></dd>

<dt><label for="password_confirmation" class="block">Confirm new password</label></dt>
<dd><%= password_field_tag 'password_confirmation', {}, :size => 45, :class => 'text' %></dd>

</dl>
<%= submit_tag 'Change password' %>
</fieldset>
<%end%>

#forgot_password.html.erb

<% form_for :user, :url => {:action => 'forgot_password'} do |form| %>
<fieldset>
<legend>Password Reset Request</legend>
<p>Enter your email address that we have on our file and click send. We will send you a
password reset link email to your email address.</p>
<p>
<label for="user_email" >Email Address:</label><br/>
<%= form.text_field :email, :size => 35, :class => 'text' %>
</p>
<%= submit_tag 'Send' %>
</fieldset>
<% end %>

#reset_password.html.erb

<% form_for :user, :url => {:action => "reset_password"} do |form| %>
<fieldset>
<legend>Reset Password</legend>

<p>
<label for="user_password" >Password</label><br/>
<%= form.password_field :password, :size => 45, :class => 'text' %>
</p>
<p>
<label for="user_password_confirmation" >Confirm Password</label><br/>
<%= form.password_field :password_confirmation, :size => 45, :class => 'text' %>
</p>
<%= submit_tag "Reset your password" %>
</fieldset>
<% end %>
</pre>
<p>Standard stuff there.</p>
<p>Now, open up app/helpers/application_helper.rb and add this code:</p>
<pre>
def user_logged_in?
session[:user_id]
end

We can use this in our views to see if the user is logged in.


Create app/views/layouts/application.html.erb:



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-US">

<head>
<title><%= @page_title || 'Rails 2.0 Forum' %></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<%= stylesheet_link_tag 'style' %>

<%= javascript_include_tag :defaults %>
</head>

<body>

<!-- ##### Header ##### -->
<div id="header">
<div class="superHeader">
<% if user_logged_in? %>
<span>You are logged in as: </span>
<%= current_user.login %>
<% else %>
<span>Welcome Guest</span>
<% end %>
</div>

<div class="midHeader">
<h1 class="headerTitle" lang="la">Rails 2.0 Forum</h1>
</div>

<div class="subHeader">
<span class="doNotDisplay">Navigation:</span>
<% if user_logged_in? %>
<%= link_to 'Logout', logout_url %>
<% else %>
<%= link_to "Signup", signup_url %>
| <%= link_to 'Login', login_url %>
<% end %>
| <%= link_to 'Contact Us', 'contact' %>
</div>

</div>

<!-- ##### Main Copy ##### -->

<div id="main-copy">
<% flash.each do |key,value| %>
<div id="flash" class="flash_<%= key %>" >
<span class="message"><%= value %></span>
</div>
<% end -%>

<%= yield :layout %>
</div>

</body>
</html>

And then delete forums.html.erb, replies.html.erb, and topics.html.erb so that only the application.html.erb will show.



Lets go ahead and get some different styling in this. Download this stylesheet and toss it in /public/stylesheets.


Alright! Now we should be able to fire up the server and have a look at what we've got.














Next, open up app/views/sessions/new.html.erb. Uncomment the 'Remember me' section, and then next to the submit button, add:


<%= link_to 'Forgot Password?', forgot_password_url %>

Create /app/views/users/change_password.html.erb:



<% form_for :action => 'change_password' do |f| %>
<fieldset>
<dl>
<dt><label for="old_password" class="block">Old Password</label></dt>
<dd><%= password_field_tag 'old_password', @old_password, :size => 45,
:class => 'text' %></dd>

<dt><label for="password" class="block">New Password</label></dt>
<dd><%= password_field_tag 'password', {}, :size => 45, :class => 'text' %>
<div><small>Between 4 and 40 characters</small></div></dd>

<dt><label for="password_confirmation" class="block">
Confirm new password</label></dt>
<dd><%= password_field_tag 'password_confirmation', {}, :size => 45,
:class => 'text' %></dd>

</dl>
<%= submit_tag 'Change password' %>
</fieldset>
<%end%>

Create /app/views/users/forgot_password.html.erb



<% form_for :user, :url => {:action => 'forgot_password'} do |form| %>
<fieldset>
<legend>Password Reset Request</legend>
<p>Enter your email address that we have on our file and click send. We will
send you a password reset link email to your email address.</p>
<p>
<label for="user_email" >Email Address:</label><br/>
<%= form.text_field :email, :size => 35, :class => 'text' %>
</p>
<%= submit_tag 'Send' %>
</fieldset>
<% end %>

And create /app/views/users/reset_password.html.erb



<% form_for :user, :url => {:action => "reset_password"} do |form| %>
<fieldset>
<legend>Reset Password</legend>

<p>
<label for="user_password" >Password</label><br/>
<%= form.password_field :password, :size => 45, :class => 'text' %>
</p>
<p>
<label for="user_password_confirmation" >Confirm Password</label><br/>
<%= form.password_field :password_confirmation, :size => 45, :class => 'text' %>
</p>
<%= submit_tag "Reset your password" %>
</fieldset>
<% end %>

We are moving kind of fast here, but most of this stuff should make sense to you, you just need to see it in action. If you have any questions over how anything works, leave a comment and I will get back to you.


Now, in app/controllers/users_controller.rb, lets add in the actions for forgot_password, reset_password, and change_password:



def change_password
return unless request.post?
if User.authenticate(current_user.login, params[:old_password])
if ((params[:password] == params[:password_confirmation]) &&
!params[:password_confirmation].blank?)
current_user.password_confirmation = params[:password_confirmation]
current_user.password = params[:password]

if current_user.save
flash[:notice] = "Password successfully updated"
redirect_to profile_url(current_user.login)
else
flash[:alert] = "Password not changed"
end

else
flash[:alert] = "New Password mismatch"
@old_password = params[:old_password]
end
else
flash[:alert] = "Old password incorrect"
end
end

#gain email address
def forgot_password
return unless request.post?
if @user = User.find_by_email(params[:user][:email])
@user.forgot_password
@user.save
redirect_back_or_default('/')
flash[:notice] = "A password reset link has been sent to your email address"
else
flash[:alert] = "Could not find a user with that email address"
end
end

#reset password
def reset_password
@user = User.find_by_password_reset_code(params[:id])
return if @user unless params[:user]

if ((params[:user][:password] && params[:user][:password_confirmation]) &&
!params[:user][:password_confirmation].blank?)
self.current_user = @user #for the next two lines to work
current_user.password_confirmation = params[:user][:password_confirmation]
current_user.password = params[:user][:password]
@user.reset_password
flash[:notice] = current_user.save ? "Password reset success." : "Password reset failed."
redirect_back_or_default('/')
else
flash[:alert] = "Password mismatch"
end
end


In config/environments/development.rb you need to add SITE="http://localhost:3000" at the bottom, we will get to this in just a sec.


Now we will take a look at the user_mailer views. They are used to format emails sent out to users. In app/views/user_mailer/activation.html.erb, remove the part where it says "You may now start adding your plugins:". Now lets create app/views/user_mailer/forgot_password.html.erb, and app/views/user_mailer/reset_password.html.erb:



#forgot_password.html.erb

Dear <%= @user.login %>,

We have had a request to reset your password, please visit
<%= @url %>

#reset_password.html.erb

Your have reset the password for your account successfully.

Username: <%= @user.login %>
Password: <%= @user.password %>

Now lets edit app/models/user.rb. Near the bottom, somewhere after the protected keyword, add the follow function:



def make_password_reset_code
self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end

And then above protected, add these functions:



def forgot_password
@forgotten_password = true
self.make_password_reset_code
end

def reset_password
# First update the password_reset_code before setting the
# reset_password flag to avoid duplicate email notifications.
update_attributes(:password_reset_code => nil)
@reset_password = true
end

#used in user_observer
def recently_forgot_password?
@forgotten_password
end

def recently_reset_password?
@reset_password
end

def recently_activated?
@recent_active
end



And now we need to set up the user_mailer model to use all of this. Change app/models/user_mailer.rb to look like this:



class UserMailer < ActionMailer::Base
def signup_notification(user)
setup_email(user)
@subject += 'Please activate your new account'

@body[:url] = "#{SITE}/activate/#{user.activation_code}"

end

def activation(user)
setup_email(user)
@subject += 'Your account has been activated!'
@body[:url] = "#{SITE}/"
end

def forgot_password(user)
setup_email(user)
@subject += 'You have requested to change your password'
@body[:url] = "#{SITE}/reset_password/#{user.password_reset_code}"
end

def reset_password(user)
setup_email(user)
@subject += 'Your password has been reset.'
end

protected

def setup_email(user)
recipients "#{user.email}"
from %("Rails Forum Admin" ) # Sets the User FROM Name and Email
subject "[Rails Forum] New account information "
body :user => user
sent_on Time.now
end
end

Note where we used the SITE constant that we set in development.rb earlier. Lets stop the server and then restart it since we changed the development.rb. Now, we should be able to go back and sign up.










If you check the console running your server, you will see SQL queries adding the new user to the database, and the email being sent(it won't actually send an email since we are in development)






Go to think link from the console(localhost:3000/activate/whatever) and you will get a message saying 'Signup complete' and you will be logged in. It will also send an email saying that your account has been activated. You can test everything by logging out and logging back in again.


Alright, we are almost done! Next, open up app/models/user_observer.rb and change the after_save function to this:



def after_save(user)
UserMailer.deliver_activation(user) if user.recently_activated?
UserMailer.deliver_forgot_password(user) if user.recently_forgot_password?
UserMailer.deliver_reset_password(user) if user.recently_reset_password?
end

Just more setting up for the mailer.


So, now we have a Signup, a few ways for taking care of forgotten passwords, and email authentication. But how do we block the adding posts and replies to only people logged in? How do we restrict creating forums and deleting posts to only an admin? Open up the file vendor/plugins/restful_authentication/generators/authenticated/templates/authenticated_system.rb and read through some of the comments about the functions:



# Returns true or false if the <%= file_name %> is logged in.
# Preloads @current_<%= file_name %> with the <%= file_name %> model if they're logged in.
def logged_in?
current_<%= file_name %> != :false
end

# Accesses the current <%= file_name %> from the session. Set it to :false if login fails
# so that future calls do not hit the database.
def current_<%= file_name %>
@current_<%= file_name %> ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
end

logged_in? checks the value of current_user and returns true if it does not equal :false. current_user tries to login from a session, from basic auth, and from a cookie. If it is unable to log them in, it will return :false and cause the value of logged_in to be false.




# Check if the <%= file_name %> is authorized
#
# Override this method in your controllers if you want to restrict access
# to only a few actions or if you want to check if the <%= file_name %>
# has the correct rights.
#
# Example:
#
# # only allow nonbobs
# def authorized?
# current_<%= file_name %>.login != "bob"
# end
def authorized?
logged_in?
end

# Filter method to enforce a login requirement.
#
# To require logins for all actions, use this in your controllers:
#
# before_filter :login_required
#
# To require logins for specific actions, use this in your controllers:
#
# before_filter :login_required, :only => [ :edit, :update ]
#
# To skip this in a subclassed controller:
#
# skip_before_filter :login_required
#
def login_required
authorized? || access_denied
end

We can use before_filter :login_required to stop people from going to /topic/1/posts/2/edit and being able to edit the post. logged_in? can be used in forum and topic's show views, to stop the form for new topics and replies from being shown. This can all be setup however you want, there are plenty of possibilities. What I am going to do is show you how to display different things based on who is logged in, and how to limit certain actions in your controllers. That should give you enough to figure out what you want to do for your specific application. First, open up app/views/forums/show.html.erb:



#find these 3 lines

<h2>New Post</h2>
<%= render :partial => @topic = Topic.new, :locals => { :button_name => 'Create' } %>
<%= link_to 'New Topic', new_forum_topic_path(@forum) %> |

#and put this around them

<% if logged_in? %>
<h2>New Post</h2>
<%= render :partial => @topic = Topic.new, :locals => { :button_name => 'Create' } %>
<%= link_to 'New Topic', new_forum_topic_path(@forum) %> |
<%end>

#and then change this line
<%= link_to 'Edit', edit_forum_path(@forum) %> |

#to this
<% if is_admin? %>
<%= link_to 'Edit', edit_forum_path(@forum) %> |
<% end %>

And now, go to app/controllers/application.rb. In this file we can write functions that will be available to all controllers. Add this to it:



helper_method :is_admin?
def is_admin?
if logged_in? && current_user.login == "admin"
true
else
false
end
end

def admin_required
is_admin? || admin_denied
end

def admin_denied
respond_to do |format|
format.html do
store_location
flash[:notice] = 'You must be an admin to do that.'
redirect_to forums_path
end
end
end

The first line, helper_method :is_admin? will let this function be used in the view. current_user.login is the username for the person logged in. You can change this method however you want to determine whether or not a user is an admin. admin_required and admin_denied will be used in our controllers to restrict access to actions - lets go to our app/controllers/forums_controller.rb and put that in:



#at the very top, right after class ForumsController < ApplicationController, add this line:

before_filter :admin_required, :only => [ :edit, :update, :create, :new, :destroy ]

Now, when you try to access edit, update, create, new, or destroy actions in your controller, if your username is not 'admin', it will go to the show forums page and have a flash saying 'You must be an admin to do that'.


Well, I guess that is it for now. We've added the ability to create users, reset passwords, verify email, and restrict actions to admins. This isn't a completely finished web app. We didn't look into any kind of input validation and we didn't go over all the things we would need to limit to users logged in(create post and create reply), and we didn't limit a lot of things to admins that we should have(such as editing and destroying posts/replies). Hopefully, you've learned enough from this to be able to figure it out yourself, but if you have any trouble feel free to leave a comment. Also, let me know if there are any typos, mistakes, or questions about the tutorial, as well if you have any suggestions. Thanks for reading!
-Ralph