Archive for the 'resources' 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.

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'

Railscasts.com – die beste Ruby on Rails Resource

Auch wenn es die meisten wohl schon kennen, die Screencasts von Ryan Bates, sind meiner Meinung nach, so ziemlich die beste Resource, was praktische Tipps mit Ruby on Rails angeht. Er spricht so ziemlich alle wichtigen Themen an und bietet auch für etwas erfahrenere Entwickler manch eine Idee.
Also unbedingt anschauen, wer es noch nicht kennt:
www.railscasts.com

Ruby on Rails: String manipulation

Es gibt einige Sachen, die ich gerne mit Strings machen möchte. Aber natürlich ist nicht ALLES in Rails schon drin. Also habe ich mir mit einem Plugin wie folgt geholfen.

z. B.: “2″.numstring? gibt mir an, ob der string nur Ziffern enthält.

/vendor/plugins/future/init.rb

require 'string_manipulation'

/vendor/plugins/future/lib/string_manipulation.rb

require 'digest/sha1'
module StringManipulation
 
  # Remove ALL unneccessary whitespaces from string
  #    "   hello   world   " #=> "hello world"
  def strip_all
    self.gsub(/ +/, ' ').strip.chomp
  end
 
  # Remove everything that is not a-z or 0-9 or space
  def strip_illegal
    self.gsub(/[^a-zA-Z0-9 ]/, '')
  end
 
  # Returns the basename of a file
  def strip_extention
    self.gsub(/(\.(.*))$/,'')
  end
 
  # Strips RAILS_ROOT/public from string
  def url_from_path
    self.gsub("#{RAILS_ROOT}/public", '')
  end
 
  # Strips RAILS_ROOT from string
  def root_from_path
    self.gsub("#{RAILS_ROOT}", '')
  end
 
  # Hashes a string with SHA1
  def hashed
    Digest::SHA1.hexdigest(self)
  end
 
  # Make a nice downcase keyword list as string, seperated by single spaces
  #    keyword_list("   My §$% KEY   wordlist   ") #=> "my key wordlist"
  def keywordlist
    self.strip_illegal.downcase.strip_all
  end
 
  # Checks if a string contains only numerical characters
  def numstring?
    self =~ /^\d+(\.\d+|\d*)$/
  end
 
end
 
class String
  include StringManipulation
end

Als Unterfunktion von Object sind die Funktionen überall verfügbar. Sehr praktisch.

Ruby on Rails: TAR-Archive in einem Verzeichnis entpacken [Extract tar archives in a directory]

Hier eine Lösungsmöglichkeit, um in einem Verzeichnis nach allen Tar-Dateien zu suchen und die Inhalte im selbigen zu entpacken (durch leichte Modifikation natürlich anpassbar). Anschließend werden die entpackten Archive gelöscht. Da der UNIX “tar”-Befehl verwendet wird, geht das ganze nur auf UNIX Systemen. Hinweis: Bei mehr als 2000-3000 Dateien im Verzeichnis würde ich das nicht so anwenden, da zu uneffizient/langsam. Aber das ist ein anderes Thema :)

Wie immer sind Verbesserungsvorschläge (Kommentare) erwünscht. Aus pragmatischen Gründen wird dann der folgende Code jedes Mal entsprechend erneuert:

class ApplicationController < ApplicationController::Base
  def untar
    result = ''
    Dir.open(PATH_TO_DIR).each do |file|
      next if file !~ /tar$/
      result += 'Extracting ' + file + '... '
      if system('cd ' + PATH_TO_DIR + '; tar -xf ' + file)
        result += 'done!<br/>Deleting ' + file + '... '
        if FileUtils::rm_r(PATH_TO_DIR + file)
          result += 'done!<br />'
        else
          result += 'error!<br />'
        end
      else
        result += 'error!<br />'
      end
      processed = true
    end
    result += '<br />Extraction completed!'
    result = 'No compressed files found.' if processed.nil?
    render(:text => result)
  end
end

PS: Manuel, vielen Dank für dieses WP! Sehr sehr schön. Viele Plugins, schöne Bilder und einfach schön! Wie wär’s, wenn du jetzt noch GANZ SCHNELL ein Ruby-Syntax-Highlight installierst :) DANKE!