Friday, August 15, 2008

Juggernaut Push Server


Juggernaut is a Rails plugin that allows you to push information out to the client, without them having to specifically request it. For certain domains, a push server is an excellent solution. Juggernaut uses a small bit of Flash to open up a flashxmlsocket from the browser to the push server, and it subscribes to the service. My roommate Taelor has been playing around with it for a little while, and has put together a chat application that creates rooms off of Digg topics. Its called Shovel Chat. He has also made a few blog posts about Juggernaut and using it to create a chatroom with rails at his blog, and has posted the source code on GitHub(note that you will need to go through his blog posts and edit some of the gem's files...)


So, the way the chat app works is that when you request the show action for a chatroom, some flash gets sent to subscribe to the channel on Juggernaut. Juggernaut will then send you a list of all the people in the room. If you type in a message and hit enter, the message gets posted to your Rails app. Inside the controller, the message gets sent to the Juggernaut server, which then pushes the message out to anyone subscribe to that channel. You could achieve a similar effect using AJAX, but you would have to have each client polling the server for updates to the chatroom, but this would be really difficult to scale.


Anyway, I've gotten his source code installed on my machine and have begun playing around with it, and hope to start contributing to his chat_sandbox on github soon. Feel free to make your own fork of his code on github and start contributing, and stay tuned for more updates!

-Ralph

Tuesday, August 12, 2008

The Ruby Hoedown

Last Thursday, my roommate and I packed and headed 3 hours south to the Ruby Hoedown in Huntsville, AL. It was held at the Shelby Center at UAH on Friday and Saturday. This was my first programming conference, so I thought I might jot down a little bit about how it went.


The first presentation was by the guys at Rails Envy. They talked about innovation in Ruby over the past year. I'm a big fan of these guys' podcasts and tutorials, and they gave a great presentation.


Next up was Robert Dempsey, who talked about Cloud Computing with Rails. He made a great argument against all the folks who say that Rails can't scale. He gave us very detailed explanations of what a Cloud is, and what scalability is. He showed us what it takes to use Amazon Web Services to scale a site up, and then gave us a brief descriptions of different utilities out there for scaling with a cloud: Amazon Web Services, Heroku, Joyent, Accelerator, Morph. One particularly intresting platform was Vertebra, which is being developed by the folks at Engineyard.


Vertebra is a "Next Generation Cloud Computing/Automation Framework". Ezra Zygmuntowicz has some slides posted at his blog. The backbone of the system runs on horizontally scalable erlang XMPP servers. XMPP is an IM/chat protocol this is very efficient. Ezra posted this tidbit on his blog: "Just to answer the question about Vertebra being open source or not. Yes vertebra will be open source, the ruby framework, the protocols and security stuff will all be open source. We may go with a commercial license on the workflow engine as that is a large piece of engineering but we have not decided yet. We want to get this out there and see what people do with it as I think there are limitless possibilities here. But we need to lock down the protocol and document everything and we are still experimenting with different parts of the system. I'd hope to have something to release in 4-6 weeks." Yehuda Katz gave a talk about Vertebra on saturday, and I had a chance to chat with him for a few minutes. He mentioned that some of the folks from Heroku were helping out on the project.


Jim Weirich and Joe O'Brien gave a great talk about mocking. This was an interesting talk because they acted it all out like they were 2 developers working on different problems that involved using mocks. The first one involved using a mock object to log in during testing, and in another one, they showed how you can refactor code so that you will be able to use a mock with it. I chatted with these guys at lunch on Saturday for a while.


Rein Henrich gave a very entertaining talk on Ruby Best Practice Patterns. He started off with a comical presentation on code unfactoring. He showed us different methods of making your code so unreadable that you are the only person able to reasonably maintain it. "Job security through code obscurity." Some of the methods he showed us were: un-DRYing your code; naming methods and variables in pig latin; and taking code that is abstracted into several small functions, and dumping it all into one huge one. After the talk on code unfactoring, he went on into his real presentation on best practice patterns. One of my favorites was the execute around method.


Some other notable talks at the conference included: bryanl(blog smartic.us), who talked about why you should "Test all the fucking time"; the truthy gem; Obie Fernandez's great talk on running a successful company; keynote talks by Chris Wanstrath and David Black; Troy Davis did a presentation on Adhearsion, a ruby framework for making and recieving phone calls; Giles Bowkett used Ruby and his program Archaeopteryx, to create music with code; Being a guitar player, Giles presentation was very interesting to me..unfortunately, Archaeopteryx is only availible for the Mac right now.


I had a great time at the conference. It was cool to be around so many other Ruby enthusiasts, and everybody was really nice. I definitely plan on going next year. Jeremy mentioned that he is planning on holding the Hoedown at Opryland Hotel in Nashville(only an hour away!), and that he hopes to get enough sponsors to host it for free. Even if it isn't free next year, I will gladly pay out another $200 to go again. If you've never been to a conference before, I highly recommend it. It was a very gratifying experience.


Well, thats all for now, I know I promised a post about my Google Summer of Code project, but I don't have it all together yet. If you are interested, it is a c-extension to speed up some of Ruby's CGI functions. You can check out the repo on github here. Farewell until next time!



Edit: Also, you can get videos of all the talks at ConFreaks. I spent a while chatting with both of the guys there, and one of them let me plug in my laptop to their powerstrip for the majority of the conference(I need a new battery...).

Sunday, July 27, 2008

Been a little while...

Hi all, thought I would make a post and update things. I've been busy working on a couple of different gigs: My Place Media, and the Google Summer of Code. Anyway, My Place Media just ran out of money(lots of bad decisions by the guy calling the shots), so I've got a lot more time to concentrate on my GSoC project.


My GSoC project is to working on extending parts of Ruby's CGI class in C (mainly the query string parsing and multipart form parsing). My mentor for the project is Pratik Naik, a Rails Core member. I'll have some more info on how the project is coming along in another post.


Anyway, I've got some ideas for expanding RailsonEdge out a little. The first is that I've purchsed the domain name railsonedge.com, and I have a shared host server. What I would like to do is to use my server to provide hosting for a few open source Rails apps, and let the developers of them either post to the blog, or just put in a feed from their blog. So if there is anyone out there that would like free hosting for your Rails project, to show it to the world, shoot me an email at rledge21 -at- gmail -dot- com, and tell me a little about yourself and your project.


I'm planning on posting some new tutorials very soon (maybe even doing a screencast, but I've not decided whether my voice would scare everybody off or not), and putting up a code snippet repository (similar to snippets.dzone.com)


Anyway, that is what is going on. Expect some posts over the next couple weeks with a couple tutorials, as well as an update to my GSoC project...later all.

Friday, May 2, 2008

Using Symbols

If you all are anything like me, symbols confuse the hell out of you. You may use them, but you don't really understand them. I decided to research it out a little today and see what I could figure out. It turns out that there are a lot of different viewpoints of symbols out there. Here are some articles I came across:



The first is a detailed overview of symbols, the second shows some of the way Rails uses symbols.


So, as a quick summary, here is what I know about symbols:
A symbol is used to represent strings, without the overhead of actually creating a string. They don't have the functionality that you get with a string, and the value of them can't be changed, but they are much more efficient than using a string. Here is part of the example from the Gluttonous article:



> patient1 = { "ruby" => "red" }
> patient2 = { "ruby" => "programming" }
> patient1.each_key {|key| puts key.object_id.to_s}
211006
> patient2.each_key {|key| puts key.object_id.to_s}
203536

The key in both hashes creates(string "ruby") has a string object created each time. So, in this example, we have 2 seperate string objects with the same name. You can achieve the same result with symbols:



> patient1 = { :ruby => "red" }
> patient2 = { :ruby => "programming" }
> patient1.each_key {|key| puts key.object_id.to_s}
3918094
> patient2.each_key {|key| puts key.object_id.to_s}
3918094

The difference here is that the same symbol is referenced in both of these. Not only is symbol a smaller object, but it gets reused.


So I am curious, what ways do you all use symbols?



-Ralph

Thursday, May 1, 2008

Intro to the Rails Console

Hi all. Today I'm going to give you a bit of an introduction to the Rails console. Using it will make your life a lot easier. To open it up, type ./script/console from your project directory. From here you can play around, test out commands, add objects to the database if you haven't created an page to do it yet. I'll be using the console from my Forum tutorial to give you examples, so here we go!


First, let's load all of our topics from the database into a variable:



>> @topics = Topic.find(:all)

Which outputs the following:



=> [#<Topic id: 1, forum_id: 1, user_id: nil,
subject: "My Rails 2.0 Forum Tutorial",
body: "Check it out, http://railsonedge.blogspot.com",
created_at: "2008-03-06 11:11:07",
updated_at: "2008-03-06 11:11:07">,
#<Topic id: 2,
.......

Easy stuff. Now lets take that hash, and make it into something useful:



y @topics

The 'y' function outputs everything in yaml format, much more readable:



---
- !ruby/object:Topic
attributes:
updated_at: 2008-03-06 11:11:07
body: Check it out, http://railsonedge.blogspot.com
subject: My Rails 2.0 Forum Tutorial
id: "1"
forum_id: "1"
user_id:
created_at: 2008-03-06 11:11:07
attributes_cache: {}

- !ruby/object:Topic
attributes:
updated_at: 2008-03-06 11:11:37
body: Check it out! http://railsonedge.blogspot.com
subject: New Blog Post Today!!
id: "2"
forum_id: "1"
user_id:
created_at: 2008-03-06 11:11:37
attributes_cache: {}

- !ruby/object:Topic
attributes:
updated_at: 2008-03-09 00:35:44
body: Welcome to the new forum.
subject: Welcome!
id: "3"
forum_id: "2"
user_id:
created_at: 2008-03-09 00:35:44
attributes_cache: {}

- !ruby/object:Topic
attributes:
updated_at: 2008-03-09 00:36:04
body: What time is it?
subject: "Question:"
id: "4"
forum_id: "2"
user_id:
created_at: 2008-03-09 00:36:04
attributes_cache: {}

Now, lets grab just one of the topics and play around with that:



@topic = Topic.find(1)

You can use the 'y' function on this also. Try this to display certain attributes of @topic:



>> @topic.body
=> "Check it out, http://railsonedge.blogspot.com"

And you can change these attributes pretty easily also:



>> @topic.body = "And now the body says this."
=> "And now the body says this."
>> @topic.body
=> "And now the body says this."

When you change an attribute, it doesn't automatically save it to the database. But not to worry, it is easy enough:



>> @topic.save
=> true

Additionally, if you are working on a project and don't have a create page set up yet for an object yet, you can just create one from the console:



>> topic = Topic.new :body => "Lorem Ipsum",
:subject => "This Subject", :forum_id => 1, :user_id => 1
=> #<Topic id: nil, forum_id: 1, user_id: 1,
subject: "This Subject", body: "Lorem Ipsum",
created_at: nil, updated_at: nil>
>> topic.save
=> true

That is enough for some basic manipulation of things in your database, but what other things can you do with the console? What if you wanted to see all of the functions that can be used on a specific object? Well, just like when you are using bash, you can use tab to auto-complete:



String.to_ [press tab not enter]

String.to_a String.to_s
String.to_enum String.to_yaml
String.to_json String.to_yaml_properties
String.to_param String.to_yaml_style
String.to_query

Another good thing to use the rails console for is to test out little bits of code to see how they run. Here is a simple example using a loop in the console:



>>@topics = Topic.find(:all)
>> for topic in @topics do
?> puts topic.subject + " - append this"
>> end

My Rails 2.0 Forum Tutorial - append this
New Blog Post Today!! - append this
Welcome! - append this
Question: - append this

As with most terminals, you can access your command history by using the up arrow.
Another cool thing, when you are messing around with routing, you can check things out quickly with app.url_for and app.get:



>> app.url_for(:controller => 'forums', :action => 'index')
=> "http://www.example.com/forums"

>> app.get '/forums/1/topics'
=> 200

>> app.get '/invalid/url'
=> 404


The underscore('_') character will return the value of the last statement:



>> 5 + 9
=> 14
>> _
=> 14
>> _ + 6
=> 20
>> _
=> 20

Well, that is it for now. Let me know what other uses you guys (and girls?) have for the console.


Later,


-Ralph

Friday, April 25, 2008

DRY Up Your Views With Layouts For Your Partials

I got comments on my previous post, DRY up your views with nested partials, letting me know that there is a better way. So here is the same thing, done the proper way:


First, I've got this cool set of divs that gives me a nice looking window I will be reusing:


 
<div id="window-top"></div>
<div id="window">
<%= render :partial => 'mypartial' %>
</div>
<div id="window-bottom"></div>

<div id="window-top"></div>
<div id="window">
<%= render :partial => 'myotherpartial' %>
</div>
<div id="window-bottom"></div>

The goal here is to be able to re-use the code for the window multiple times. First, lets create a partial called '_lorem.html.erb':



<p>"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum." </p>

And another called '_blah.html.erb':



<p>blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah </p>


Alright! Now, make one last partial, which will be used for the layout, called '_window.html.erb'. Note that it needs to be created in the same directory as the other partials. This is where you put the code for the cool window you just made.



<div id="window-container">
<div id="window-top-left"></div><div id="window-top">Window Title</div>
<div id="window-top-right"></div>
<div id="window">
<%= yield %>
</div>
<div id="window-bottom-left"></div><div id="window-bottom">
</div><div id="window-bottom-right"></div>
</div>

And now, when you want to render a partial that is inside your window, all you need to do is set the layout to window:



<h1>Test of nested partials</h1>

<%= render :partial => 'blah', :layout => 'window' %>

<%= render :partial => 'lorem', :layout => 'window' %>


And thats it! Enjoy DRYing up your views. Later

-Ralph



EDIT: if you use :layout => 'layouts/window', you can put '_window.html.erb' in /views/application and reuse it throughout the site.

Wednesday, April 16, 2008

DRY up your views with nested partials

NOTE: I got a couple of comments letting me know that the proper way to do this is to use a layout with your partial. Please see this post for instructions. Since making this post, I've been told the partial nesting is generally a bad idea, so use at your own risk.




Got a good one for you today that should help clean up your views and DRY things out. How many times, when building a website, do you use the same code over and over again to build this cool looking window that is re-used over and over?


 
<div id="window-top"></div>
<div id="window">
<%= render :partial => 'mypartial' %>
</div>
<div id="window-bottom"></div>

<div id="window-top"></div>
<div id="window">
<%= render :partial => 'myotherpartial' %>
</div>
<div id="window-bottom"></div>

Well, you can use nested partials to achieve the same effect and re-use your window code. First, lets create a partial called '_lorem.html.erb':



<p>"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est
laborum." </p>

And another called '_blah.html.erb':



<p>blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah </p>


Alright! Now, make one last partial, '_window.html.erb'. This is where you put the code for the cool window you just made.
Inside of it, you want to render a partial, and pass in the variable 'name':



<div id="window-container">
<div id="window-top-left"></div><div id="window-top">Window Title</div>
<div id="window-top-right"></div>
<div id="window">
<%= render :partial => name %>
</div>
<div id="window-bottom-left"></div><div id="window-bottom">
</div><div id="window-bottom-right"></div>
</div>

And now, when you want to render a partial that is inside your window, all you need to do is render the 'window' partial, and as a local
variable named 'name', pass in the name of the partial you want rendered inside of it:



<h1>Test of nested partials</h1>

<%= render :partial => 'window', :locals => {:name => 'lorem' } %>

<%= render :partial => 'window', :locals => {:name => 'blah' } %>


And thats it! Enjoy DRYing up your views with nested partials. Later

-Ralph

Wednesday, April 9, 2008

ActiveScaffold - Helpers for columns with same name overwrite each other - FIX

Really quick one today. I was working on my ActiveScaffold project, and came across the following problem:
My Events table has a column called schools, and so does my Schools table. For the Events, I needed a dropdown box listing all schools...while for Schools, I needed a text box to show up to type in the name of the school. Since textboxes are the default for activescaffold, I shouldn't need to do anything with schools in it, however, when I wrote a form override for Events in event_helper.rb to give it a dropdown box...the dropdown showed up in Schools also. So first, I tried doing a form override in the school_helper.rb. No luck, the event helper method has the same name, and overrides whatever method I put in school with the same name. So to fix it, I removed my helper methods from the event and school helpers. In application_helper.rb, I made a function with the same name as the form override function I had been trying to use, and then used an if to determine which model the helper was being called from. Here is the code:


# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper

#hack to make activescaffold form overrides work when 2 models have a field that is the same name
def school_form_column(record, input_name)
if record.class.name == "Event"
select_tag input_name, options_for_select(aschools, selected = record.school)
else #record.class.name =="School"
text_field :record, :school, :name => input_name
end
end

private
#used in case of Event
def aschools
@schools = School.find(:all, :order => 'school')
@schooloptions = Hash.new
for school in @schools
@schooloptions[school.school] = school.abbrv
end
@schooloptions = @schooloptions.sort.each { |e| puts "#{e[0]} => #{e[1]}" }

end

end



Me: 2
ActiveScaffold: 0

Done.

Wednesday, April 2, 2008

Rails 2.0 Links

Decided to make a post with links to all the good resources I have come across so far. I'll be putting a permanent link to it on the sidebar. Feel free to leave a comment if you have any links you would like me to add.


RubyPlus.org - Lots of good screencasts; Restful Authentication, AWDR's Depot app for Rails 2.0


Rails Envy - They have a great ActiveRecord intro video here, among other things


Railscasts - Should need no introduction :)


Planet Ruby on Rails - Has feeds from many different Rails blogs all in one convenient place


InstantRails - For you Windows users, this program is a quick way to get up and going with Rails


DanielFischer.comTutorial for using Gmail as a mail server for a Rails project.


Jonathan s ng - ActionMailer Tutorial


Render Examples - Great render examples, helps when needing to render things with AJAX


Absolute Moron's Guide to Forms in Rails - Great form tutorial



Will be adding many more links in the days to come, this is just a preliminary list. Also, I would like to get some links for setting up Rails for development on several OS's(Linux flavors, MAC, ...), so if you have any please leave a comment.


Later.

-Ralph

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



Monday, February 25, 2008

Rails Forum Tutorial for Beginners(Part 2 of 3)

This is part 2 of my Rails 2.0 Forum tutorial for beginners. You can find part 1 here. Last time, we created our Forum, Topic, and Reply objects. We nested topics inside of forums and adjusted the views/controllers to allow for the nesting. This time, we will take care of replies being nested in a topic.

The nesting in /config/routes.rb has already been taken care of in the first tutorial:
  map.resources :topics, :has_many => :replies


The first thing we are going to do is to create partials for /app/views/replies/new.html.erb and /app/views/replies/edit.html.erb. Create the file /app/views/replies/_reply.html.erb

#/app/views/replies/_reply.html.erb

<% form_for([@topic, @reply]) do |f| %>
<p>
<b>Subject</b><br />
<%= f.text_field :subject %>
</p>

<p>
<b>Body</b><br />
<%= f.text_area :body %>
</p>

<p>
<%= f.submit button_name %>
</p>
<% end %>


And now change edit and new to:


#/app/views/replies/edit.html.erb

<h1>Editing reply</h1>

<%= error_messages_for :reply %>

<%= render :partial => @reply, :locals => { :button_name => "Submit" } %>

<%= link_to 'Show', [@topic, @reply] %> |
<%= link_to 'Back', replies_path %>



#/app/views/replies/new.html.erb

<h1>New reply</h1>

<%= error_messages_for :reply %>

<%= render :partial => @reply, :locals => { :button_name => "Reply" } %>

<%= link_to 'Back', replies_path %>


Same stuff we did last time...
Now lets go to /app/controllers/replies_controller.rb
Add this at the top, to make sure a topic gets loaded everytime the replies controller is used:

before_filter :load_topic

def load_topic
@topic = Topic.find(params[:topic_id])
end

inside the new function change the line:

@reply = Reply.new

to this:

@reply = @topic.replies.build

Change the edit function:

#old edit function

def edit
@reply = Reply.find(params[:id])
end


#new edit function

def edit
@reply = @topic.replies.find(params[:id])
end

Change the create function:

#old create

def create
@reply = Reply.new(params[:reply])

respond_to do |format|
if @reply.save
flash[:notice] = 'Reply was successfully created.'
format.html { redirect_to(@reply) }
format.xml { render :xml => @reply, :status => :created, :location => @reply }
else
format.html { render :action => "new" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end


#new create

def create
@reply = @topic.replies.build(params[:reply])

respond_to do |format|
if @reply.save
flash[:notice] = 'Reply was successfully created.'
format.html { redirect_to([Forum.find(@topic.forum_id), @topic]) }
format.xml { render :xml => @reply, :status => :created, :location => @reply }
else
format.html { render :action => "new" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end



Notice the change in redirect_to...after a reply to a topic is submitted, I want to send the user back to the forum the topic was in, to get the forum, I had to use the find function and pass it the forum_id of the topic and the topic.


In the update and destroy functions:

#old update and destroy

def update
@reply = Reply.find(params[:id])

respond_to do |format|
if @reply.update_attributes(params[:reply])
flash[:notice] = 'Reply was successfully updated.'
format.html { redirect_to(@reply) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end

def destroy
@reply = Reply.find(params[:id])
@reply.destroy

respond_to do |format|
format.html { redirect_to(replies_url) }
format.xml { head :ok }
end
end



#new update and destroy

def update
@reply = @topic.replies.find(params[:id])

respond_to do |format|
if @reply.update_attributes(params[:reply])
flash[:notice] = 'Reply was successfully updated.'
format.html { redirect_to(@topic) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @reply.errors, :status => :unprocessable_entity }
end
end
end


def destroy
@reply = @topic.replies.find(params[:id])
@reply.destroy

respond_to do |format|
format.html { redirect_to(@topic) }
format.xml { head :ok }
end
end


Now that we have all of that done...lets head over to /app/views/topics/show.html.erb so we can display our replies and the form for a new reply.


In addition to showing replies and a form for a new one, I changed the formatting a little, so here are the contents on show.html.erb

<p>
<h3>Forum: - <%=h @forum.name %></h3>
</p>

<p>
<b><%=h @topic.subject %></b>
</p>

<p>
<%=h @topic.body %>
</p>

<% unless @topic.replies.empty? %>

<% @topic.replies.each do |reply| %>
<p>
<b><%= reply.subject %></b>
</p>
<p>
<%=h reply.body %>
</p>

<% end %>
<% end %>

<h2>Reply</h2>
<%= render :partial => @reply = Reply.new, :locals => { :button_name => 'Reply' } %>

<%= link_to 'Edit', edit_forum_topic_path(@forum, @topic) %> |
<%= link_to 'Back', topics_path %>



Since this is the same stuff we were doing in the first tutorial, I won't bother explaining any of it...you should be able to tell what is going on. Feel free to leave a comment if you do have any questions.


Now, fire up the web server and head to localhost:3000/forums/1/topics/1 (assuming you have a topic already created).






And create a couple of replies:





And thats it! (10 points go to anyone who catches the video game reference in reply #2 in the screenshot).

In this tutorial, we nested replies inside of a topic, and set the views/controllers accordingly. Next time, we will take a look at authentication using the RESTful authentication plug-in and acts_as_state_machine plugin.

Feel free to leave comments, and link me from your site. See you next time!

Friday, February 22, 2008

Rails Forum Tutorial for Beginners (Part 1 of 3)

This is my first tutorial, so bear with me. I am writing it as if trying to teach someone brand new to rails what is going on. We are going to be creating a forum site in Ruby on Rails(2.0). I plan on this to be a several-part series, so keep coming back and checking for updates.

So here we go!



rails myforum -d mysql

This will create the project folder 'myforum' and force the database used to mysql(the newest version of rails default to sqlite3). All kinds of files and folders are created, but don't let them overwhelm you. Rails is an application framework, so by creating all this stuff it will force you to keep everything organized according to Rails' conventions.

Now, the general layout I decided on for the forums is this: The site will have multiple forums, each forums is going to have multiple topics, and each topic will have multiple replies. To get this going, lets try this:


cd myforum
ruby script/generate scaffold Forum name:string description:text


More files and directories are created, setting up migration files for the database, a model, a controller, and views for the Forum class. Lets analyze the command we just gave:
ruby script/generate - on a mac or on linux, it would be script/generate(I'm on windows). You will be using script/generate for a lot of things in rails, so get used to typing that one.
scaffold Forum - This tells rails that you want to generate a scaffold for the class 'Forum
name:string description:text - in the forums table that will be created, it will have 2 columns: name, and description. the :string and :text are the type of data, with string being a small 1 liner used for naming, and text being something that can be much longer. Moving on....


rake db:create
rake db:migrate


rake db:create will create your mySQL database for you (called myforum_development). rake db:migrate will create the table for the forum class.

Now lets fire up the server and see what we've got!

ruby script/server

And point your web browser(and you better not be using IE!) to http://localhost:3000


So the server is working fine, now go to http://localhost:3000/forums



If you click on New forum, it will give you a form with fields for Name and Description.





And then click back:




Thats right! With only 4 lines of code, rails was able to generate all of this! The scaffolding will create everything you need to Create, Read, Update, and Delete(CRUD).

Moving on, lets set things up so that rails will show the list of forums page by just going to http://localhost:3000, and get rid of that rails info page. Delete the file public/index.html, and then in config/routes.rb do this:

#In config/routes.rb

ActionController::Routing::Routes.draw do |map|
map.resources :forums
map.root :controller => 'forums', :action => 'index' # <-------add this line

..........
..........
end


So this is our first editing of a rails file. routes.rb is very important to the whole rails applications. They take a request for a URL and turn it into a request from a controller and an action. In this case, the line we added will take a request for the root(http://localhost:3000) and give us back the index action of the forums controller(which will list all the forums). Save, and again, point the browser to http://localhost:3000:




Great! Now, lets go ahead and generate scaffolding for our other 2 classes: Topics and Replies:


ruby script/generate scaffold Topic forum:references user:references subject:string body:text

ruby script/generate scaffold Reply topic:references user:references subject:string body:text

rake db:migrate

We have something new here. forum:references means that a topic will contain a foreign key to the forum it belongs to. It will also contain a foreign key for the user the posts it. Additionally, a reply will belong to a certain topic. And again, rake db:migrate will create the additional tables in the database. Don't worry about the User class yet, that part will be in a later tutorial when we add authentication.

If you go to http://localhost:3000/topics and http://localhost:3000/topics, you will see similar pages to the forum page that was created earlier.

Now that we have our scaffolding up, its time to take a look at some of the code that was generated and put it where we want it. First, lets go to /config/routes.rb and do a few things:


BEFORE:
#In config/routes.rb

ActionController::Routing::Routes.draw do |map|

map.resources :replies
map.resources :topics
map.resources :forums

map.root :controller => 'forums', :action => 'index'
...............
...............
end



AFTER:
#In config/routes.rb

ActionController::Routing::Routes.draw do |map|

map.resources :forums, :has_many => :topics
map.resources :topics, :has_many => :replies
map.resources :replies

map.root :controller => 'forums', :action => 'index'
...............
................
end


This sets up routing so that to view a specific post, you would go to a URL like: /forums/1/posts/3, and to see a reply /posts/1/replies/3.

Moving on, lets edit the files in /app/models/


#forum.rb

class Forum < ActiveRecord::Base
has_many :topics
has_many :replies, :through => :topics
end
------------------------------
#topic.rb

class Topic < ActiveRecord::Base
belongs_to :forum
belongs_to :user
has_many :replies
end
-------------------------------
#reply.rb

class Reply < ActiveRecord::Base
belongs_to :topic
belongs_to :user
end



All of that should make sense to you, just fleshing out some of the stuff we've already talked about.

Next, lets check out /app/controllers/topics_controller.rb. Add these lines to it:


before_filter :load_forum

def load_forum
@forum = Forum.find(params[:forum_id])
end


before_filter :load_forum will cause the load_forum method to be called anytime the topics controller is accessed.


Now, in the index function, change:
@topics = Topic.find(:all)

to
@topics = @forum.topics


Now change every instance of:
@topic = Topic.find(params[:id])

to
@topic = @forum.topics.find(params[:id])

and in the new function:
@topic = Topic.new

to
@topic = @forum.topics.build


Change the create function from:

def create
@topic = Topic.new(params[:topic])

respond_to do |format|
if @topic.save
flash[:notice] = 'Topic was successfully created.'
format.html { redirect_to(@topic) }
format.xml { render :xml => @topic, :status => :created, :location => @topic }
else
format.html { render :action => "new" }
format.xml { render :xml => @topic.errors, :status => :unprocessable_entity }
end
end
end

to

def create
@topic = @forum.topics.build(params[:topic])

respond_to do |format|
if @topic.save
flash[:notice] = 'Topic was successfully created.'
format.html { redirect_to(@forum) }
format.xml { render :xml => @topic, :status => :created, :location => @topic }
else
format.html { render :action => "new" }
format.xml { render :xml => @topic.errors, :status => :unprocessable_entity }
end
end
end


In update, change the line:

format.html { redirect_to(@topic) }

to

format.html { redirect_to(@forum) }

and finally, in destroy, change the line:

format.html { redirect_to(topics_url) }

to

format.html { redirect_to(forum_topics_url(@forum)) }


Alright, lets take a look at all that we just did. With the code that was generated by the scaffold, Rails did not take into account the fact that we need to retrieve our topic classes by a foreign key(forum_id). The majority of the changes were in setting that up correctly. The other changes were in the redirect_to function calls. Whenever I create, update, or destroy a topic, I want it to redirect back to the forum I was on instead of going back to the topic page automatically assigned.

Now, when we go to look at a specific forum, we want it to list out not just the forum title and description, but all the topics associated with that forum. Open up /app/views/forums/show.html.erb


<p>
<b>Name:</b>
<%=h @forum.name %>;
</p>

<p>
<b>Description:</b>
<%=h @forum.description %>
</p>


<%= link_to 'Edit', edit_forum_path(@forum) %> |
<%= link_to 'Back', forums_path %>


Since this is our first look at a view, let go over it a bit.

The <% tags work kind of like <?php tags. They switch out of normal HTML markup to ruby code. Inside these tags are where the magic happens. So a tag with just <% will not display anything, they are useful for doing things like loops. A tag with <%= will output the result into the HTML markup. So the line <%=h @forum.name %> will display the name of the forum. The lowercase 'h' is there escape HTML characters in output.
Change the file to this:


<p>
<b>Forum:</b>
<%=h @forum.name %> - <%=h @forum.description %>
</p>

<% unless @forum.topics.empty? %>
<h2>Topics</h2>
<% @forum.topics.each do |topic| %>
<b><%= link_to topic.subject, [@forum, topic] %></b><br />
<% end %>
<% end %>

<%= link_to 'Edit', edit_forum_path(@forum) %> |
<%= link_to 'Back', forums_path %>



Now one more thing; I want to have a 'Create New Topic' form at the bottom of the page. First, lets take a look at /app/views/topics/new.html.erb and /app/views/topics/edit.html.erb. These 2 files are nearly identical. This is a good time to use the DRY(Don't Repeat Yourself) that Rails is big on. Lets create a new file called _topic.html.erb in the /app/views/topics/ folder and put the following code into it:

<% form_for([@forum, @topic]) do |f| %>
<p>
<b>Subject</b><br />
<%= f.text_field :subject %>
</p>

<p>
<b>Body</b><br />
<%= f.text_area :body %>
</p>

<p>
<%= f.submit button_name %>
</p>
<% end %>

And then change edit.html.erb and new.html.erb to look like this:

#edit.html.erb

<h1>Editing topic</h1>

<%= error_messages_for :topic %>

<%= render :partial => @topic, :locals => { :button_name => "Submit" } %>

<%= link_to 'Show', [@forum, @topic] %> |
<%= link_to 'Back', topics_path %>

----------------------------
#new.html.erb

<h1>New topic</h1>

<%= error_messages_for :topic %>

<%= render :partial => @topic, :locals => { :button_name => "Create" } %>

<%= link_to 'Back', topics_path %>


That cleaned up a good bit of code. Since the only real difference in the 2 forms for those pages was the button name, we created what is called a partial in the file _topic.html.erb, and then rendered the partial in new.html.erb and edit.html.erb, passing in the value of the button name. And now, we can also re-use the partial in /app/views/forums/show.html.erb

Change the file to look like this:


<p>
<b>Forum:</b>
<%=h @forum.name %> - <%=h @forum.description %>
</p>

<% unless @forum.topics.empty? %>
<h2>Topics</h2>
<% @forum.topics.each do |topic| %>
<b><%= link_to topic.subject, [@forum, topic] %></b><br />
<% end %>
<% end %>

<h2>New Post</h2>
<%= render :partial => @topic = Topic.new, :locals => { :button_name => 'Create' } %>

<%= link_to 'Edit', edit_forum_path(@forum) %> |
<%= link_to 'Back', forums_path %>


One more thing, open up /app/views/topics/show.html.erb and remove this part:

<p>
<b>User:</b>
<%=h @topic.user %>
</p>

Since we haven't implemented the authentication yet.

And change the line:
<%=h @topic.forum %>

to
<%=h @forum.name %>

And then change:
<%= link_to 'Back', topics_path %>

to
<%= link_to 'Back', forum_path(@forum) %>

Save everything, fire up the server, and head back to http://localhost:3000
ruby script/server



Click Show



And there we go! You can add a new post, and then click on it to show it.

So far in the tutorial, we have generated scaffolding for 3 classes(forum, topic, reply), set up the nested routing for topics to be inside forums, and replies to be inside topics, and modified the controllers and view for forum and topic to work like we need it to.

I've decided this will be a 3-part tutorial. The next part will be to get the Replies working(it will be very similar to what we did with nesting topics inside forums) by modifying the views for topics, and the views and controllers for replies. The third part will be to set up Authentication and Admin priveleges.

Let me know what you think, or if you have any questions. And tell your friends.

See you next time!

Wednesday, February 20, 2008

Ruby on Rails 2.0 forum

I decided to start work on a forum today. Using the concepts I learned in Akita On Rails's tutorial to route a comment into a blog post can be similarly used to route a post to a forum and a reply to a post. I might look into something more complicated later such as a reply to a reply(digg style). I'm going to start by getting it all working regularly and then attempt to add in some AJAX to make it look spiffy.

Anyway, first I decided that I needed some kind of Authentication for accounts, sessions, and whatnot so I went back to my favorite resource RubyPlus.org and checked out the screencast RESTful Authentication tutorial.

There were a couple small problems I ran into, I'm assuming because there is a new version of act_as_state_machine plugin that has came out since the tutorial that was wrote. I will add that to the post tomorrow, along with a tutorialized version of all the steps I have taken so far.

Oh, and here are some beginner resources/tutorials I have found around the net the past few days:



Well thats it, will update you on my forum app tomorrow. Thanks for coming, and feel free to spread the word!

Monday, February 18, 2008

AWDR depot app for Rails 2.0 (RubyPlus.org) - Part 2

Here goes part 2.

I've finished the screencasts for chapter 8, 9, and 10.

Here are the problems I came across while doing so:

At the beginning of chapter 8, the top of the screen is cut off when creating sessions in the database, the is the whole line:

rake db:sessions:create


And then towards the beginning of chapter 10, a line is cut off in 005_create_line_items.rb

t.decimal :total_price, :null => false, :precision => 8, :scale => 2


I had some trouble with the ajax highlight effect in add_to_cart.js.rjs

page[:current_item].visual_effect :highlight,
:startcolor => "#88ff88",
:endcolor => "#114411"


and was not able to figure it out. When I clicked Add to Cart, I would get the error "TypeError: $("current_item") has no properties" and then another popup window with a lot of code in it. I looked around the internet for a while trying to get it fixed, but nothing I tried worked. If anyone figures this out, let me know. I ended up commenting out that line in add_to_cart.js.rjs.

Everything else works great. I really like Bala's screencasts, they are a great resource for learning Rails 2.0. After finishing them, I believe I know enough to make something that is not too complicated. For my next post, I believe I am going to start working on a forum in Rails, and AJAX it up(I could use a lot more experience with AJAX).

See you all next time!

Thursday, February 14, 2008

New Resource for Beginner 2.0 Tutorials

Just found a site with some great video tutorials for beginners. Its RubyPlus.org. You have to create a free account to access the download.

When I first went to learn Ruby, the book unanimously suggested to me was Agile Web Development with Rails. After a quick search I had found out that the tutorials, of course, didn't work with 2.0. A little more searching and I came across RubyPlus. He has the depot app from the book adapted to Rails 2.0, and video tutorials on it. You will need some of the source that came with the book, which can be downloaded here.

After you create your account at RubyPlus, click Archives to see the tutorials. The AWDR depot ones are 19, 22, 27, 28, 29. It looks like there is a lot of good material on this site besides depot tutorials, I plan to spend a lot of time watching Bala's screencasts.

Also, you can find his blog here.

On to the tutorial:

I've finished the first 2 screencasts without too much trouble. He moves pretty fast, and copies/pastes a lot of code when he creates new files, so be ready to pause so you can type everything out.

In the first screencast, we set up the project, created scaffolding, and played around with the view a little. In the second, we created an admin namespace, and then nested a product route under it, changing the controllers and views so that only an admin could add, edit, and destroy books. So far, much like the blog app from the AkitaOnRails site.

There was a point where you needed some images and css files from the AWDR book (link above), and despite what he says on his page, you don't need to buy the book to download it(although I had the book already). There is also a segment in the first screencast where he is creating a bootstrap.rake file to set up automatic population of the database, and the screen cuts off some of the code he pastes into the file, the full contents of the file are:

SITE_DIR = File.join(RAILS_ROOT, 'themes/site-' + (ENV['SITE_ID'] || '1'))
namespace :db do
namespace :bootstrap do
desc "Load initial database fixtures (in db/bootstrap/*.yml) into the current environment's database.
Load specific fixtures using FIXTURES=x,y"
task :load => :environment do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'db', 'bootstrap',
'*.{yml,csv}'))).each do |fixture_file|
Fixtures.create_fixtures('db/bootstrap', File.basename(fixture_file, '.*'))
end
end
end
end


See you next time!