Archive for the 'ruby on rails' Category

XML Sitemap Generator for Rails

I realy like the way the Google (XML) Sitemaps Generator for WordPress handles the generation of my sitemaps and informs Google about the changes on my blog.

I was missing this in the Rails world for a long time, so I decided to build my own Rails plugin.

Here it is: “XML Sitemap Generator for Rails”

It’s just a quick implementation of all the functionality you need to let your Rails App generate a XML Sitemap and ping Google about the updates.
It’s not made to be scale more to give your small site the abillitiy to have a sitemap.

I’ll work on it in the future to make it easy to add some custom URL’s and maybe to have a version to scale…

Check it out on: GitHub
If you have any suggestions fork the project and send me a pull request.

render_nested_form Helper

Die in Rails 2.3 eingeführten Nested Forms haben mir am Anfang sehr viele Kopfschmerzen bereitet und tun dies immer mal wieder.

Dank schorsch vom SalesKing muss ich mir um diese Dinge keine Sorgen mehr machen.
Sein render_nested_form Helper übernimmt alles und hat ein paar sehr nette Options.

# Options are:
# * <tt>:new</tt> - specify a certain number of new elements to be added to the form. Useful for displaying a few blank elements at the bottom.
# * <tt>:name</tt> - override the name of the association, both for the field names, and the name of the partial
# * <tt>:partial</tt> - specify the name of the partial in which the form is located.
# * <tt>:fields_for</tt> - specify additional options for the fields_for_associated call
# * <tt>:locals</tt> - specify additional variables to be passed along to the partial
# * <tt>:render</tt> - specify additional options to be passed along to the render :partial call
# * <tt>:skip</tt> - array of elements which will be skipped, usefull if you already rendered a partial in the same form with parts of the data. eg. obj.addresses, render the firt address on top of form, render all the other addresses at the bottom
 
f.render_nested_form(@project.tasks, :new => 3, :partial=>'some_partial', :locals=>..)

Update:
Ich hab den Helper geforked und die Option :as hinzugefügt. Damit kann man den Namen der lokalen Variable definieren, wenn man den Partial z.B. für unterschiedliche Objekte und Attribute nutzt (bei mir ist das ein Upload Form).
Hier findet ihr den Fork.

Get HTTP Headers in Ruby

Ein kleiner Schnipsel, um nur den Header eines HTTP Aufrufes zu erhalten.

url = URI.parse('http://manuel.funkensturm.de/')
req = Net::HTTP::Get.new(url.path)
res = Net::HTTP.start(url.host, url.port) { |http| http.request_head('/feed/') }
res.to_yaml

Interessant sind in meinen Augen folgende Werte:

>> res['last-modified']
=> "Tue, 11 Aug 2009 16:09:01 GMT"
>> res['content-type']
=> "application/rss+xml; charset=\"UTF-8\""
>> res['etag']
=> "\"ca39ca8e3c9d3b858ef0d711956e00ad\""

über last-modified oder den etag kann man dann schauen, ob man den Feed abholt oder die Aktion durchführt…

Funkenrailsdav: Webdav with Rails e.g. for ical

So you want a rails application to give you a webdav? Good, railsdav can do this for you. However, it might take you an hour or two as well to figure out how it works :)

This plugin is a copy of the original railsdav plugin with some modifications to make it run out-of-the-box. Just drop this plugin into your newly created rails application and it becomes a webdav-server. It comes with Authentication, so you can publish and synchronize your ical-files without fear :)

This was done using Rails 2.3.2.

Get it here: http://github.com/funkensturm/railsdav

Presenting: The Funkengallery Demo App

Still in beta mode, but we are proud to present a Rails 2.3 application, modularized in plugins, fully I18n (German, English, Swedish), and of course published at github.

This funkengallery demo application includes demonstration of all our plugins:

  • acts_as_category
  • acts_as_identifiable
  • funkengallery
  • funkenlogin
  • funkenlogin
  • irobot
  • manipulify

Again, this is beta still, which means that for example the admin area wants your models to be called exactly “Category” and “User”. So not 100% modularized yet, but we’re getting there and you can already use it as a perfect standalone application ;)

What’s so special about this gallery is the very dynamic user rights management and the simplicity. It is designed for a complex category tree with many thousands of pictures. However, you might expect flickr design and you get… well… funkengallery. It is different and simpler, but really neat if you want to share your pictures quick, private and with individual user rights.

Screenshots and Demo

Check out our Downloads site to see how you can easily test your local demo.

Rails: JPG Bilder kaputt beim Upload (grauer Balken)

Völlig zufällig wurden Bilder beim Upload in Rails mit einem grauen Balken versehen.

Mal viel grau, mal wenig grau. Mann muss genau hinschauen bei diesem Beispiel, ganz unten rechts:

dsc01326jzyuu

Zuerst dachte ich, es sei RMagick, dass versucht das Bild zu verkleinern (resize) und irgendwann merkte ich, dass der Upload an sich schon fehlerhaft war. Dann dachte ich es sei der YUI Uploader, habe aber zum Glück das Problem gefunden.

Zum speichern der Datei habe ich das hier verwendet:

File.open(target, "wb").write(params[:upload].read)

Das darf man nicht! :)
Man muss es so machen:

file = File.new(target, "wb")
file.write params[:upload].read
file.close

Passenger für Ruby on Rails aus TextMate “automatisch” neu starten

Manchmal möchte man seine Ruby-on-Rails-Applikation im Passenger manuell neu starten. Sprich eine Datei my_app/tmp/restart.txt anlegen. Ich habe ein kleines Command-Skript für TextMate dafür geschrieben. Wenn man es ausführt (in diesem Beispiel mit APFEL+R), wird beim nächsten Browseraufruf alles neu geladen. Praktisch wenn man Plugins entwickelt :)

Hier der Command in TextMate:

restart_passenger

Und hier der Code dafür

18
19
20
21
22
23
24
#!/usr/bin/env ruby
 
require "#{ENV['TM_BUNDLE_SUPPORT']}/lib/rails_bundle_tools.rb"
f = File.open File.join(RailsPath.new.rails_root, "tmp", "restart.txt"), "w"
f.close
 
puts "Rails Application will be reloaded!"

ActiveSupport::Memoizable Cache löschen

Auch wenn es vielen von vorn herein klar ist, mir war es nicht sofort klar :)

Das tolle Memoizable in Rails 2.2 macht das Caching von Methoden einfacher. Ich bin dabei auf eine Frage gestoßen:

Wie lösche ich den Cache?

Als ich mir den Quellcode von Memoizable angeschaut habe, dachte ich auf den ersten Blick, dass memoize_all ALLE Methoden einer Klasse memoizen würde und unmemoize_all alles wieder deaktiviert.

Nun, tatsächlich löscht unmemoize_all nur den gesamten Cache und er wird automatisch mit jedem Aufruf einer Methode Stück für Stück wieder aufgebaut. Wenn man also z. B. eine Änderung vornimmt, sollte man unmemoize_all ausführen.

memoize_all hingegen, führt alle gecachten Methoden auf einmal aus (!) und speichert die Ergebnisse im Cache. Aber das kann doch nicht sein, oder? Kann mir das hier mal jmd. erklären :)

Nützliche Terminal Befehle

Hier soll eine Liste mit nützlichen Terminal Befehlen entstehen, da man so manche einfach jedes mal vergisst und dann suchen muss.

Nameserver einer Domain herausfinden
nslookup funkensturm.de

MX Record einer Domain herausfinden
nslookup -query=mx funkensturm.de

DNS Cache flushen
dscacheutil -flushcache

Prozesse (zu einem Command) mit PID’s herausfinden
ps aux | grep -i ruby
| grep -i command nur wenn man die Prozesse zu einem Command haben will

Prozesse beenden
kill -9 PID
killall command

mit den zwei fang ich mal an. Wird aber immer schön erweitert. Wenn ihr welche habt einfach in die Comments!

FINALLY! RailsICalendar ical ics publish with ruby on rails

Das hat echt was Nerven gekostet, aber ich bin mehr als zufrieden mit dem Resultat.

Ich darf vorstellen: Wenn man seinen Kalender in Mac OS X (z. B. per Webdav) auf seinen Server lädt (bzw. synchronisiert) und auf diesem Server auch Ruby on Rails läuft, dann kann man seinen Kalender jetzt auf seiner Webseite veröffentlichen.

In fact I just realize I should better speak english, because someone’s German might be somewhat rusty :)

So again: You have ics files on your server (e.g. via webdav) and Ruby on Rails is running? Great, let’s publish your calendar. The idea came from the great PHPicalendar script.

This is what it will somewhat look like:
bild-1.png

I am sorry to not have made a plugin out of this yet, but, hey, the basics are there, help yourself :) If you have any questions feel free to comment.

Requirements:

  • Vpim plugin with sudo gem install vpim

Features:

  • Read several ICS files from a directory on the server
  • Parse all the ical events in them
  • Cache the current calendar in yaml files
  • (The cache will be refreshed when a ICS file was updated meanwhile)
  • HTML will be presented for the calendar
  • Currently you can only choose a date and see the next X days

A word on recurrence of events

  • It does do most of the recurrence rules!
  • Specifically: All that VPIM supports
  • PLUS: EXDATE is also supported!

The Code

Initializer (config/initializers/any_filename.rb)

1
2
3
4
5
# Path to icalendar *.ics files on your server
PATH_ICS       = "#{RAILS_ROOT}/private/calendars/"
PATH_ICS_CACHE = "#{RAILS_ROOT}/tmp/calendars/"
FileUtils::mkdir_p(PATH_ICS)       unless File.exists?(PATH_ICS)
FileUtils::mkdir_p(PATH_ICS_CACHE) unless File.exists?(PATH_ICS_CACHE)

Controller (app/controllers/calendars_controller.rb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
require 'vpim'
class CalendarsController < ApplicationController
 
  def index
    # Load parameters if submitted
    session[:date_year]  = params[:options]["date(1i)"] if !params[:options].blank? && !params[:options]["date(1i)"].empty?
    session[:date_month] = params[:options]["date(2i)"] if !params[:options].blank? && !params[:options]["date(2i)"].empty?
    session[:date_day]   = params[:options]["date(3i)"] if !params[:options].blank? && !params[:options]["date(3i)"].empty?
 
    # Load standard if nothing submitted
    session[:date_year]  = Time.now.year  if session[:date_year].blank?
    session[:date_month] = Time.now.month if session[:date_month].blank?
    session[:date_day]   = Time.now.day   if session[:date_day].blank?
 
    # Set variables
    @scope     = 7
    @events   = []
    @today    = Time.gm(session[:date_year], session[:date_month], session[:date_day])
    cachefile = File.join(PATH_ICS_CACHE, "#{@today.to_s(:ical)}_#{@scope}.yml")
 
    # Kill cache if outdated
    if File.exists?(cachefile)
      killcache = false
      Dir.glob(File.join(PATH_ICS, '*.ics')).each do |file|
        killcache = true if File.mtime(file) > File.mtime(cachefile)
      end
      if killcache
        Dir.glob(File.join(PATH_ICS_CACHE, '*.*')).each do |file|
          File.delete(file)
        end
      end
    end
 
    # Load calendar from cache
    if File.exists?(cachefile)
      @events = YAML.load_file cachefile 
    else
      # No cache, parse each icalendar *.ics file in PATH_ICS and check for event occurences
      Dir.glob(File.join(PATH_ICS, '*.ics')).each do |file|
        category = File.basename(file, '.ics')
        Vpim::Icalendar.decode(File.open(file)).each do |calendar|
          calendar.components do |event|
            for day in 0..@scope
              if start = event.occurs_in?(@today+(60*60*24*day), @today+(60*60*24)+(60*60*24*day))
                myend = start + (event.dtend - event.dtstart)
                @events < < {
                  'category' => category,
                  'day'      => day,
                  'start'    => start,
                  'duration' => ((event.dtend - event.dtstart) / 60).round, # In minutes
                  'end'      => myend,
                  'allday'   => start.hour == 0 && start.min == 0 && start.sec == 0 && myend.hour == 0 && myend.min == 0 && myend.sec == 0,
                  'data'     => event
                }
              end
            end
          end
        end
      end
      @events = @events.uniq # Just in case :)
      # Save cache
      File.open(cachefile, 'w') { |f| YAML.dump(@events, f) }
    end
  end
 
end

Single View (view/calendars/index.html.erb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
< %
  width  = 110      # Width of one day
  height = 6        # height of 15 minutes in pixels
  buffer = 28       # free space for dates in day column (at top of each day)
  minh   = 20       # Minimum of event height
  cutmornings = 120 # I don't have events between 0:00 and 6:00, cut these pixels off
%>
 
<div id="action">
 
< % for day in 0...@scope + 1 do 
  today = (@today+(60*60*24*day))
  case today.wday  
    when 0  
      dayclass = 'class="sunday"'
    when 6  
      dayclass = 'class="saturday"'
    else  
      dayclass = 'class="otherday"'
  end
  %>
  <div id="ical_day" <%= dayclass %>
    style=" width:  < %= width %>px;
            height: < %= height*96 + buffer - cutmornings %>px;
            left:   < %= day*width + day*10 %>px;">
    < % if today.year == Time.now.year && today.month == Time.now.month && today.day == Time.now.day %>
      <b>< %= 'Today' %></b>
    < % else %>
      < %= _(today.strftime("%a")) +', '+ today.to_s(:date) %>
    < % end %>
  </div>
< % end %>
 
< % tops = Array.new(@scope + 1, '')
@events.each do |event|
 
  # Exclude recurrence rule hack
  today = (@today+(60*60*24*event['day']))
  exme = false
  event['data'].propvaluearray('EXDATE').each do |exdate|
    exdate = exdate.to_time
    exme = true if today.year == exdate.year && today.mon == exdate.mon && today.day == exdate.day
  end
  next if exme # Skip this event
 
  if event['allday']
    # All-day events will be inserted later
    tops[event['day']] += '&nbsp; ' + event['data'].summary.to_s + '<br/>'
  else 
    eventheight = ((event['duration']/15)*height).round
    eventheight = minh if eventheight < minh
    %>
    <div id="ical_event"
      style=" background: #<%= eventcolor(event['category']) %>;
              width:  < %= width-2 %>px;
              height: < %= eventheight.to_s %>px;
              top:    < %= event['start'].hour*height*4 + (event['start'].min/15)*6 + buffer - cutmornings %>px;
              left:   < %= event['day']*width + event['day']*10 %>px;">
      < %= '<b>'+ event['start'].to_s(:time) +' - '+ event['end'].to_s(:time) +'<br />' %>
      < %= event['data'].summary %>
    </div>
  < % end %>
< % end %>
 
< % tops.each_with_index do |top, day| %>
    <div id="ical_event_top"
      style=" width: <%= width-2 %>px;
              height: < %= height %>px;
              top: < %= buffer %>px;
              left: < %= day * width + day * 10 %>px;">
      < %= top %>
    </div>
< % end %>
 
<div id="vertical_spacer" style="height: <%= height*96 + buffer*2 - cutmornings %>px;">&nbsp;</div>
 
</div>

Layout View (views/layouts/calendars.html.erb)

        < % form_tag :controller => 'calendars', :action => nil, :id => nil do |f| %>
          < %= date_select("options", "date", :default => @today, :order => [:day, :month, :year]) %>
            < %= submit_tag 'Show' + ' &raquo;', :class => 'date_button' %>
        < % end %>

Helper (for coloring events) (app/helpers/calendar_helper.rb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module CalendarsHelper
 
  def eventcolor(category)
    case category  
      when 'Wichtig'   # This is the name of the .ics file
        return 'f66'
      when 'Sonstiges'
        return '4f4'
      when 'Studium'
        return 'fb4'
      when 'Privat'
        return '77f'
      when 'Freunde'
        return 'f4f'
      else  
        return 'fb4'
    end
  end
 
end

Stylesheet (public/stylesheets/calendar.css)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**************** DIVs ***************************/
 
div#action {
  position: absolute;
  margin-left: 10px;
  margin-top: 10px;
  padding: 4px;
  border-left: 0px;
}
 
div#ical_day {
  position: absolute;
  padding-top: 5px;
  text-align: center;
  font-size: 10px;
}
 
.otherday {
  background: #ddd;
}
 
.sunday {
  background: #fdd;
}
 
.saturday {
  background: #ddf;
}
 
#ical_event {
  position: absolute;
  border: 1px #555 solid;
  background: #fb4;
  font-size: 8px;
  text-align: left;
}
 
#ical_event_top {
  position: absolute;
  border: 0px;
  font-size: 9px;
  font-weight: bold;
  text-align: left;
}
 
/**************** Fonts ***************************/
 
#title {
  float:right;
  color: #ddd;
  margin-top: 30px;
  margin-right: 10px;
  line-height: 11px;
  font-size: 16px;
  text-align: right;
}
#title .description {
  color: #777;
  font-size: 10px;
  margin: 0;
}

Routes (could be optional) (config/routes.rb)

1
2
  # CALENDAR Controllers
  map.connect 'calendar', :controller => 'calendars'