blog_logic 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -1,14 +1,14 @@
1
1
  source 'http://rubygems.org'
2
- source 'http://jose.seologic.com:8808/'
3
2
 
4
3
  gem 'bson_ext'
5
4
  gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'
6
5
  gem 'ckeditor', :git => 'git://github.com/Bantik/rails-ckeditor.git', :branch => 'rails3_mongoid'
7
6
  gem 'kaminari'
8
- gem 'mongoid', '>= 2.0.0.beta.17'
9
- gem 'rails', '>= 3.0.1'
7
+ gem 'mongoid-tree', :require => 'mongoid/tree'
8
+ gem 'rails', '3.0.10'
10
9
  gem 'rmagick', '2.12.2' # version compatible with heroku
11
- gem 'scaffold_logic', '>= 1.0.3'
10
+ gem 'scaffold_logic'
11
+ gem 'stringex'
12
12
  gem 'tanker'
13
13
 
14
14
  group :development do
@@ -17,7 +17,6 @@ group :development do
17
17
  end
18
18
 
19
19
  group :test do
20
- gem 'angry-pickle'
21
20
  gem 'be_valid_asset'
22
21
  gem 'capybara'
23
22
  gem 'cucumber-rails'
@@ -30,6 +29,4 @@ group :test do
30
29
  gem 'mocha'
31
30
  gem 'rcov'
32
31
  gem 'rspec-rails'
33
- gem 'simplecov', '>= 0.4.0', :require => false
34
- gem 'viewcumber'
35
32
  end
data/Gemfile.local CHANGED
@@ -1,15 +1,15 @@
1
1
  source 'http://rubygems.org'
2
- source 'http://jose.seologic.com:8808/'
3
2
 
4
3
  gem 'bson_ext'
5
- gem 'carrierwave'
4
+ gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'
6
5
  gem 'ckeditor', :git => 'git://github.com/Bantik/rails-ckeditor.git', :branch => 'rails3_mongoid'
7
6
  gem 'kaminari'
8
- gem 'mongoid', '>= 2.0.0.beta.17'
9
- gem 'rails', '>= 3.0.1'
7
+ gem 'mongoid-tree'
8
+ gem 'rails', '3.0.10'
10
9
  gem 'rmagick', '2.12.2' # version compatible with heroku
11
10
  gem 'scaffold_logic', :path => '~/Documents/projects/scaffold_logic'
12
- gem 'SystemTimer'
11
+ gem 'stringex'
12
+ gem 'tanker'
13
13
 
14
14
  group :development do
15
15
  gem 'jeweler'
@@ -17,7 +17,6 @@ group :development do
17
17
  end
18
18
 
19
19
  group :test do
20
- gem 'angry-pickle'
21
20
  gem 'be_valid_asset'
22
21
  gem 'capybara'
23
22
  gem 'cucumber-rails'
@@ -30,6 +29,4 @@ group :test do
30
29
  gem 'mocha'
31
30
  gem 'rcov'
32
31
  gem 'rspec-rails'
33
- gem 'simplecov', '>= 0.4.0', :require => false
34
- gem 'viewcumber'
35
32
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -0,0 +1,49 @@
1
+ class Admin::BlogCategoriesController < ApplicationController
2
+ before_filter :authenticate_user! if Object.const_defined?('Devise')
3
+ before_filter :find_blog_category, :only => [:destroy, :edit, :update, :show]
4
+
5
+ def index
6
+ @blog_categories = BlogCategory.roots
7
+ end
8
+
9
+ def show
10
+ end
11
+
12
+ def new
13
+ @blog_category = BlogCategory.new :parent_id => params[:parent_id]
14
+ end
15
+
16
+ def create
17
+ @blog_category = BlogCategory.new params[:blog_category]
18
+ if @blog_category.save
19
+ flash[:notice] = 'Successfully created the blog_category.'
20
+ redirect_to @blog_category.root? ? admin_blog_category_path(@blog_category) : admin_blog_category_path(@blog_category.parent)
21
+ else
22
+ render :action => 'new'
23
+ end
24
+ end
25
+
26
+ def edit
27
+ end
28
+
29
+ def update
30
+ if @blog_category.update_attributes params[:blog_category]
31
+ flash[:notice] = 'Successfully updated the blog_category.'
32
+ redirect_to admin_blog_categories_path
33
+ else
34
+ render :action => 'edit'
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @blog_category.destroy
40
+ flash[:notice] = 'Successfully destroyed the blog_category.'
41
+ redirect_to @blog_category.root? ? admin_blog_categories_path : admin_blog_category_path(@blog_category.parent)
42
+ end
43
+
44
+ private
45
+
46
+ def find_blog_category
47
+ @blog_category = BlogCategory.find params[:id]
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ class BlogCategoriesController < ApplicationController
2
+ before_filter :find_category_by_permalink, :only => :show
3
+
4
+ def index
5
+ @blog_categories = BlogCategory.roots_with_posts
6
+ end
7
+
8
+ def show
9
+ if @blog_category.blank?
10
+ flash[:notice] = "That category doesn't exist."
11
+ redirect_to Blog.first.path
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def find_category_by_permalink
18
+ @blog_category ||= BlogCategory.by_slug(params[:id])
19
+ end
20
+ end
data/app/models/blog.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  class Blog
2
+ include BlogLogic::Base
2
3
  include Mongoid::Document
3
4
  include Mongoid::Timestamps
4
- include BlogLogic::Base
5
5
 
6
- # Attributes =====================================================================================
6
+ # Scopes =========================================================================================
7
+ scope :by_slug, lambda {|slug| {:where => {:slug => "#{slug}".gsub('//','/')} } }
8
+
9
+ # Mongoid ========================================================================================
7
10
  field :title
8
11
  field :slug
9
12
  field :description
@@ -11,37 +14,36 @@ class Blog
11
14
  field :posts_per_page, :type => Integer
12
15
  field :rss_enabled, :type => Boolean
13
16
  field :has_topics, :type => Boolean, :default => false
14
-
15
- # Indices ========================================================================================
16
-
17
- # Constants ======================================================================================
18
-
19
- # Scopes =========================================================================================
20
- scope :by_slug, lambda {|slug| {:where => {:slug => "#{slug}".gsub('//','/')} } }
21
-
22
- # Relationships ==================================================================================
23
17
  embeds_many :posts
18
+ has_many :blog_categories
19
+ has_many :post2s
24
20
 
25
21
  # Behavior =======================================================================================
26
-
27
22
  attr_accessor :desired_slug
28
23
  has_slug :desired_slug
29
24
 
30
- # Callbacks ======================================================================================
31
-
32
25
  # Validations ====================================================================================
33
-
34
26
  validates_presence_of :title, :description, :desired_slug
35
27
 
36
- # Class methods ==================================================================================
37
-
38
28
  # Instance methods ===============================================================================
29
+ def copy_posts
30
+ self.posts.each do |post|
31
+ p = Post2.create :slug => post.slug, :content => post.content, :tags => post.tags, :author => post.author, :published_at => post.published_at, :state => post.state, :publication_date => post.publication_date, :summary => post.summary
32
+ p.title = post.title
33
+ p.save
34
+ self.post2s << p
35
+ end
36
+ end
39
37
 
40
38
  def feed_address
41
39
  "/#{self.slug}/feed.rss"
42
40
  end
43
41
 
44
42
  def humanize_path
43
+ self.path
44
+ end
45
+
46
+ def path
45
47
  "/#{self.slug}/".gsub(/\/\//,'/').gsub(/\/\//,'/')
46
48
  end
47
49
 
@@ -60,5 +62,4 @@ class Blog
60
62
  def search(keyword)
61
63
  self.posts.published.where(:content => /#{keyword}/i)
62
64
  end
63
-
64
65
  end
@@ -0,0 +1,88 @@
1
+ class BlogCategory
2
+ include LuckySneaks::StringExtensions
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Mongoid::Tree
6
+
7
+ # Mongoid ========================================================================================
8
+ field :name
9
+ field :description
10
+ field :slug
11
+
12
+ # Behavior =======================================================================================
13
+ before_destroy :destroy_children
14
+ before_save :set_slug
15
+ validates_presence_of :name
16
+ validates_uniqueness_of :name
17
+
18
+ # Class methods: Overrides =======================================================================
19
+ def self.children
20
+ super.asc :name
21
+ end
22
+
23
+ def self.roots
24
+ super.asc :name
25
+ end
26
+
27
+ # Class methods ==================================================================================
28
+ def self.by_name value
29
+ self.find :first, :conditions => {:name => value}
30
+ end
31
+
32
+ def self.by_slug value
33
+ self.find :first, :conditions => {:slug => /^#{value}$/i}
34
+ end
35
+
36
+ def self.roots_with_posts
37
+ self.roots.asc(:name).select{ |c| c.has_posts? }
38
+ end
39
+
40
+ # Instance methods: Overrides ====================================================================
41
+ def children_with_posts
42
+ self.children.asc(:name).select{ |c| c.has_posts? }
43
+ end
44
+
45
+ def parent_id= value
46
+ self[:parent_id] = value == 'nil' ? nil : value
47
+ end
48
+
49
+ # Instance methods ===============================================================================
50
+ def has_posts?
51
+ ! self.all_posts.blank?
52
+ end
53
+
54
+ def parent_category=(name)
55
+ unless name.blank?
56
+ self.parent = BlogCategory.find_or_create_by :name => name
57
+ self.save
58
+ end
59
+ end
60
+
61
+ def all_posts
62
+ (self.posts + self.children.map{ |c| c.posts }).uniq.flatten
63
+ end
64
+
65
+ def posts
66
+ Blog.first.posts.select{ |p| p.blog_category_ids.include? self.id }
67
+ end
68
+
69
+ def subcategory?
70
+ ! self.root?
71
+ end
72
+
73
+ def url
74
+ "#{Blog.first.path}topics/#{self.slug}/"
75
+ end
76
+
77
+ private
78
+
79
+ # Returns an array of this category's and its ancestors' names.
80
+ def ancestors_and_self_names
81
+ self.ancestors_and_self.map{ |l| l.name }
82
+ end
83
+
84
+ # Returns the permalink for this locale.
85
+ def set_slug
86
+ self.slug = (ancestors_and_self_names * '/').to_url.gsub('-slash-', '/')
87
+ end
88
+ end
data/app/models/post.rb CHANGED
@@ -27,6 +27,7 @@ class Post
27
27
  index :state, :unique => false
28
28
 
29
29
  embedded_in :blog, :inverse_of => :posts
30
+ has_and_belongs_to_many :blog_categories
30
31
 
31
32
  # Behavior =======================================================================================
32
33
  attr_accessor :desired_slug
@@ -0,0 +1,126 @@
1
+ class Post2
2
+ include LuckySneaks::StringExtensions
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Tanker
6
+
7
+ # Constants ======================================================================================
8
+ STATES = ['draft', 'published']
9
+
10
+ # Scopes =========================================================================================
11
+ scope :drafts, :where => {:state => 'draft'}
12
+ scope :published, :where => {:state => 'published'}, :descending => :publication_date
13
+ scope :by_slug, lambda {|slug| {:where => {:slug.in => ["#{slug}".gsub('//','/'), "/#{slug}/".gsub('//','/')] } } }
14
+
15
+ # Mongoid ========================================================================================
16
+ field :title
17
+ field :content
18
+ field :tags, :type => Array
19
+ field :slug
20
+ field :author
21
+ field :published_at, :type => Date
22
+ field :state
23
+ field :publication_date, :type => DateTime
24
+ field :summary
25
+
26
+ index :slug, :unique => false
27
+ index :state, :unique => false
28
+
29
+ belongs_to :blog
30
+ references_and_referenced_in_many :blog_categories
31
+
32
+ # Behavior =======================================================================================
33
+ after_create :set_slug
34
+
35
+ # Tanker =========================================================================================
36
+ tankit 'idx' do
37
+ indexes :author
38
+ indexes :content
39
+ indexes :summary
40
+ indexes :tags
41
+ indexes :title
42
+ end
43
+
44
+ after_destroy :delete_tank_indexes
45
+ after_save :update_tank_indexes
46
+
47
+ # Validations ====================================================================================
48
+ # class DesiredSlugPresenceAndUniquenessValidator < ActiveModel::EachValidator
49
+ # def validate_each(object, attribute, value)
50
+ # object.desired_slug = object.title unless object.desired_slug
51
+ # if object.blog && object.blog.posts.map{|p| p.slug unless p == object}.include?(object.desired_slug)
52
+ # object.errors[attribute] << (options[:message] || "must be unique.")
53
+ # end
54
+ # end
55
+ # end
56
+
57
+ # validates :desired_slug, :desired_slug_presence_and_uniqueness => true
58
+ validates_presence_of :title
59
+ validates_presence_of :content
60
+
61
+ # Instance methods ===============================================================================
62
+ def draft?
63
+ self.state == 'draft' || self.state.nil?
64
+ end
65
+
66
+ def full_path
67
+ self.path
68
+ end
69
+
70
+ def humanize_path
71
+ "/#{self.slug}/".gsub(/\/\//,'/').gsub(/\/\//,'/')
72
+ end
73
+
74
+ def my_index
75
+ self.blog.posts.index(self)
76
+ end
77
+
78
+ def next_post
79
+ i = self.my_index + 1
80
+ i = 0 if i > (self.blog.posts.size - 1)
81
+ self.blog.posts[i]
82
+ end
83
+
84
+ def path
85
+ "#{self.blog.humanize_path}#{self.humanize_path}".gsub('//', '/')
86
+ end
87
+
88
+ def previous_post
89
+ i = self.my_index - 1
90
+ i = self.blog.posts.size - 1 if i < 0
91
+ self.blog.posts[i]
92
+ end
93
+
94
+ def publish!
95
+ self.update_attributes(:state => 'published', :publication_date => Time.zone.now)
96
+ end
97
+
98
+ def published?
99
+ self.state == 'published'
100
+ end
101
+
102
+ def search_description
103
+ self.summary.gsub(/<\/?[^>]*>/, '')[0..199].html_safe
104
+ end
105
+
106
+ def search_title
107
+ self.title
108
+ end
109
+
110
+ # Sets the slug for this locale. Slugs from the locale tree are used to build this locale's URL.
111
+ def set_slug
112
+ self.slug = self.title.to_s.to_url
113
+ end
114
+
115
+ def unpublish!
116
+ self.update_attributes :state => 'draft', :publication_date => nil
117
+ end
118
+
119
+ def state=(arg)
120
+ self[:state] = arg.downcase
121
+ end
122
+
123
+ def status
124
+ self.state ? self.state.capitalize : 'Draft'
125
+ end
126
+ end
@@ -0,0 +1,5 @@
1
+ <tr class="<%= cycle('odd', 'even') -%>">
2
+ <td><%= link_to blog_category.name, admin_blog_category_path(blog_category) -%></td>
3
+ <td><%= blog_category.description -%></td>
4
+ <td class="crud_links"><%= admin_crud_links(blog_category, [:show, :edit, :destroy]) -%></td>
5
+ </tr>
@@ -0,0 +1,30 @@
1
+ <% if @blog_category.errors.any? %>
2
+ <div id="error_explanation">
3
+ <h2>Please correct the following errors:</h2>
4
+ <% @blog_category.errors.to_a.in_groups(2, false).each do |group| %>
5
+ <ul>
6
+ <% group.each do |msg| %>
7
+ <li><%= msg %></li>
8
+ <% end %>
9
+ </ul>
10
+ <% end %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <fieldset class="form_container">
15
+ <%= legend_tag 'About This Blog Category' -%>
16
+
17
+ <div class="form_column">
18
+ <%= f.text_field :name -%>
19
+ <%= f.select :parent_id, [['(None)', 'nil']] + BlogCategory.all.map{ |c| [c.name, c.id] } -%>
20
+ </div>
21
+ <div class="form_column">
22
+ <%= f.text_area :description -%>
23
+ </div>
24
+ <div class="link_block">
25
+ <%= f.submit 'Save', :class => 'link_button' -%>
26
+ <%= link_to 'Cancel', admin_blog_categories_path, :class => 'link_button' -%>
27
+ </div>
28
+ </fieldset>
29
+
30
+ <br style="clear: both;" />
@@ -0,0 +1,5 @@
1
+ <% set_title 'Edit a Blog Category' %>
2
+
3
+ <%= form_for @blog_category, :url => admin_blog_category_url(@blog_category) do |f| %>
4
+ <%= render :partial => 'form', :locals => {:f => f } %>
5
+ <%- end -%>
@@ -0,0 +1,18 @@
1
+ <%- set_title 'Blog Categories' -%>
2
+
3
+ <table class="standard">
4
+ <tr>
5
+ <th><%= sort_link('blog_categories', 'name', params) -%></th>
6
+ <th><%= sort_link('blog_categories', 'description', params) -%></th>
7
+ <th> </th>
8
+ </tr>
9
+ <%- if @blog_categories.empty? -%>
10
+ <tr><td colspan="4">No blog categories have been created yet.</td></tr>
11
+ <%- else -%>
12
+ <%= render :partial => 'blog_category', :collection => @blog_categories -%>
13
+ <%- end -%>
14
+ </table>
15
+
16
+ <div class="link_block attached table">
17
+ <%= link_to 'New Blog Category', new_admin_blog_category_path, :class => 'link_button' -%>
18
+ </div>
@@ -0,0 +1,5 @@
1
+ <% set_title 'Create a New Blog Category' %>
2
+
3
+ <%= form_for @blog_category, :url => admin_blog_categories_url do |f| %>
4
+ <%= render :partial => 'form', :locals => {:f => f } %>
5
+ <%- end -%>
@@ -0,0 +1,41 @@
1
+ <%- set_title 'Blog-Category Details' -%>
2
+
3
+ <div class="form_container">
4
+ <%= legend_tag 'About This Blog Category' -%>
5
+ <div class="form_column">
6
+ <%= faux_field 'Name', @blog_category.name %>
7
+ <%= faux_field 'Description', @blog_category.description %>
8
+ </div>
9
+ <div class="form_column">
10
+ <%- if @blog_category.parent_id -%>
11
+ <%= faux_field 'Parent', @blog_category.parent.name %>
12
+ <%- end -%>
13
+ </div>
14
+ <br style="clear: both;" />
15
+ </div>
16
+
17
+ <div class="link_block attached">
18
+ <%= link_to 'Edit', edit_admin_blog_category_path(@blog_category), :class => 'link_button' -%>
19
+ <%= link_to 'Back', admin_blog_categories_path, :class => 'link_button' -%>
20
+ </div>
21
+
22
+ <div class="form_container">
23
+ <%= legend_tag 'Subcategories' -%>
24
+
25
+ <%- if @blog_category.children.empty? -%>
26
+ <p><em>No subcategories have been created yet.</em></p>
27
+ <%- else -%>
28
+ <table class="standard">
29
+ <tr>
30
+ <th><%= sort_link('blog_categories', 'name', params) -%></th>
31
+ <th><%= sort_link('blog_categories', 'description', params) -%></th>
32
+ <th> </th>
33
+ </tr>
34
+ <%= render :partial => 'blog_category', :collection => @blog_category.children -%>
35
+ </table>
36
+ <%- end -%>
37
+ </div>
38
+ <div class="link_block attached">
39
+ <%= link_to 'Add Subcategory', new_admin_blog_category_path(:parent_id => @blog_category.id), :class => 'link_button' -%>
40
+ <%= link_to 'Back', admin_blog_categories_path, :class => 'link_button' -%>
41
+ </div>
@@ -9,11 +9,9 @@
9
9
  <li><%= msg %></li>
10
10
  <% end %>
11
11
  </ul>
12
- <br style="clear: both;" />
13
12
  <% end %>
14
13
  <br style="clear: both;" />
15
14
  </div>
16
- <br style="clear: both;" />
17
15
  <% end %>
18
16
 
19
17
  <fieldset class="form_container">
@@ -44,17 +42,30 @@
44
42
  <div class="form_column">
45
43
  <%= f.cktext_area :content, :toolbar => 'Full', :id => 'content_field', :height => '500px' -%>
46
44
  </div>
45
+ </fieldset>
46
+
47
+ <fieldset class="form_container">
48
+ <%= legend_tag 'Categories' -%>
49
+
47
50
  <div class="form_column">
51
+ <%- BlogCategory.roots.asc(:name).each do |blog_category| -%>
52
+ <label><%= blog_category.name -%></label><br />
53
+ <%- blog_category.leaves.in_groups(3, false).each do |group| -%>
54
+ <div class="three_column">
55
+ <%- group.each do |child| -%>
56
+ <%= check_box_tag 'post[blog_category_ids][]', child.id, @post.blog_category_ids.include?(child.id), :class => 'category_selector', :id => child.id -%><label for="<%= child.id -%>" class="inline"><%= child.name -%></label>
57
+ <%- end -%><br />
58
+ </div>
59
+ <%- end -%><br />
60
+ <br />
61
+ <%- end -%>
48
62
  </div>
49
63
  <div class="link_block">
50
64
  <%= f.submit 'Publish', :class => 'link_button' -%>
51
65
  <%= f.submit 'Save as Draft', :class => 'link_button' -%>
52
66
  <%= f.submit 'Preview', :class => 'link_button' -%>
53
67
  <%- if @post.new_record? -%>
54
- <%= link_to 'Cancel', admin_blog_posts_path(@blog), :class => 'link_button' -%>
55
- <%- else -%>
56
- <%= link_to 'Cancel', admin_blog_post_path(@blog, @post), :class => 'link_button' -%>
68
+ <%= link_to 'Cancel', @post.new_record? ? admin_blog_posts_path(@blog) : admin_blog_post_path(@blog, @post), :class => 'link_button' -%>
57
69
  <%- end -%>
58
70
  </div>
59
71
  </fieldset>
60
-
@@ -29,10 +29,17 @@
29
29
  <%= @post.content.html_safe -%>
30
30
  </div>
31
31
  <%- end -%>
32
+ </fieldset>
33
+
34
+ <fieldset class="form_container">
35
+ <%= legend_tag 'Categories' -%>
36
+ <ul>
37
+ <%- @post.blog_categories.each do |c| -%>
38
+ <li><%= link_to c.name, c.url -%></li>
39
+ <%- end -%>
40
+ </ul>
32
41
  <div class="link_block">
33
42
  <%= link_to 'Edit', edit_admin_blog_post_path(@blog, @post), :class => 'link_button' -%>
34
43
  <%= link_to 'Back', admin_blog_posts_path(@blog), :class => 'link_button' -%>
35
44
  </div>
36
- </fieldset>
37
-
38
-
45
+ </fieldset>
@@ -0,0 +1,16 @@
1
+ <%- set_title "Topics in #{Blog.first.title}" -%>
2
+
3
+ <%- if @blog_categories.empty? -%>
4
+ <p>
5
+ <em>No blog categories found.</em>
6
+ </p>
7
+ <%- else -%>
8
+ <ul>
9
+ <%- @blog_categories.each do |blog_category| -%>
10
+ <li>
11
+ <%= link_to blog_category.name, blog_category.url -%><br />
12
+ <%= blog_category.description -%>
13
+ </li>
14
+ <%- end -%>
15
+ </ul>
16
+ <%- end -%>
@@ -0,0 +1,25 @@
1
+ <%-
2
+ set_title "#{Blog.first.title}: #{@blog_category ? @blog_category.name : ''}"
3
+ @meta_description = @blog_category.description
4
+ -%>
5
+
6
+ <%- unless @blog_category.all_posts.empty? -%>
7
+ <ul>
8
+ <%- @blog_category.all_posts.each do |post| -%>
9
+ <li>
10
+ <%= link_to post.title, post.path -%><br />
11
+ <%= post.summary -%>
12
+ </li>
13
+ <%- end -%>
14
+ </ul>
15
+ <%- end -%>
16
+
17
+ <%- unless @blog_category.children.empty? -%>
18
+ <p>Subcategories</p>
19
+
20
+ <ul>
21
+ <%- @blog_category.children.each do |category| -%>
22
+ <li><%= link_to category.name, category.url -%></li>
23
+ <%- end -%>
24
+ </ul>
25
+ <%- end -%>