3.2. Documents
3.1 Model, Controller
has_many and belongs_to allow to have 1 to n relations betweens objects. But here we want to use n to n relations : one document can be in many categories and categories can have many documents. In fact it’s two 1 to n relations. So we are going to use the has_and_belongs_to_many and belongs_to attributes.
Open the app/models/document.rb file. This is where the Document class is defined. Add the following two lines in the class :
has_and_belongs_to_many :categories
belongs_to :author
We have defined a relation with two other models : categories and author. So : the document belongs and has many categories, and he belongs to one author only. We know will be able to access to the categories and author attributes.
The categories attribute will be a collection, author a simple object.
Now we are going to define the methods of the document controller and then the views. Here is the controller’s methods :
model :document
def index
end
def list
@publications = Document.find_all() # get all the documents
end
def show
@item = Document.find(@params["id"]) # get one document using the id to
# know which one is the good one
end
def new
@categories = Category.find_all # get all the categories
@year = Time.now.year # get the year
@month = Time.now.month # get the month
@monthname = Time.now.strftime("%B") # get the month name
@day = Time.now.day # get the day
end
def create
@categories = Category.find_all # get all the categories
item = Document.new # Create a new instance of Todo, so create a new item
item.attributes = @params["document"] # The fields of item should be set to what's in the "new_item" hash
if item.save # Try to save our item into the database
for category in @categories # if it is saved we can add the categories document links
if (@params[category.name]) # so we look in the params if there is one who got the same
# name as the category
item.categories<<(category) # if there is one it is because the user has checked the box
# so we insert the category in the collection using the '<<' method
end
end
redirect_to(:action => "list") # Return to the list page if it suceeds
else
render_text "Couldn't add new item" # Print an error message otherwise
end
end
# get all the things we need to display a real edit form
def edit
@categories = Category.find_all # get all the categories
@year = Time.now.year # get the year
@month = Time.now.month # get the month
@monthname = Time.now.strftime("%B") # get the month name
@day = Time.now.day # get the day
@document = Document.find(@params["id"]) # get the good document
end
# update method, called by the edit form
def update
@categories = Category.find_all # get all the categories
@document = Document.find(@params['document']['id']) # get the document into the db using the id passed in the
# form to get the good one
if @document.author_id == @session["author_id"] # verify if the logged user has the write to edit the document
@document.attributes = @params['document'] # update the attributes
if @document.save # try to save the document
@document.categories.clear # if it is saved we clear all the links
# relative to this document in the categories_document table
for category in @categories # we recreate the good links
if (@params[category.name])
@document.categories<<(category)
end
end
flash['notice'] = 'document was successfully updated.'
redirect_to(:action => 'show', :id => @document.id) # it's ok so we redirect
else
render_action 'edit' # not ok go back to the edit page
end
else
flash['notice'] = 'You can\'t edit this article.' # the logged user doesn't have the right to edit the doc
redirect_to(:action => 'list')
end
end
# define the secure methods
protected
def secure?
["new", "edit", "update", "create" ].include?(action_name)
end
3.2 Views
- list.rhtml
- show.rhtml
- new.rhtml
- edit.rhtml
<h1>Documents</h1>
<%= link_to("New document", :controller => "document", :action => "new") %>
<ul>
<% @publications.each do |@item| %>
<li>
<%= link_to(@item.title, :controller => "document", :action => "show", :id => @item.id) %> @
<% for @cat in @item.categories %>
<%= link_to(@cat.name, :controller => "category", :action => "show", :id => @cat.id) %>,
<% end %>
<% if (@session["author_id"] && @session["author_id"] == @item.author_id ) %>
:: <%= link_to("Edit", :controller => "document", :action => "edit", :id => @item.id) %>
<% end %>
</li>
<br />
<% end %>
</ul>
Ok same idea as previous list view. For each publication/document we display the title and the categories where it is. We display an edit link if the logged user is the document’s author.
show.rhtml :
<h1><%= @item.title %></h1>
<p>
<%= textilize_without_paragraph(@item.description) %>
</p>
<p>File : <a href="/pub/<%= @item.filename %>"><%= @item.filename %></a><br />
In :
<% for @cat in @item.categories %>
<%= link_to(@cat.name, :controller => "category", :action => "show", :id => @cat.id) %>,
<% end %><br />
</p>
Simple : display the document’s info. This view introduce a redcloth method : textilize_without_paragraph(). It transform a textile formated text into html. You must have RedCloth installed to be able to use it.
new.rhtml
<h1>New Document</h1>
<form action="/document/create" method="post">
<p><label for="document_title">Title</label><br />
<input id="document_title" name="document[title]" size="30" type="text" value="" /></p>
<p><label for="document_description">Description</label><br />
<textarea cols="65" id="document_description" name="document[description]" rows="15" wrap="virtual">
</textarea></p>
<p><label for="document_date">Date</label><br />
<select name="document[date(1i)]">
<% for @ynum in (@year-2..@year+10) %>
<% if @ynum == @year %>
<option value="<%= @ynum %>" selected="selected">
<% else %>
<option value="<%= @ynum %>">
<% end %>
<%= @ynum %></option>
<% end %>
</select>
<select name="document[date(2i)]">
<% for @mnum in (1..12) %>
<% if @mnum == @month %>
<option value="<%= @mnum %>" selected="selected">
<% else %>
<option value="<%= @mnum %>">
<% end %>
<%= @mnum %></option>
<% end %>
</select>
<select name="document[date(3i)]">
<% for @dnum in (1..31) %>
<% if @dnum == @day %>
<option value="<%= @dnum %>" selected="selected">
<% else %>
<option value="<%= @dnum %>">
<% end %>
<%= @dnum %></option>
<% end %>
</select>
</p>
<p><label for="document_categories">Categories</label><br />
<% for @category in @categories %>
<%= @category.name %> : <input type="checkbox" name="<%= @category.name %>" value="0"/><br />
<% end %>
</p>
<input type="hidden" name="document[author_id]" value="<%= @session["author_id"] %>"/>
<p>
<label for="document_filename">Filename</label><br />
<input id="document_filename" name="document[filename]" size="30" type="text" value="" /></p>
<input type="submit" value="Create" /></form>
Big … But quite simple in fact. You just have to look to the date select list, this is where the most difficult thing is. Remember the new method of the document controller. There was some variables to store the day, the month and the year. We use them here to get the good date selected in the select lists.
edit.rhtml :
<h1>Edit Document</h1>
<%= form_tag(:action => "update") %>
<p><label for="document_title">Title</label><br />
<input id="document_title" name="document[title]" size="30" type="text" value="<%= @document.title %>" />
</p>
<p><label for="document_description">Description</label><br />
<textarea cols="65" id="document_description" name="document[description]" rows="15" wrap="virtual">
<%= @document.description %>
</textarea></p>
<p><label for="document_date">Date</label><br />
<select name="document[date(1i)]">
<% for @ynum in (@year-2..@year+10) %>
<% if @ynum == @year %>
<option value="<%= @ynum %>" selected="selected">
<% else %>
<option value="<%= @ynum %>">
<% end %>
<%= @ynum %></option>
<% end %>
</select>
<select name="document[date(2i)]">
<% for @mnum in (1..12) %>
<% if @mnum == @month %>
<option value="<%= @mnum %>" selected="selected">
<% else %>
<option value="<%= @mnum %>">
<% end %>
<%= @mnum %></option>
<% end %>
</select>
<select name="document[date(3i)]">
<% for @dnum in (1..31) %>
<% if @dnum == @day %>
<option value="<%= @dnum %>" selected="selected">
<% else %>
<option value="<%= @dnum %>">
<% end %>
<%= @dnum %></option>
<% end %>
</select>
</p>
<p><label for="document_categories">Categories</label><br />
Exist in :
<% for cat in @document.categories %>
<%= cat.name %>,
<% end %>
<br />
<% for @category in @categories %>
<% if @document.categories.include? @category %>
<%= @category.name %> : <input type="checkbox" name="<%= @category.name %>" value="0" checked="checked"/><br />
<% else %>
<%= @category.name %> : <input type="checkbox" name="<%= @category.name %>" value="0"/><br />
<% end %>
<% end %>
</p>
<input type="hidden" name="document[author_id]" value="<%= @session["author_id"] %>"/>
<input type="hidden" name="document[id]" value="<%= @params["id"] %>"/>
<p><label for="document_filename">Filename</label>
<br />
<input id="document_filename" name="document[filename]" size="30" type="text" value="" /></p>
<input type="submit" value="Update" />
<%= end_form_tag %>
Quite the same one, but look, we add the value of the document in the form this time.
3.3 Let's test !
Open the http://howto/document/list page. You should see nothing appart the title and the new article link. Click on it. Here is the new article form (if you are logged in) !. And you can see that there is one checkbox for each category you’ve previously created. Complete the form, check one or more category and click on the create button. And voila here you are !