Topic: Trestle, My Rails Learning Project

After doing tutorials till blue in the face, and after hearing the best way to really get it is to throw yourself into a project, I've 'done started one.' I'm trying to write a Web-Magazine CMS, which I've dubbed Trestle.

Goals:
1. Display Current issue with associated articles
2. Display past issues with their articles
3. Create a secure Admin where articles and issues can be assembled and deployed (or scheduled to deploy)
4. Make the system flexible for various layouts and differing numbers of articles, even per issue

If it flies I will probably want to add other trappings like RSS, e-mail notifications etc.

At the moment its on my local machine but in a few days I'll scoot it over to my server for your viewing pleasure.


SO FAR... I've a simple controller called "issue" and models for "issue" and "articles".

In the MySQL DB "issues" are basically just an ID and a date. "Articles" have a key to associate them to "issues".

As it stands I have the issues controller load some arrays from each model, returning the latest issue (by id) and one article. I even have it all pretty in a layout (hey, I'm first a designer). And that's where I'm stuck. I want to show many articles, but I'm wrestling with how to do that. I've looked at joins, polymorphic, tree, trying to decide how best to pull up an array of articles and put them in XHTML. In my mind I just want a query that finds all articles where issue_id = id. Even if that worked, I'm not sure how to roll out the array. I don't think I want a typical loop cause each article may have very different layout.

class Article < ActiveRecord::Base
  belongs_to :issue
  def self.find_current_articles
    Article.find(:first)
  end
end
#CLASS MODEL FILE
class Issue < ActiveRecord::Base
  def self.find_current_issue
    Issue.find(:first, :order => "id DESC")
  end
  has_many :articles
end
class IssueController < ApplicationController
  def index
    @issues = Issue.find_current_issue
    @articles  = Article.find_current_articles
  end
end
<div id="article">
  <h2><%= @articles.title %></h2>
  <%= @articles.body %>
</div>

That works for one article, but changing it to :all confuses the array's objects. I've tried a few :conditions but nothing has worked yet.

BTW I have the Agile Rails book permanently fixed to my lap of you want to reference it for me.

So what should my strategy be?

"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.

Re: Trestle, My Rails Learning Project

class Article < ActiveRecord::Base
  belongs_to :issue
  def self.find_current_articles(issue_id = nil)
    if issue_id then
      Article.find(:all, :conditions => [ "issue_id = ?", issue_id ])
    else 
      Article.find(:all)
    end
  end
end
<% @articles.each do |article| %>
<div id="article">
  <h2><%= article.title %></h2>
  <%= article.body %>
</div>
<% end %>

That should hopefully get you on your way ... ?

"Take up your cross before your crown" :: http://no-spec.com

Re: Trestle, My Rails Learning Project

Note that if you've got an issue obj you can do the following:

<% issue.articles.each do |article| %>
<div id="article">
  <h2><%= article.title %></h2>
  <%= article.body %>
</div>
<% end %>
"Take up your cross before your crown" :: http://no-spec.com

Re: Trestle, My Rails Learning Project

Great boost. As per our IRC conversation (that all those people not on IRC are missing out on, especially my wit'i humor), I'll add a layout hierarchy id to each article and look at processing and displaying that with partials. Cool stuff.

"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.

Re: Trestle, My Rails Learning Project

I haven't tested this, so there might be a couple of things to fix, but this is straight from my head on a possible approach:

  # Model
  class Article < ActiveRecord::Base
    belongs_to :issue
    
    TIME_TO     = Time.now.localtime
    TIME_FROM   = Time.now.localtime.ago(30.days)
    
    class << self
      def find_current_articles(from=TIME_FROM, to=TIME_TO)
        find(:all, :conditions => ["created_at BETWEEN ? AND ?", from, to])
      end
    end  
  end


  # Model
  class Issue < ActiveRecord::Base
    has_many :articles
    
    class << self
      def find_current_issues(limit=5)
        find(:first, :order => "created_at DESC", :limit => limit)
      end
    end
  end
  
  # Controller
  class IssuesController < ApplicationController
    
    def index
      @issues = Issue.find_current_issues(7)
      
      respond_to do |wants|
        wants.html
      end
    end
  end
  
  # Issue#index View
  <% @issues.each do |issue| %>
    <h2><%= h issue.title %></h2>
    <h3>Articles in this Issue</h3>
    <ul><%= render :partial => "articles/article", :collection => issue.articles %></ul>
  <% end %>
  
  # articles/_article.html.erb
  <li>
    <h4><%= article.title %></h4>
    <p><%= truncate(article.body, 100, link_to('read on...', article_url(article)) ) %></p>
  </li>

Re: Trestle, My Rails Learning Project

You can simplify this a lot. For starters:

# app/models/article.rb
class Article < ActiveRecord::Base
  belongs_to :issue
end

# app/models/issue.rb
class Issue < ActiveRecord::Base
  has_many :articles
  def self.find_current_issue
    find(:first, :order => "issue.publication_date DESC", :include => [:articles])
  end
end


# app/controllers/issue_controller.rb
class IssueController < ApplicationController
  def index
    @issue = Issue.find_current_issue
    @issues = Issue.find(:all, :include => [:articles])
  end
end


# app/views/issues/index.html.erb
<h1>Current Issue</h1>
<%= render :partial => @issue.articles %>

# app/views/issues/_article.html.erb
<%= content_tag(:li, link_to(article.title, article)) %>

For the admin area, you can create a modular controller using ./script/generate like so:

./script/generate controller admin/issues

./script/generate controller admin/articles

I ususlly use restful_authentication for login.

Ryan Heneise  |  Art of Mission  |  Now with extra-strong Donor Tools mojo

Re: Trestle, My Rails Learning Project

@Robert

Thats good. Haven't tried it yet because I'm still doing some thinking through some of the concepts behind it. As you know, right now it pulls the latest issue by finding the simple :first argument to get the highest ID. That works great if issues aren't published on a schedule, except I will need a way of allowing people to navigate past issues.

On the other hand there probably should be some way of setting up a schedule and have pre-prepared issues in a que for release. Goodness this sounds like a lot of work.

@ryenski

Thats fantastic. I like the elegant simplicity. Since this project is mostly for learning, could you explain a few things you did? I have a basic idea, but some elaboration on ":include => [:articles]" and how you use the arrays in the views would be quite nice.

I hope to keep at this tonight but I need to work on some client stuff first.

"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.

Re: Trestle, My Rails Learning Project

Leovenous,

I haven't been a part of this topic, but I'll try to help you to understand some of the fine code that's been suggested to you.  In regards to ryenski's code, the :include => [:articles] is called eager loading. 

If you were to iterate through all the issues and then all of the articles associated with it,  you would have multiple queries going to the server.  In loading the page, each issue would also result in another query to the server to return it's articles.

By using ryenski's example, and taking advantage of Rails’s eager loading feature, you can greatly reduce the number of queries required. Using as suggested, results in one query, instead of many (depending on how many issues, and articles you have).

As an added bonus, you get code that's easier to understand smile 

I hope that helps.

Last edited by zreed20 (2008-01-05 17:35:49)

But they that wait upon the Lord shall renew their strength

Re: Trestle, My Rails Learning Project

Cool. I'm trying to set up a repository so any of you can snag a copy.

"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.

Re: Trestle, My Rails Learning Project

Okay I have an svn repos at svn://67.207.129.88/var/svn/trestle
The models are fixed and there are complete fixtures for tests. I've got my test DB running right now in WebRick.

There are two controllers. issue and admin, so needless to say you'll have to point your browser to one of them. Issue has, well, issues. Admin works.

As of Revision 17 the find_latest_issue now has a condition to limit it to "status = 1" (Published). Also in all my partials I am using .inspect because I can't get just one part of the array. I think this is because my @issue.articles.each sends the array for both articles at once, rather than one at a time.

I still have some confusion on the code:

    @issue = Issue.find_current_issue
    @issues = Issue.find(:all, :include => [:articles])

Isn't that redundant? I mean, the :include => is already in the Issue.find_current_issue model. Are they really both necessary? I went ahead an knocked it off the find_current_issue model and it seems to be fine.

Last edited by Leovenous (2008-01-06 21:43:12)

"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.

Re: Trestle, My Rails Learning Project

Leovenous,

You shouldn't need both if you're only iterating through the @issues.  That will pull in all the issues for you. Also, you might want to add a condition, so that as you gain many articles, you can move "older" ones into an archive.  You could do this with a date condition, or a field_that you set with a checkbox like this.  Also you might want to order them by date so that the most recent shows up at the top.

@issues = Issue.find(:all, :conditions => ["archive != 1 and published = 1"], :include => [:articles], :order => "created_at DESC" )

This would find all the published Issues that aren't archived and put the most recent at the top, and pull in the associated articles.

Can you paste the code from your partial, I don't have time to checkout your code and setup the app at work.  But I'll take a look if you paste it.

Zack

But they that wait upon the Lord shall renew their strength

Re: Trestle, My Rails Learning Project

I do have a condition, and it works fine.

Here is where the code is at:

#Admin controller for my Dashboard. Works.
class AdminController < ApplicationController
  def index
    @all_issues = Issue.find(:all, :order => "id DESC")
    @total_issues = Issue.count
  end
end

#Issue Controller
class IssueController < ApplicationController
  def index
    @issue = Issue.find_current_issue
    @issues = Issue.find(:all, :include => [:articles])
  end
end

#Issue Model
class Issue < ActiveRecord::Base
  has_many :articles
  def self.find_current_issue
    find(:first, :conditions => "status = 1", :order => "issues.id DESC")
  end
end

#Article Model
class Article < ActiveRecord::Base
  belongs_to :issue
end

I can get that to output articles to my view, however for the Issues to be highly template-able, I have been trying to use a partial for each article. That way I could achieve about any layout I could want with stylesheets. Each article has a "hirearchy" number. That number gets slapped onto the end of the partial name. The problem is the array comes out funny in the partial. This is most obvious when I use .inspect. Maybe there is a better way than partials to achieve what I'm going for. If so I'm all ears. Here is the view related code:

# views/issue/index.rhtml
<% @issue.articles.each do |article| %>
  <% partial_name = "hierarchy" + article.hierarchy_id.to_s%>
  <%= render(:partial => partial_name, :object => article) %>
<% end %>

# partial _hierarchy1.rhtml
<div id="article">
  <%= h(@issue.articles.inspect) %>
</div>
"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.

Re: Trestle, My Rails Learning Project

Update:

I renamed the project because it seems "trestle" is used by someone else.

I also moved the repos: svn://67.207.129.88/var/repos/publicate-r

It probably says rev 1 because I put it in a new repos.

So now I'm calling it Publicate-r.

I'll probably work on it later this week. Still appreciate any helps/advice, in general or in specific.

"Bear 270, young man. Bear two, seven, zero, over." - Musings of a flight simulator guru, me.