Hacking Typo 5.1.2
I am creating a website that required some data tables. Typo does not have any built in functionality to accomplish this, but since it is just a rails app, it is fairly easy to add such functionality. So here is what I wanted to do and how I accomplished it.
1) Add the tables I need to typo
2) Create the admin screens to add/update/delete data from these tables and integrate them into Typos admin interface.
3) Pull the data from these tables into blog posts in a semi dynamic fashion.
Read on for how I accomplished these tasks.
1) Add tables.
Since Typo has not adopted the Rails 2 convention of using time stamped migrations, I just did this by hand – not a big deal.
2) Create admin screens.
I will assume that you know how to create normal rails models/controllers and views to match your tables. I just did it by using the built in rails script/generate scaffold routine. They don’t need to be fancy, this is just for admin use. Of course when you create these models/controllers and views they have no security and are not part of the Typo interface. When you have them working normally to your satisfaction here is how to add them to Typo admin interface.
a) Move the controller to the admin app/controller/admin directory. Edit the controller code and Change the class declaration from;
class MyController < ApplicationController
to
class Admin::MyController < Admin::BaseController
of course MyController is the name of your controller.
This controller now participates in the Typo admin security system.
To add this controller to the admin interface you will need to edit config/initializers/access_rules.rb
There you will find the AccessConrol.map and a series of map.project_modules below. These modules correspond to the new AccessControl system and to the Admin Menus.
The data tables I added would be considered content so I added the controllers I created to the content map. Just add a line for each controller you wish to access. Now your controller is callable for the admin interface. Note if you don’t add the controller here, the security system will not let you access the controller.
b) Now we have to get the views into the admin area so the controller can find them. Like the controller just move your view subdirectory to the view admin subdirectory. Your views will now use the admin layout. The final change you should make is to edit your views to further integrate them into the admin interface. First set the @page_heading variable and add the code to pull in the appropriate submenu. The beginning of each of my views looks like this:
<% @page_heading = "My Page Heading" %>
<% subtabs_for(:content) %>
You should change the :content to match the menu you placed your controller under.
All done.
3) Pull the data from these tables into blog posts in a semi dynamic fashion.
Now for the harder part – of course since I already did it, it can be easy for you. I created a textfilter macro that allows me to call rails methods in which I create the view code I want to display for the database record. I called this macro RunCode. So in the blog post I can simply enter <typo:runcode>some_method_name(args)</typo:runcode>. When the post is published Typo calls the method and inserts the result into the blog post at that point. If you are interested in the macro let me know and I will make it available.
Have fun hacking Typo.
Ruby on Rails on Debian Etch with fastcgi and Phusion Passenger
Debian Etch has fairly current versions of Ruby and Rails that can be installed through the package management system. However, the installation of the Gem package manager does not know anything about the Rails installation. This will break software that checks Gem for Rails information. So here is my method of getting Ruby on Rails running the way I want on Debian Etch.
I assume in the following that you have a Debian Etch server up and running with Apache2 and Mysql. The bold lines can be copy and pasted into your terminal window.
1) Install the normal Debian packages for ruby and rails.
aptitude install ruby libzlib-ruby rdoc irb rubygems rails eruby
2) Now we will install fastcgi and the ruby mysql bindings. To do this you will have to edit you /etc/apt/sources.list and add the contrib and non-free options. Your main deb line should look like this:
deb http://ftp.debian.org/debian/ etch main contrib non-free
Now we do the install
aptitude update;aptitude install libapache2-mod-fastcgi libmysql-ruby apache2-prefork-dev
3) Next we update some apache modules and restart apache.
a2enmod ssl
a2enmod rewrite
a2enmod suexec
a2enmod include
a2enmod fastcgi
/etc/init.d/apache2 force-reload
4) Now we get down to the business of updating ruby and rails.
First update gem
gem update –system
This will break gem. To fix it do
cp /usr/bin/gem1.8 /usr/bin/gem
Now update rails
gem update -d rails
if you need an older version of rails to be available then install it like this
gem install -d -v1.1.6 rails
This will install rails 1.1.6 (the version installed by debian package manager) and make it know to gem.
4) Now if you want the latest rails speed up install Phusion Passenger
gem install passenger
passenger-install-apache2-module
The passenger install is good about telling you if you don’t have everything it needs and how to get it.
Thats it you should have a fully updated and functioning Ruby on Rails system on Debian Etch.
Rails Date Parser 1
So this is a simple to install simple to use extension to the ruby Date class. To use it simply copy the following code to a file named dateparse.rb and place it in the lib directory in your rails app. Then open up the application controller and and put require 'dateparse' at the top of the file. (Note: put it before the class declaration).
Then in your controller or view code use Date.parse_date() in place of Date.parse(). It recognizes the following formats
YYYY-mm-dd ISO Standard
mm-dd-YYYY American
mm-dd Assume current year
mm/dd/YYYY American standard
mm/dd
mm.dd.yyyy European
dd.mm.yyyy
dd mmmmm yyyy example: 22 may 1945 or 22nd may 1945
dd mmmmm example: 22 may (current year)
mmmm dd yyyy example: may 22 1945 or may 22nd 1945
mmmm dd, yyyy example: may 22, 1945
mmmm dd example may 22 or may 22nd
dd example 4 or 4th
today (tod) or now common names for days and abbreviations
tomorrow (tom)
yesterday (yes)
Next Week, Last Week, Next Month, Last Month, Next Year, Last Year
+n[units] or -n[units]: Date from today: examples: '+22', '+22 days',
'+22d', '-4 weeks', '-4w', '-4week', '+6 months', '+6m', '+6month',
'-2 years', '-2y'
# This library adds the parse_date method to the Standard Ruby Date Class. # Its purpose is to make the entry of user friendly date formates painless # in Rails applications. # # My motivation for making this was the desire for user friendly date input # in rails applications without resorting to javascript or some kind of widget. # # To use: drop this file into ./lib, require it into the application # controller. # # Written by Tom WillettHope this helps someone.# Version 1.0 # # with a tip of the hat to # Stuart Rackham # and Datetime ToolBocks # # Date Formats recognized by this method: # YYYY-mm-dd ISO Standard # mm-dd-YYYY American # mm-dd Assume current year # mm/dd/YYYY American standard # mm/dd # mm.dd.yyyy European # dd.mm.yyyy # dd mmmmm yyyy example: 22 may 1945 or 22nd may 1945 # dd mmmmm example: 22 may (current year) # mmmm dd yyyy example: may 22 1945 or may 22nd 1945 # mmmm dd, yyyy example: may 22, 1945 # mmmm dd example may 22 or may 22nd # dd example 4 or 4th # today (tod) or now common names for days and abbreviations # tomorrow (tom) # yesterday (yes) # Next Week, Last Week, Next Month, Last Month, Next Year, Last Year # +n[units] or -n[units]: Date from today: examples: '+22', '+22 days', # '+22d', '-4 weeks', '-4w', '-4week', '+6 months', '+6m', '+6month', # '-2 years', '-2y' # class Date # Parse date string with one of the following formats: # # The string argument is first converted to a string with #to_s. # Returns nil if passed nil or an empty string. # Raises ArgumentError if string can't be parsed. # def self.parse_date(org_string) string = org_string.to_s.strip.downcase return nil if org_string.empty? today = Date.today if string =~ /^(\d{4})-(\d{1,2})-(\d{1,2})$/ # YYYY-mm-dd format. ISO Standard year = $1.to_i month = $2.to_i day = $3.to_i elsif string =~ /^(\d{1,2})-(\d{1,2})-(\d{4})$/ # mm-dd-yyyy format year = $3.to_i month = $1.to_i day = $2.to_i elsif string =~ /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/ # mm.dd.yyyy or dd.mm.yyyy format year = $3.to_i if $1.to_i > 12 day = $1.to_i month = $2.to_i else day = $2.to_i month = $1.to_i end elsif string =~ /^(\d{1,2})-(\d{1,2})$/ # mm-dd format year = today.year.to_i month = $1.to_i day = $2.to_i elsif string =~ /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/ # mm/dd/yyyy format year = $3.to_i month = $1.to_i day = $2.to_i elsif string =~ /^(\d{1,2})\/(\d{1,2})$/ # mm/dd format year = today.year month = $1.to_i day = $2.to_i elsif string =~ /^(\d{1,2})(?:st|nd|rd|th)?,? (\w+) (\d{2,4})$/ # 'd mmmm yyyy' format date_array = ParseDate.parsedate("#{$1} #{$2} #{$3}", true) year = date_array[0].to_i month = date_array[1].to_i day = date_array[2].to_i elsif string =~ /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{2,4})$/ # 'mmmm d yyyy' format date_array = ParseDate.parsedate("#{$2} #{$1} #{$3}", true) year = date_array[0].to_i month = date_array[1].to_i day = date_array[2].to_i elsif string =~ /^(\d{1,2})(?:st|nd|rd|th)?,? (\w+)$/ # 'd mmmm' format y = today.year.to_s date_array = ParseDate.parsedate("#{$1} #{$2} #{y}", true) year = date_array[0].to_i month = date_array[1].to_i day = date_array[2].to_i elsif string =~ /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,?$/ # 'mmmm d' format y = today.year.to_s date_array = ParseDate.parsedate("#{$2} #{$1} #{y}", true) year = date_array[0].to_i month = date_array[1].to_i day = date_array[2].to_i elsif string =~ /^(\d{1,2})(st|nd|rd|th)?$/ # 'd' format year = today.year month = today.month day = $1.to_i elsif string =~ /^tod[\w]+$|^now$/ # today or now year = today.year month = today.month day = today.day elsif string =~ /^tom/ # tomorrow today += 1 year = today.year month = today.month day = today.day elsif string =~ /^yes/ # yesterday today -= 1 year = today.year month = today.month day = today.day elsif string =~ /^(next|last)\s(week|month|year)$/ # Next Week, Last Week, Next Month, Last Month, Next Year, Last Year year = today.year month = today.month day = today.day case $2 when 'week' if $1 == 'next' today += 7 else today -= 7 end year = today.year month = today.month day = today.day when 'month' month = (($1 == 'next') ? (month + 1) : (month - 1)) if month > 12 month = 1 year += 1 elsif month < 1 month = 12 year -= 1 end when 'year' year = (($1 == 'next') ? (year + 1) : (year - 1)) end elsif string =~ /^([+-]\d+)(?:\s*(d|days?|w|weeks?|m|months?|y|years?))?$/ # Date intervals. year = today.year month = today.month day = today.day n = $1.to_i units = $2 || 'days' case units.first when 'd' today = today + n year = today.year month = today.month day = today.day when 'w' today = today + n*7 year = today.year month = today.month day = today.day when 'm' sign = n <=> 0 month = today.month + sign * (n.abs % 12) year = today.year + sign * (n.abs / 12) if month <1 month += 12 year -= 1 elsif month > 12 month -= 12 year += 1 end when 'y' year = year + n end else raise ArgumentError.new("\"#{org_string}\" is not recognized as a Date") end begin result = Date.new(year,month,day) rescue raise ArgumentError.new("\"#{org_string}\" is not recognized as a Date") end result end end
link_to_sort
I have created a little view helper that I use in my ROR applications. I call it link_to_sort. What it does is create a clickable header on a list page that sorts the list by that header.
I add it to the application helper and make minimal changes to the controller list action and add one routine to the application controller to make it work.
def link_to_sort(title, field_name)
txt= '<a href="/' + @controller.controller_name + '/' + @controller.action_name + '?direction=' + @ndir + '&order=' + field_name + '">' + title + '</a>'
if @order == field_name
if @direction == 'ASC'
txt += '<img alt="Arrow-up" class="dirimg" src="/images/arrow-up.png" />'
else
txt += '<img alt="Arrow-down" class="dirimg" src="/images/arrow-down.png" />'
end
end
return txt
end As I mentioned, put this in app/helpers/application_helper.rb and it will be available to all your views. This assumes that you have two small images in you images directory called arrow-up.png and arrow-down.png to represent the direction of sort. I will leave that to the reader to supply.
To the application controller (app/controllers/application.rb) add the following routine:
def get_order(default_order, default_direction = 'ASC')
@order = params[:order] || default_order
params[:direction] = 'DESC' if default_direction == 'DESC' and params[:direction] != 'ASC'
if params[:direction] == 'ASC'
@ndir = 'DESC'
else
@ndir = 'ASC'
end
@direction = params[:direction] || 'ASC'
if @direction != 'ASC'
@direction = 'DESC'
end
@od = @order.sub(',',' ' + @direction + ',') + ' ' + @direction
end With these two pieces of code in place, modifying your views and controllers to take advantage of sortable headers in the list view is simple
First modify the controller by adding:
:order => get_order('id')
To the end of the line that sets up paging for the list. So for example if you had a list of contacts the paging line would look like:
@contact_pages, @contacts = paginate :contacts, :per_page => 10, :order => get_order('id')
By the way the 'id' in the examples is the default sort column and can be changed to match your needs
Now in the view, replace your headings like:
<th>Name</th><th>City</th> ...... With
<th><%= link_to_sort 'Name', 'lastname, firstname' %></th>
<th><%= link_to_sort 'City', 'city' %></th> ..... As you might have guessed the two parameters that link_to_sort take are the Label for the column and the actual sort columns. You will notice in the first example you can give multiple columns for the sort. The first time you click the column will be sorted ascending and the second time you click it will be sorted descending.
A little arrow will appear beside the column when sort is active showing the direction of sort
Log4r and Rails 7
In a rails application I am working on I needed to set up logging for the events happening in the system. After a little perusing on the net I figured that log4r was the way to go. Then as with a lot of ruby apps, I found the documentation wanting. Yes I know the api and source is on line but a little more help to get started would be helpful. So I thought I would share what I had gleaned from the various sources and how I got it working.
Here are the problems I encountered and the solutions I came up with:
Install
Installation is easy using gem:
gem install log4r
Now you must include the log4r classes in your code with
require "log4r"
What is required for a simple logging setup?
First you must create a new logger. You can do that with code like:
MyLog = Log4r::Logger.new("mylog")
Now you must create an outputter for your log
I was interested in logging to a file so didn't bother with figuring out the ins and outs of logging to different devices.
To create a simple logger that logs to the file mylog.log in the rails log directory, you would create code like this:
Log4r::FileOutputter.new('my_log'
:filename=>"#{RAILS_ROOT}/log/mylog.log",
:trunc=>false)
This will create the log file named my_log which logs to the file mylog.log in the rails log directory and append to it. Note: Log4r has a log rotator, but since I am developing on linux, I elected to just let the system rotate my log files.
Now we must associate this file outputter with the logger we created earlier.
To do that we just user the following code:
myLog.add('my_log')
To put it all together
require "log4r"
MyLog = Log4r::Logger.new("mylog")
Log4r::FileOutputter.new('my_log'
:filename=>"#{RAILS_ROOT}/log/mylog.log",
:trunc=>false)
myLog.add('my_log')
Where do you put this code in a typical rails application?
I came up with two places, useful for different things.
First I placed it in config/environment.rb. This worked well, but I had to restart the web server every time I made a change. Debug messages were non-existent also.
Next I placed the setup in app/controllers/application.rb. This allowed me to make changes without restarting the webserver but slowed down the application.
So I did the initial setup and tweaking of the logging with the code in application.rb and moved it to environment.rb when it was done.
Now that we have the log setup how is it used?
Each log4r logger has default logging levels
DEBUG < INFO < WARN < ERROR < FATALTo log something in our code you would use something like:
MyLog.info('My Log Message')
This will produce log output like
INFO MyLog: My Log Message
You log a message by giving the name of your logger and the log level.
What if I want to change the levels?
In the application I am writing I needed to log login/logouts and all activity of the users. To do this I wanted different log levels than the default. Here is how I did it: First I had to include the log4r configurator like this:
require "log4r/configurator"Then I configured my custom levels:
Log4r::Configurator.custom_levels "Error","Access","Activity","Debug"These two lines were placed immediately after the initial require, before creating the logger. Now I could create log messages with:
MyLog.access("User #{@user.name} logged in")
Producing a log message like:
Access MyLog: User tomw logged in
What about changing the log format?
Log4r has several different log format options built in Basic Formatter (the default formatter), Object Formatter, Pattern Formatter and Simple Formatter. None of these produced exactly the output I wanted and Log4r allows you to create your own formatters. The way you do this is to create a class of the type Log4r::Formatter and use it in place of the built in formatters. A Formatter class looks like this:
class MyFormatter < Log4r::Formatter
def format(event)
buff = Time.now.strftime("%a %m/%d/%y %H:%M %Z")
buff += " - #{Log4r::LNAMES[event.level]}"
buff += " - #{event.data}\n"
end
end
This formatter produces log lines that look like this:
Thu 02/01/07 10:03 EST - Access - User Alfred E Newman From IP: 12.13.14.15 -- User Logged in.Change the buff statements in your class to reflect how you want the log line to look. I have another log that I only wanted to contain the data I feed it. The formatter looks like this:
class UpFormatter < Log4r::Formatter
def format(event)
"#{event.data}\n"
end
end
It puts a line in the log that only contain the data given it. Thus the log line:
MyLog.access("Some Log Event")
Will produce the following log line:
Some Log Event
How do you hook these new formatters into your log?
Its simple. When you are creating the log outputter simply add the formatter to the options hash, Thusly:
Log4r::FileOutputter.new('my_log',
:filename=>"#{RAILS_ROOT}/log/mylog.log",
:trunc=>false,
:formatter=> MyFormatter)
This only scratches the surface of what you can do with log4r but should get you going.
Data Normalization in Rails 2
For a project I am working on (in Rails of course) I wanted to display currency like currency in a data field. This was easy just use the number_to_currency helper in the view. Ok maybe not so easy when you use the text_field helper. Some code will illus trate how to do it.
<%= text_field "invoice", 'total', :value => number_to_currency(@invoice['total']) %>
That only took a few minutes to figure out.
Now the problem comes trying to save the value back into the database. ActiveRecord typecasts $9.00 to 0.00 because $9.00 is con sidered a string. I knew it should be easy to do and after far too long looking for the answer I found it. In the model, use the b efore_save callback to make changes to the before_type_cast attribute. Some code will illustrate it:
class Invoice < ActiveRecord::Base
before_save :fix_currency
def fix_currency
total_before_type_cast.tr!('$,','')
end
end
You could, of course, add any other normalization routines here.
Hope this will save someone some time.
Install Ruby/Rails with FastCGI on Apache2 2
Install Ruby with FastCGI on Apache2
apt-get install ruby ruby1.8-dev rdoc ri1.8 apache2-prefork-dev make
Install the FastCGI library
*Note 2.4.0 is the current version if it is not available visit www.fastcgi.com to get the latest.
cd /usr/local/src
wget http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz
tar xvzf fcgi-2.4.0.tar.gz
cd fcgi-2.4.0
./configure && make && make install
Installation of mod_fastcgi.so for Apache2
Download the mod_fastcgi sources from http://www.fastcgi.com/dist/mod_fastcgi-2.4.2.tar.gz. If the Link is not working, try to get the latest mod_fastcgi sources instead. Make the mod_fastcgi module.
cd /usr/local/src
wget http://www.fastcgi.com/dist/mod_fastcgi-2.4.2.tar.gz
tar xvzf mod_fastcgi-2.4.2.tar.gz
cd mod_fastcgi-2.4.2
cp Makefile.AP2 Makefile
make top_dir=/usr/share/apache2
make install top_dir=/usr/share/apache2
Enable mod_fastcgi in Apache2
echo 'LoadModule fastcgi_module /usr/lib/apache2/modules/mod_fastcgi.so' > /etc/apache2/mods-available/fastcgi.load
a2enmod fastcgi
Installation of ruby-fcgi bindings, Ruby GEM and Rails:
Download the ruby-fcgi bindings from http://sugi.nemui.org/pub/ruby/fcgi/ruby-fcgi-0.8.6.tar.gz. If the Link is not working, try to get the latest ruby-fcgi bindings instead.
cd /usr/local/src
wget http://sugi.nemui.org/pub/ruby/fcgi/ruby-fcgi-0.8.6.tar.gz
tar xvzf ruby-fcgi-0.8.6.tar.gz
cd ruby-fcgi-0.8.6
ruby install.rb config
ruby install.rb setup
ruby install.rb install
cd /usr/local/src
wget http://rubyforge.org/frs/download.php/5207/rubygems-0.8.11.tgz
tar zxvf rubygems-0.8.11.tgz
cd rubygems-0.8.11
ruby1.8 setup.rb
gem install rubygems-update
gem install rails --include-dependencies
gem install mysql
Answer 2 mysql 2.7(ruby)
Restart Apache:
/etc/init.d/apache2 restart
In the directories in which you want to run Ruby besure the following directive is listed in the .htaccess.
AllowOverride all
Then setup you ruby environment in the .htaccess file
