SimpleHashtag
Ruby gem for Rails that parses, stores, retreives and formats hashtags in your model.
Simple Hashtag is a mix between–well–hashtags, as we know them, and categories. It will scan your Active Record attribute for a hashtag, store it in an index, and display a page with each object containing the tag.
It's simple, and like all things simple, it can create a nice effect, quickly.
Installation
Add this line to your application's Gemfile:
gem 'simple_hashtag'
And execute:
$ bundle
Then you have to generate the migration files:
$ rails g simple_hashtag:migration
This will create two migration files, one for the hashtags
table and one for the hashtagging
table.
You will need to run rake db:migrate
to actually create the tables.
Optionnally, you can create views, if only to guide you through your own implementation:
$ rails g simple_hashtag:views
This will create a basic controller, a short index view and a small helper. It assume your views follow the convention of having a directory named after your model's plural, and a partial named after your model's name.
app
|-- views
| |-- posts
| | |-- _post.html.erb
Usage
Just add include SimpleHashtag::Hashtaggable
in your model.
Simple Hasthag will parse the body
attribute by default:
class Post < ActiveRecord::Base
include SimpleHashtag::Hashtaggable
end
If you need to parse another attribute instead,
add hashtaggable_attribute
followed by the name of your attribute, i.e.:
class Picture < ActiveRecord::Base
include SimpleHashtag::Hashtaggable
hashtaggable_attribute :caption
end
From here on, if your text contains a hashtag, say #RubyRocks, Simple Hasthag will find it, store it in a table and retreive it and its associated object if asked. Helpers are also available to create a link when displaying the text.
Controller and Views
If you don't want to bother looking at the genrerated controller and views, here is a quick peek. In a Controller, display all hashtags, or search for a Hashtag and its associated records:
class HashtagsController < ApplicationController
def index
@hashtags = SimpleHashtag::Hashtag.all
end
def show
@hashtag = SimpleHashtag::Hashtag.find_by_name(params[:hashtag])
@hashtagged = @hashtag.hashtaggables if @hashtag
end
end
The views could resemble something like this:
Index:
<h1>Hashtags</h1>
<ul>
<% @hashtags.each do |hashtag| %>
<li><%= link_to hashtag.name, hashtag_path(hashtag.name) %></li>
<% end -%>
</ul>
Show:
<h1><%= params[:hashtag] %></h1>
<% if @hashtagged %>
<% @hashtagged.each do |hashtagged| %>
<% view = hashtagged.class.to_s.underscore.pluralize %>
<% partial = hashtagged.class.to_s.underscore %>
<%= render "#{view}/#{partial}", {partial.to_sym => hashtagged} %>
<% end -%>
<% else -%>
<p>There is no match for the <em><%= params[:hashtag] %></em> hashtag.</p>
<% end -%>
In the gem it is actually extracted in a helper.
Routes
If you use the provided controller and views, the generator will add two routes to your app:
get 'hashtags/', to: 'hashtags#index', as: :hashtags
get 'hashtags/:hashtag', to: 'hashtags#show', as: :hashtag
The helper generating the link relies on it.
Spring Cleaning
There is a class method SimpleHashtag::Hashtag#clean_orphans
to remove unused hashtags from the DB.
It is currently not hooked, for two reasons:
- It is not optimised at all, DB-wise.
- Destructive method should be called explicitly.
Knowing all this, you can hook it after each change, or automate it with a Cron job, or even spring-clean manually once in a while.
Improvements for this method are listed in the Todo section below.
Gotchas
Association Query
The association between a Hashtag and your models is a polymorphic many-to-many.
The object returned by the query is an array, not an Arel query, so you can't chain (i.e.: to specify the order), and should do it by hand:
hashtag = SimpleHashtag.find_by_name("RubyRocks")
posts_and_picts = hashtag.hattaggables
posts_and_picts.sort_by! { |p| p.created_at }
find_by
To preserve coherence, Hashtags are stored downcased.
To ensure coherence, they are also searched downcased.
Internally, the model overrides find_by_name
to perform the downcase query.
Should you search Hashtags manually you should use the SimpleHashtag::Hashtag#find_by_name
method, instead of SimpleHashtag::Hashtag.find_by(name: 'RubyRocks')
ToDo
Simple Hashtag is in its very early stage and would need a lot of love to reach 1.0.0. Among the many improvement areas:
- Make the Regex that parses the text for the hashtag much more robust. This is how Twitter does it: https://github.com/twitter/twitter-text-rb/blob/master/lib/twitter-text/regex.rb (Yes, that's 362 lines of regex. Neat.)
- Allow for multiple hashtagable attributes on the same model
- Allow a change in the name of the classes and tables to avoid conflicts
- Make it ORM agnostic
- Add an option so the helper displays the
#
or not, global or per model - Add an option to clean orphans after each edit or not
- Improve the
SimpleHashtag::Hashtag#clean_orphans
method to do only one SQL query
Contributing
All contributions are welcome. I might not develop new features (unless a project does require it), but I will definitely merge any interesting feature or bug fix quickly.
You know the drill:
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Add passing tests
- Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request