Archive for the ‘Ruby’ Category

Had a problem with downloading a spreadsheet when some of the entries had characters with accents or other symbols. Fix:

csv << ["#{user.username}", "#{user.fullname.force_encoding("utf-8")}", "#{user.institution}", "#{user.day1}", "#{user.day2}", "#{user.lunch}", "#{user.dinner}", "#{user.created_at.to_s(:month_and_day)}"]

Just add .force_encoding("utf-8") to the field causing the problems. Might have to do this in a bunch of places, but luckily for us, it was just in one.

I have a problem with a rails app that I thought would have been fixed by switching from the mysql gem to the ruby-mysql gem. Unfortunately, after deploying this change, the entire app crashed. And due to my unfamiliarity with git, I screwed that up as well. Here is how I fixed things:

yo$  rm -rf .bundle && bundle install && git add Gemfile.lock && git commit -m "Added Gemfile.lock"
yo$ git push --all
yo$ cap deploy

Recently I had learned how to write a csv file in my rails app using the fasterCSV gem. In Rails3, CSV is now part of the standard library, so things are a little different. I also did a few trickier things this time around, so I’m updating my notes.

In Gemfile, add:

require 'csv'

As before, create config/download_csv.yml

development:
    all: /Users/me/Desktop/all.csv
    current: /Users/me/Desktop/current.csv
    previous: /Users/me/Desktop/previous.csv
    
production:
    all: /tmp/all.csv
    current: /tmp/current.csv
    previous: /tmp/previous.csv

In the model, add a method to make the csv file.

  def self.download_charges(project_id, timeframe)
    csv_settings = YAML.load_file("#{Rails.root.to_s}/config/download_csv.yml")[Rails.env]
    filename = csv_settings[timeframe]
    if (timeframe == 'current')
      @entries = Entry.billable.current_month.where(:project_id => project_id).newest_first
    end
    if (timeframe == 'previous')
      @entries = Entry.billable.previous_month.where(:project_id => project_id).newest_first
    end
    if (timeframe == 'all')
      @entries = Entry.billable.where(:project_id => project_id).newest_first
    end
    CSV.open("#{filename}",'w') do |csv|
      csv << ["Report Generated On #{Time.now.to_s(:fulldate)}"]
      csv << ["Account","#{@entries.first.project.account}","#{@entries.first.project.subaccount}"]
      csv << ["#{@entries.first.project.name}"]
      csv << ["Total Charges", "#{@entries.sum(:cost)}"]
      csv << [""]
      csv << ['Date','Staff Member', 'Hours', 'Charge', 'Note']
      @entries.each do |entry|
        csv << ["#{entry.start_date.to_s(:fulldate)}","#{entry.user.fullname}","#{entry.hours}", "#{entry.cost}", "#{entry.note}"]
      end
    end
  end

In the controller, make a method to call.

  def download_charges
    csv_settings = YAML.load_file("#{Rails.root.to_s}/config/download_csv.yml")[Rails.env]
    filename = csv_settings["#{params[:timeframe]}"]
    Entry.download_charges(params[:project_id], params[:timeframe])
    send_file "#{filename}", :type => 'text/csv'
  end

and add it to the filter_resource access line.

  filter_resource_access :additional_collection => [[:download_charges, :index], :index]

To my routes file, add:

  resources :entries do
    collection do
      get 'download_charges', :as => :download_charges
    end
  end

Lastly, in my view, add the required links. Here they're a little trickier because I want the same method to work for different time frames.

	<% unless params[:project_id].nil? %>
	
			<%= link_to 'Download All Charges', :controller => :entries, :action => :download_charges, :project_id => @entries.first.project_id, :timeframe => 'all' %>
			<%= link_to 'Download Current Month Charges', :controller => :entries, :action => :download_charges, :project_id => @entries.first.project_id, :timeframe => 'current'%>
			<%= link_to 'Download Previous Month Charges', :controller => :entries, :action => :download_charges, :project_id => @entries.first.project_id, :timeframe => 'previous'%>
		
	<% end %>

We have a rails application that allows users to sign up for an event. People not logged in are only allowed to see the names of people who have signed up to attend. People with logins are allowed to see all the contact info for the attendees. I decided it would also be nice to make a downloadable spreadsheet of this information for those people who will need to contact the attendees. Here is how I did it.

First, get the fasterCSV gem. Add this to the Gemfile:

gem 'fastercsv', :require => 'faster_csv'

Basically, all that will happen is that there will be a link on the attendees index page that only logged in users will see (and be allowed to access). This is done in two steps. First the csv file is generated and then it is downloaded.

So, where shall I put the file? Since my development and production workstations have different operating systems, I’m going to have different paths on both of them. I created an yaml file to hold this information.

config/download_csv.yml

development:
    csvfile: /Users/me/Desktop/output.csv
    
production:
    csvfile: /local/code/web/app/myapp/shared/system/output.csv

In my attendee model, I created a method to generate the csv file.

  def self.generate_csv
    csv_settings = YAML.load_file("#{Rails.root.to_s}/config/download_csv.yml")[Rails.env]
    csvfile = csv_settings['csvfile']
    @attendees = Attendee.order('lastname ASC')
    FasterCSV.open("#{csvfile}",'w') do |csv|
      csv << ['Firstname','Lastname','Institution','Address','Email','Number attending']
      @attendees.each do |attendee|
        csv << [attendee.firstname, attendee.lastname, attendee.institution, attendee.address, attendee.email, attendee.fest_count]
      end
    end
  end

In my attendee controller, I created a method called download_csv. In this method, I actually call the generate_csv method above. (Side note: I originally had it set up so that the generate_csv method was called with each new create method. But then decided that fewer people would be downloading the csv file than would be registering. And I changed things so that the csv is only generated when someone wants to download it.)

  def download_csv
    csv_settings = YAML.load_file("#{Rails.root.to_s}/config/download_csv.yml")[Rails.env]
    csvfile = csv_settings['csvfile']
    Attendee.generate_csv
    send_file "#{csvfile}", :type => 'text/csv'
  end

Since I'm using authlogic and declarative_authorization in this app, I also needed to edit my filter_resource_access line in the method to:

filter_resource_access :additional_collection => [[:download_csv, :edit], :edit]

(I.E. People who are allowed to edit attendees are also allowed to download the file.)

Lastly, on my view I have the link:

<% if current_user %>
	<%= link_to 'Download Spreadsheet of Attendees', :controller => :attendees, :action => :download_csv %>
<% end %>

This all worked perfectly on my laptop while I tested it. But when I uploaded it to the server, it kept giving me an empty file, even though the file on the server was not empty. Apparently, this is a common problem. All I needed to do was edit my environments/production.rb file to match this:

  # Specifies the header that your server uses for sending files
  #config.action_dispatch.x_sendfile_header = "X-Sendfile"

  # For nginx:
  config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

We have an openldap server running. I wrote a little rails app that required authentication and that I wanted to authenticate against users in the openldap server. Here’s how I did it.

Here is the relevant part of my Gemfile. I’m using Rails 3.0.3.

gem 'authlogic'
gem 'declarative_authorization'
gem 'net-ldap', :require => 'net/ldap'

My user migration:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :username
      t.string :firstname
      t.string :lastname
      t.string :persistence_token
      t.datetime :last_request_at
      t.string :role

      t.timestamps
    end
  end

Add to my user model:

  acts_as_authentic do |c|
    c.validate_password_field = false
    c.logged_in_timeout = 2.hours
  end    

  def valid_password?(password)
    ldap_settings = YAML.load_file("#{Rails.root.to_s}/config/ldap.yml")[Rails.env]
    #logger.info ldap_settings.inspect
    
    ldap_settings[:host] = ldap_settings['host']
    ldap_settings[:port] = ldap_settings['port']
    ldap_settings[:encryption] = { :method => :simple_tls } if ldap_settings['ssl']
    ldap_settings[:auth] = 
      { :method => :simple, :username => "uid=#{self.username},#{ldap_settings['base']}", :password => password}

    #logger.info ldap_settings.inspect
    
    ldap = Net::LDAP.new(ldap_settings)
    ldap.bind
  end

Add to my user_session model:

  logout_on_timeout true

My new user_session:

<%= form_for @user_session, :as => :user_session, :url => { :action => 'create' } do |f| -%>
  

<%= f.label :username %>
<%= f.text_field :username %>

<%= f.label :password %>
<%= f.password_field :password %>

<%= f.submit 'Login' %>

<% end %> <%= link_to 'Back', :back %>

Needed the stuff after @user_session because of a problem with authlogic and rails3.

Finally, I created a yaml file with the info for my ldap server. It's in config/ldap.yml.

development:
    host: eagle.example.com
    port: 636
    base: ou=people,dc=group,dc=example,dc=com
    ssl: true

production:
    host: eagle.example.com
    port: 636
    base: ou=people,dc=group,dc=example,dc=com
    ssl: true

That pretty much did it. You MUST use port 636 and ssl:true for this to work. Starttls over port 389 will not work. (At least, it didn't for me.)

I installed ruby 1.9.2 and had a problem with rubygems.

[root@server]# gem list
ERROR:  Loading command: list (LoadError)
    no such file to load -- zlib
ERROR:  While executing gem ... (NameError)
    uninitialized constant Gem::Commands::ListCommand

To fix, I had to:

yum install zlib-devel

And then go back to the ruby source code and in ~/ruby-1.9.2-p0/ext/zlib, run the following:

[root@server zlib]# ruby extconf.rb
checking for deflateReset() in -lz... yes
checking for zlib.h... yes
checking for kind of operating system... Unix
checking for crc32_combine() in zlib.h... yes
checking for adler32_combine() in zlib.h... yes
creating Makefile
[root@server zlib]# make
gcc -I. -I/usr/local/include/ruby-1.9.1/i686-linux -I/usr/local/include/ruby-1.9.1/ruby/backward -I/usr/local/include/ruby-1.9.1 -I. -DHAVE_ZLIB_H -DOS_CODE=OS_UNIX -DHAVE_CRC32_COMBINE -DHAVE_ADLER32_COMBINE -D_FILE_OFFSET_BITS=64 -fPIC -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -o zlib.o -c zlib.c
gcc -shared -o zlib.so zlib.o -L. -L/usr/local/lib -Wl,-R/usr/local/lib -L. -rdynamic -Wl,-export-dynamic -lz -lpthread -lrt -ldl -lcrypt -lm -lc
[root@server zlib]# make install
/usr/bin/install -c -m 0755 zlib.so /usr/local/lib/ruby/site_ruby/1.9.1/i686-linux