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
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:
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.
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
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.
rails myforum -d mysql
cd myforum
ruby script/generate scaffold Forum name:string description:text
rake db:create
rake db:migrate
ruby script/server
#In config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :forums
map.root :controller => 'forums', :action => 'index' # <-------add this line
..........
..........
end
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
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
#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
before_filter :load_forum
def load_forum
@forum = Forum.find(params[:forum_id])
end
@topics = Topic.find(:all)
@topics = @forum.topics
@topic = Topic.find(params[:id])
@topic = @forum.topics.find(params[:id])
@topic = Topic.new
@topic = @forum.topics.build
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
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
format.html { redirect_to(@topic) }
format.html { redirect_to(@forum) }
format.html { redirect_to(topics_url) }
format.html { redirect_to(forum_topics_url(@forum)) }
<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 %>
<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 %>
<% 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 %>
#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 %>
<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 %>
<p>
<b>User:</b>
<%=h @topic.user %>
</p>
<%=h @topic.forum %>
<%=h @forum.name %>
<%= link_to 'Back', topics_path %>
<%= link_to 'Back', forum_path(@forum) %>
ruby script/server
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