Posted by Jim Morris
Sat, 23 Jun 2007 21:02:49 GMT
This is a simple one.
I use the excellent acts_as_taggable plugin, and I wanted to have a
tag cloud like everyone does.
Of course I have different Models that can be tagged, and I want to
keep everything DRY, so I created a helper and put the following in
the application_helper.rb file
def tag_cloud(model, title= nil)
m= model.to_s.camelcase.constantize
plural= model.to_s.capitalize.pluralize
title ||= plural
tags= m.tag_counts(:order => 'tags.name')
gen= ""
unless tags.empty?
urlmeth= "tagged_#{model.to_s.pluralize}_path".to_sym
gen= "<div class=\"tagcloud\">"
gen += "<h3>#{title}</h3>"
tags.each do |t|
gen += "<span style=\"font-size:#{calc_size(t.count)}%\">"
gen += link_to(h(t.name), self.send(urlmeth, :tag => t.name))
gen += "</span> "
end
gen += "</div>"
gen += "<hr/>"
end
gen
end
This allows me to have multiple tag clouds for the different models,
see snowdogsr.us on the left bar at the bottom.
The font is bigger for the more popular tags, and you can click on any
tag to get a listing of all matching articles.
I use RESTful routing so I added this to routes.rb
map.resources :places, :collection => { :tagged => :get }
which creates a named route tagged_places_path which returns an
index with the places tagged with the tag that was clicked.
The places_controller method looks like this...
def tagged
@places= Place.find_tagged_with(params[:tag], :order => "updated_at DESC")
@filter= "Tagged with #{params[:tag]}"
respond_to do |format|
format.html { render :action => 'index' }
format.xml { render :xml => @places.to_xml }
end
end
Posted in Rails | Tags acts_as_taggable, rails, tagcloud | no comments | no trackbacks
Posted by Jim Morris
Sat, 23 Jun 2007 20:49:00 GMT
In
part 1
I outlined my project to implement snowdogsr.us
a social networking site for snow dogs.
I am pleased to announce that version 1 of this site is up, however I
had to make some trade-offs to get it up this far. I needed to
prioritize my goals and just get the essentials implemented.
So the ability to create Posts, Events and Places, and add photos of
your dogs was goal number 1. The linking to friends was postponed,
although most of the logic was implemented, I couldn't figure out how
to expose that in an intuitive UI.
The first thing I wanted was to be able to rate the various Posts
and Places people entered, and how better to do that than to copy the
Netflix and Google groups method of showing 5 stars and clicking on
them to rate the item.
To that end some googling found an
excellent CSS method
to do this,
all I had to do was integrate it into Rails. I had some extra business
rules I needed to deal with
- Only people logged in can rate an article
- The creator of the article can not rate their own article
- If the rating is readonly then it still needs to be displayed as
stars, just not clickable ones.
Out of the box the Stars were always clickable, so I needed a way to
show readonly stars when the user was not allowed to rate the article,
luckily the above referenced blog explained how to do that in the
blogs comments.
I'm using the acts_as_rated plugin, which works fine so long as you
don't try using the optimized method of creating a stats table, that
method didn't seem to work, and for now I'll just let the SQL do the
math.
As I have many models that acts_as_rated I needed to write a helper to
display the stars, and handle the rating clicks, and the business
logic.
The helpers look a little ugly as you need to figure out what the
parent model is.
So here is the code that goes in application_helper.rb
def star_rating(rating, obj_type, id, allow_rate= true)
per= rating > 0 ? (rating/5.0)*100 : 0;
url_meth= "rate_#{obj_type}_path".to_sym
if allow_rate
links= [
link_to('1', send(url_meth, {:id => id, :rate => 1}), :method => :put, :class => "one-star", :title =>"1 star out of 5"),
link_to('2', send(url_meth, {:id => id, :rate => 2}), :method => :put, :class => "two-stars", :title =>"2 stars out of 5"),
link_to('3', send(url_meth, {:id => id, :rate => 3}), :method => :put, :class => "three-stars", :title =>"3 stars out of 5"),
link_to('4', send(url_meth, {:id => id, :rate => 4}), :method => :put, :class => "four-stars", :title =>"4 stars out of 5"),
link_to('5', send(url_meth, {:id => id, :rate => 5}), :method => :put, :class => "five-stars", :title =>"5 stars out of 5")]
end
r= " "
r += "<span class=\"inline-rating\"> <ul class=\"star-rating\"> <li class=\"current-rating\" style=\"width:#{per}%;\"></li>"
if allow_rate
(0..4).each do |i|
r += "<li>#{links[i]}</li>"
end
end
r += "</ul></span>"
r += '<span class="prompt">Click star to rate</span>' if allow_rate
r
end
def render_rating(o, allow_rating=true)
can_rate= allow_rating && logged_in? && ! o.created_by?(current_user)
o_type= o.class.to_s.downcase
if o.rated?
r= "Rating #{o.rating_average}/5 by #{pluralize(o.rated_count, 'person')}"
r += star_rating(o.rating_average, o_type, o.id, can_rate)
elsif can_rate
r= "Not yet Rated"
r += star_rating(0, o_type, o.id, can_rate)
else
r= ""
end
r
end
The render_rating method is what you call from your view, the
star_rating method generates the HTML.
(I presume you included the star_rating.css which is available from
the above mentioned blog, and the acts_as_rated plugin.)
The render_rating method takes the model object being rated, and an
optional override to allow_rating or not (which determines if the
stars are clickable or not).
I include some text saying what the rating is and how many people have
rated the article, followed by the stars showing the rating.
I have all my ratings set to 1 to 5, YMMV.
In my view (HAML) code I have...
= render_rating(place)
Which will render the rating for the current place article.
the Place model has this code...
acts_as_rated(:rating_range => 1..5, :rater_class => 'Person')
because I have a Person class as the rater rather than the default
User.
The rating percentage is calculated using..
per= rating > 0 ? (rating/5.0)*100 : 0;
Which is used by the CSS to highlight the stars correctly.
I use RESTful routing so the links are generated using named
routes, however as the named route will be different depending on the
Model object being rated I need to use this code...
map.resources :places, :member => { :rate => :put }
def rate
@place = Place.find(params[:id])
rating= params[:rate].to_i
@place.rate(rating, current_user.person)
respond_to do |format|
format.html { redirect_to :back }
format.xml { head :ok }
end
end
url_meth= "rate_#{obj_type}_path".to_sym
...
send(url_meth, {:id => id, :rate => 1})
One last thing slightly on topic, I added the following to the
acts_as_rated.rb file so I could get a list of the top rated articles,
seemed like a logical thing that anyone would want to do ;)
def find_top_rated(limit=nil)
rating_class = acts_as_rated_options[:rating_class].constantize
base_sql = <<-EOS
select #{table_name}.*,COALESCE(average,0) AS rating_average from #{table_name} left outer join
(select avg(rating) as average, rated_id
from #{rating_class.table_name}
where rated_type = '#{class_name}'
group by rated_id) as rated
on rated_id=id
order by rating_average DESC
EOS
base_sql += " LIMIT #{limit}" if limit
find_by_sql base_sql
end
I hope this helps others trying the same thing, as always comments and
code improvements always welcome.
Posted in Rails | Tags acts_as_rated, rails, rating, stars | 11 comments | no trackbacks
Posted by Jim Morris
Thu, 31 May 2007 20:27:00 GMT
I have been using JEdit more and more for my rails development, I have
gone back and forth between it and Epsilon, however JEdit is starting
to win out. I have upgraded to the latest pre version (4.3pre9).
I have modified a number of macros to do my bidding, and I dumped the
Ruby Plugin because I kept running into things it did that I disliked,
and it still seems a little buggy.
The best thing I did was update the ruby.xml Language mode to fully
indent properly, like unindent end else rescue etc, and do this when
you type those words. This is now possible with some new features in
the 4.3pre9 series.
I also wrote a HAML language mode.
The plugins I currently use are...
- Buffer Selector
- BufferTabs
- Common Controls
- Console
- CssEditor
- CtagsSideKick
- ErrorList
- Highlight
- Info Viewer
- Latest Version
- Log Viewer
- MacroManager
- OpenIt
- Project Viewer
- QuickNotepad
- RecentBufferSwitcher
- SideKick
- SuperAbbrevs
- SwitchBuffer
- Tags
- TextTools
- XercesPlugin
The macros I have downloaded, modified or written to help with rails development
are...
- Expand_Hash.bsh - My macro to expand # to #{} when in a string
- Go_to_Ruby_method_v0.5.bsh - Downloaded from the macromanager
- Open_Related_File.bsh - Downloaded from the macromanager
- Search_Ruby_documentation - A modified version of the one I
downloaded, modified to use qri, and select from a list if multiple hits
- Run_Test_Case.bsh - My macro to run a specific test case or
specification. Bind it to a key (I use Shift-F11), put the cursor in a test case or specification and type the shortcut, and that specific test case will run, the results going to the console plugin.
- Select_Super_Abbrevs.bsh - My macro to select from a list of matching
SupperAbbrevs
- Find_Next_Selected.bsh - Downloaded from the macromanager
I have linked the ones I have written or modified so you can download
them if you like.
Read more...
Posted in Rails, JEdit | Tags editor, jedit, macros, rails | 4 comments | no trackbacks
Posted by Jim Morris
Thu, 31 May 2007 19:59:01 GMT
I have started a new project for myself and a few friends,
Snow Dogs R Us. This site just went live!
(on 6/20/2007). It is a full blown Web2.0 (insert other buzz words
here), social networking site for Snow Dogs and their (human) parents.
It is certainly a challenging project and taking much longer than I
anticipated.
The basic features include sign-up, forgot password, login, optional
profiles for the user and their snow dogs, and all the usual stuff
there.
The advanced features include linking to friends for both the humans
and the pets, so the humans can link to other human friends, and the
pets can link to their friends too (Ala MySpace and Facebook etc). I
want to eventually have a LinkedIn type of network
as well, so you can see how many degrees you are away from other
people, this is a real challenge to do in Rails, and I will probably
actually write a Java engine to do that, as it is pretty compute and
memory intensive. (Its not really an essential feature of the site,
but I like technical challenges).
Of course there will be the ability to comment on everything, rate
everything and tag everything, and there has to be the obligatory
Mashup with Flickr and YouTube for all those cute photos and videos of
our pets. (What other Web2.0 feature have I missed?).
Luckily the Rails community
has already made available a lot of plugins we can use. I currently
use these plugins...
active_scaffold - for the admin interface
acts_as_taggable_on_steroids - for the tagging
haml - For all my HTML structure needs
test_spec_on_rails - a better way to test
acts_as_commentable - pretty basic comments
cssformbuilder - highly customized, I need to make my mods available
restful_authentication - for authentication, I added the forgot
password hack slightly modified
transactional_migrations - Ideal for Postgresql
acts_as_rated - I started using acts as rateable but switched
calendar_date_select - for inputting dates
sexy_migrations - Makes migrations so much easier
will_paginate - For paginating those pages
attachment_fu - For uploading the pictures (and FreeImage and ImageScience for scaling them)
I looked at
has_many_friends
but it was so close to what I had already done I just decided to keep
my version, plus I want to add that degrees of freedom stuff later on.
There have been many posts about the friendships links in Rails, some
decided to simply have a HABTM with two entries to make the link
symmetric, I don't like wasting space so my solution uses a single
entry for their friendship, and some SQL to find friends regardless of
whether they invited you or you invited them. I'll blog about that
later when I optimize it a bit more, as friendship links can grow
exponentially I think it is prime candidate for optimization both in
the amount of space is uses in the database and for database access,
and simple has_many :through construct work but in this case will be
very inefficient.
Another feature is the ability to create events and invite everyone or
just your friends (think evite for dogs), this is relatively simple
except keeping track of the invitations and who accepted etc. I also
link to Places, because an event is usually at a place, and of course
the place needs to have directions, so a link to Google maps is in
order there. Of course a place needs to be taggable and commentable and
rateable.
Lastly is the ability to make recommendations. In the dog community we
share advice on Vets, food, dog friendly places to visit like
restaurants, parks, beaches etc. These all need to be taggable,
rateable and commentable and of course searchable. I've gone back and
forth on how to implement that, right now it is a simple table called
inputs (user input), which is taggable, commentable, and rateable. It
relies on people tagging properly, IE tag with restaurant for dog
friendly restaurants, and vet for vet recommendations etc. I
originally had a belongs_to input_type with predefined categories,
but decided to trust the user and just allow tags. We'll see how that
works. Maybe the tag input field needs to auto_complete with a list of
current tags so people are more inclined to tag with already used
tags.
My biggest problem is the web design or look and feel, I am a
programmer not a designer, so I can solve the complex programming
issues and implement all the functionality, but I get really stuck
when I try to figure out how to present that information. If this was
a "for pay" project I would hire a web designer, but it is definitely
a not-for-profit project so I can't afford that, so I'll struggle with
the design.
I'll post more entries on some of my solutions to the technical
problems as I go.
A few things I have learned, is to use
piston
for managing plugins, and avoid alpha plugins :)
Posted in Rails | Tags networking, rails, social, web2.0 | 9 comments | no trackbacks