Home Notes Web development

Development
Rails 3 error: 'require': no such file to load -- readline (LoadError) E-mail
Monday, 07 February 2011 21:38

If you get an error like this trying to run rails console in a Rails 3 project:

`require': no such file to load -- readline (LoadError)

In Ubuntu you need to run the following (see here for other platforms):

sudo apt-get install libncurses5-dev libreadline5-dev

Then locate your current Ruby installation.  Mine, for example, is under /home/pranshu/.rvm/gems/ruby-1.9.2-p0 because I'm using RVM to manage multiple Ruby versions.

 

Run:

cd /home/pranshu/.rvm/gems/ruby-1.9.2-p0/ext/readline
ruby extconf.rb
make
make install

Next time you try running rails console it should run fine.

 
Installing Authlogic in Rails 3 E-mail
Monday, 24 January 2011 16:52

The authlogic gem/plugin seems to be the most popular authentication extension for Rails right now.  It comes with some good tutorials, but they're not up to date for Rails 3, so I culled the following steps from various sources to get it to work with Rails 3.  (Here is the official tutorial.)

 

My implementation is:

  1. Name (name) and e-mail (email) are required fields
  2. E-mail serves as the login username
  3. User paths are placed behind '/admin' prefix, with an admin controller and index view for convenience
  4. Users are managed by the user model and users controller, and user sessions by the user_sessions controller (there is a user_session model but it's empty).

Here are the steps to follow:

  1. In your Gemfile, add the following (and afterwards run bundle install):
  2. gem 'authlogic'

  3. Generate models:
  4. rails g authlogic:session user_session
     
    rails g model user

  5. Generate controllers:
  6. rails g controller user_sessions
     
    rails g controller users
     
    rails g controller admin

  7. Add fields to create_user model migration.  This is what mine looked like:
  8. class CreateUsers < ActiveRecord::Migration
      def self.up
        create_table :users do |t|
          t.string    :name,                :null => false
          t.string    :email,               :null => false, :length => 320
     
          t.string    :crypted_password,    :null => false
          t.string    :password_salt,       :null => false
          t.string    :persistence_token,   :null => false
     
          # Magic columns, just like ActiveRecord's created_at and updated_at.
          # These are automatically maintained by Authlogic if they are present.
          t.integer   :login_count,         :null => false, :default => 0
          t.integer   :failed_login_count:null => false, :default => 0
          t.datetime  :last_request_at
          t.datetime  :current_login_at
          t.datetime  :last_login_at
          t.string    :current_login_ip
          t.string    :last_login_ip
     
          t.timestamps
        end
        add_index :users, :email
      end
     
      def self.down
        remove_index :users, :email
        drop_table :users
      end
    end

  9. Create a create_user_sessions migration:
  10. class CreateUserSessions < ActiveRecord::Migration
      def self.up
        create_table :user_sessions do |t|
     
          t.timestamps
        end
      end
     
      def self.down
        drop_table :user_sessions
      end
    end

The contents of the various files are:

Models

class User < ActiveRecord::Base
    acts_as_authentic
    validates :name, :email, :presence => true
    validates :email, :uniqueness => true
 
end

Controllers

class AdminController < ApplicationController
 
  before_filter :require_user  
 
   def index
 
   end
 
 end

Views

<%= form.label :name %><br />
 
<%= form.text_field :name %><br />
 
<br />
 
<%= form.label :email %><br />
 
<%= form.text_field :email %><br />
 
<br />
 
<%= form.label :password, form.object.new_record? ? nil : "Change password" %><br />
 
<%= form.password_field :password %><br />
 
<br />
 
<%= form.label :password_confirmation %><br />
 
<%= form.password_field :password_confirmation %><br />

<h1>Register</h1>
 
<% form_for @user, :url => users_path do |f| %>
 
  <%= render :partial => "form", :object => f %><br />
 
  <%= f.submit "Register" %>
 
<% end %>

<h1>Edit user</h1>
 
<% form_for @user, :url => user_path do |f| %>
 
  <%= render :partial => "form", :object => f %>
 
  <%= f.submit "Update" %>
 
<% end %>
<br /><%= link_to "View user", user_path %>

<p>
 
  <b>Name:</b>
 
  <%=h @user.name %>
 
</p>
 
<p>
 
  <b>E-mail:</b>
 
  <%=h @user.email %>
 
</p>
<p>
 
  <b>Login count:</b>
 
  <%=h @user.login_count %>
 
</p>
<p>
 
  <b>Last request at:</b>
 
  <%=h @user.last_request_at %>
 
</p>
<p>
 
  <b>Last login at:</b>
 
  <%=h @user.last_login_at %>
 
</p>
<p>
 
  <b>Current login at:</b>
 
  <%=h @user.current_login_at %>
 
</p>
<p>
 
  <b>Last login ip:</b>
 
  <%=h @user.last_login_ip %>
 
</p>
<p>
 
  <b>Current login ip:</b>
 
  <%=h @user.current_login_ip %>
 
</p>
 
<%= link_to 'Edit', edit_user_path %>

<h1>Login</h1>
 
<% form_for @user_session, :url => sign_in_path do |f| %>
 
  <%= f.label :email %><br />
 
  <%= f.text_field :email %><br />
 
  <br />
 
  <%= f.label :password %><br />
 
  <%= f.password_field :password %><br />
 
  <br />
 
  <%= f.check_box :remember_me %>
 
<%= f.label :remember_me %><br />
 
  <br />
 
  <%= f.submit "Login" %>
 
<% end %>

<h1>Admin panel</h1>
 
<%= link_to "Users", users_path %><br />

Routes.rb

#for users
 
scope :path => '/admin' do
 
    resources :users #places all the "users" links behind /admin URL prefix
 
 end
 
#for user_sessions
 
match "/admin"    => "admin#index"
 
get   "sign_in"   => "user_sessions#new"
 
post  "sign_in"   => "user_sessions#create"
 
match "sign_out"  => "user_sessions#destroy"

 
Using Akamai's Content Control Utility SOAP API E-mail
Tuesday, 28 December 2010 12:24

Akamai's Content Control Utility (CCU) SOAP API has a malformed WSDL (at https://ccuapi.akamai.com/ccuapi-axis.wsdl).  Here is what soapUI shows:

ccuapi-1

 

Here, on the other hand, is the properly formatted SOAP envelope that works:

<?xml version="1.0" encoding="UTF-8"?> 
        <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 
xmlns:ns1="http://www.akamai.com/purge"
 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
          <SOAP-ENV:Body>
            <ns1:purgeRequest>
              <name xsi:type="xsd:string">uname</name>
              <pwd xsi:type="xsd:string">passwd</pwd>
              <network xsi:type="xsd:string"></network>
              <opt SOAP-ENC:arrayType="xsd:string[0]" xsi:type="ns1:ArrayOfString">
                <item xsi:type="xsd:string">action=remove</item>
                <item xsi:type="xsd:string">type=arl</item>
              </opt>
              <uri SOAP-ENC:arrayType="xsd:string[1]" xsi:type="ns1:ArrayOfString">
                <item xsi:type="xsd:string">' + url + '</item>
              </uri>
            </ns1:purgeRequest>
          </SOAP-ENV:Body>
        </SOAP-ENV:Envelope>

See the differences?

Below is an implementation of it in Rails using the Savon gem:

The check_urlink function:

def check_urlink(urlink)
urlink = "http://" + urlink unless (urlink.blank? or urlink.starts_with?("http://"))    
end

 
Ruby on Rails > Will_paginate - customizing the styling E-mail
Tuesday, 23 November 2010 21:22

The method below doesn't work in the Rails 3 version of will_paginate.  Look here for instructions for on that.

 

Sometimes you get very specific directions from designers, and sometimes there are places where they're not able or willing to budge.  Recently I had a project, built in Ruby on Rails, where the pagination needed to look like:

 

will_paginate

By default, will_paginate provides links for "Previous page", "Next page", and numerical links for the pages in your collection.

 

The Rails will_paginate gem/plugin provides a lot of styling flexibility out of the box, but this was something that needed customizing at the code level.  Thankfully the project is well-designed and allows for easy extensibility.  (The code that follows is not mine, but unfortunately I've lost the original location of where I found it, otherwise I would attribute it to its original source.)

 

Here's what I had to do:

  1. The black square separator was cut as an image from the layout PSD
  2. Created the HTML and set the CSS for the static elements on the page (the "page" text and the black square separator in the image above)
  3. Create a special helper in my Rails application to overwrite the default WillPaginate::LinkRenderer class.  I called this file pagination_list_link_renderer.rb and placed it under /app/helpers. (code below)
  4. In the view where the pagination was to show, I implemented it with the following:

<%- if @count > 12 -%><span class="medium" style="display:inline">page&nbsp;</span>
<%- end -%><%= will_paginate @entries, :renderer => PaginationListLinkRenderer %>

 

The pagination_list_link_renderer.rb file looks like:

 
jQuery/jCarousel - image/content misalignment E-mail
Thursday, 11 November 2010 20:49

(I know this would be much easier to demonstrate with an example, and I'll try to get one up soon.)

Problem

When using the jCarousel plugin for jQuery for a carousel in your website that shows not just an image but associated content that changes with each image, it can happen that if you click rapidly on the previous/next buttons the content and the associated content will get out of synchronization, so that, for example:

  • Clicking twice rapidly forward will display Image[2] and Associated_content[3], because while the image is slowly sliding in the text content responds to the two rapid clicks and advances twice.

Solution

I had this happen recently in a project for a client, and this is the workaround I devised for the problem:

function nextClick() {
 
var index = $('.active').index();
 
if(index < /*total number of items */){
 
     // remove class active from last item
 
     $('.active').removeClass('active')
 
     // add "active" class to the index+1 div with class "associated"
 
     $('div.associated').eq(index+1).addClass('active');
 
     // advance carousel
 
     carousel.next();
 
     }
 
     window.setTimeout(function(){
 
          $('.next').one('click', nextClick);
 
          }, 1000);
 
     return false;
 
}

The key here are the .one() jQuery method (instead of the more common .click() method) and the window.setTimeout JavaScript method.

jQuery's .one() binds the click event just once, and each time the window.setTimeout JavaScript method kicks in, disables the button for 1000 milliseconds, then enables it again and rebinds .one() to let the user click the navigation buttons once again.

 

As for jCarousel, here's how that's configured:

jQuery(document).ready(function() {
 
jQuery('#carousel').jcarousel({
 
     visible: 1,
 
     scroll: 1,
 
     start: /* number of items */,
 
     wrap: null,       buttonNextHTML: null,
 
     buttonPrevHTML: null,
 
     initCallback: mycarousel_itemLoadCallback
 
     });
 
});

The callback function mycarousel_itemLoadCallback looks like:

function mycarousel_itemLoadCallback(carousel, state){
 
// all your regular code in this function
 
     jQuery('.next').one('click', nextClick);
 
     jQuery('.prev').one('click', prevClick);
 
}

And prevClick() is configured just like nextClick() above, except the .eq value is index-1 instead of index+1.

 
All-in-One-SEO-Pack plugin and WordPress 2.8.6 and earlier E-mail
Saturday, 06 November 2010 16:21

It's a known issue that the form submit button disappears when using the latest version of the All In One SEO Pack plugin with older versions of WordPress.  I'm running WordPress 2.8.6 and it was an issue for me.

 

Here is the fix.

 
WordPress: Restrict access with PHP sessions E-mail
Sunday, 05 September 2010 18:33

For a recent WordPress website, I used the following code in the theme's functions.php file to restrict access to those 21 years of age or above.  (I know anyone can answer "Yes" even if they're not 21, but for legal purposes the question has to be asked.)  Additionally, I added simple code to allow bots to get through to crawl the site for SEO purposes.

// check for bots
$bots = array(
 'AdsBot [Google]'   => array('AdsBot-Google', ''),
 'Alexa [Bot]'    => array('ia_archiver', ''),
 'Alta Vista [Bot]'   => array('Scooter/', ''),
 'Ask Jeeves [Bot]'   => array('Ask Jeeves', ''),
 'Baidu [Spider]'   => array('Baiduspider+(', ''),
 'Exabot [Bot]'    => array('Exabot/', ''),
 'FAST Enterprise [Crawler]' => array('FAST Enterprise Crawler', ''),
 'FAST WebCrawler [Crawler]' => array('FAST-WebCrawler/', ''),
 'Francis [Bot]'    => array('http://www.neomo.de/', ''),
 'Gigabot [Bot]'    => array('Gigabot/', ''),
 'Google Adsense [Bot]'  => array('Mediapartners-Google', ''),
 'Google Desktop'   => array('Google Desktop', ''),
 'Google Feedfetcher'  => array('Feedfetcher-Google', ''),
 'Google [Bot]'    => array('Googlebot', ''),
 'Heise IT-Markt [Crawler]' => array('heise-IT-Markt-Crawler', ''),
 'Heritrix [Crawler]'  => array('heritrix/1.', ''),
 'IBM Research [Bot]'  => array('ibm.com/cs/crawler', ''),
 'ICCrawler - ICjobs'  => array('ICCrawler - ICjobs', ''),
 'ichiro [Crawler]'   => array('ichiro/2', ''),
 'Majestic-12 [Bot]'   => array('MJ12bot/', ''),
 'Metager [Bot]'    => array('MetagerBot/', ''),
 'MSN NewsBlogs'    => array('msnbot-NewsBlogs/', ''),
 'MSN [Bot]'     => array('msnbot/', ''),
 'MSNbot Media'    => array('msnbot-media/', ''),
 'NG-Search [Bot]'   => array('NG-Search/', ''),
 'Nutch [Bot]'    => array('http://lucene.apache.org/nutch/', ''),
 'Nutch/CVS [Bot]'   => array('NutchCVS/', ''),
 'OmniExplorer [Bot]'  => array('OmniExplorer_Bot/', ''),
 'Online link [Validator]' => array('online link validator', ''),
 'psbot [Picsearch]'   => array('psbot/0', ''),
 'Seekport [Bot]'   => array('Seekbot/', ''),
 'Sensis [Crawler]'   => array('Sensis Web Crawler', ''),
 'SEO Crawler'    => array('SEO search Crawler/', ''),
 'Seoma [Crawler]'   => array('Seoma [SEO Crawler]', ''),
 'SEOSearch [Crawler]'  => array('SEOsearch/', ''),
 'Snappy [Bot]'    => array('Snappy/1.1 ( http://www.urltrends.com/ )', ''),
 'Steeler [Crawler]'   => array('http://www.tkl.iis.u-tokyo.ac.jp/~crawler/', ''),
 'Synoo [Bot]'    => array('SynooBot/', ''),
 'Telekom [Bot]'    => array('
This e-mail address is being protected from spambots. You need JavaScript enabled to view it
', ''),
 'TurnitinBot [Bot]'   => array('TurnitinBot/', ''),
 'Voyager [Bot]'    => array('voyager/1.0', ''),
 'W3 [Sitesearch]'   => array('W3 SiteSearch Crawler', ''),
 'W3C [Linkcheck]'   => array('W3C-checklink/', ''),
 'W3C [Validator]'   => array('W3C_*Validator', ''),
 'WiseNut [Bot]'    => array('http://www.WISEnutbot.com', ''),
 'YaCy [Bot]'    => array('yacybot', ''),
 'Yahoo MMCrawler [Bot]'  => array('Yahoo-MMCrawler/', ''),
 'Yahoo Slurp [Bot]'   => array('Yahoo! DE Slurp', ''),
 'Yahoo [Bot]'    => array('Yahoo! Slurp', ''),
 'YahooSeeker [Bot]'   => array('YahooSeeker/', ''),
);
 
 
$botvisit = 0;
 
$agent = strtolower($_SERVER['HTTP_USER_AGENT']);
 
//loop through array and see if the user agent contains a bot name
foreach($bots as $botname => $bot){
 //if visit from bot, let them through
    if(stripos($agent,$bot[0])!==false){
    
     $botvisit = 1;
    }
}

This function checks whether the PHP session is set.  If it is, that means the user has already specified they are over 21 and they don't need to be shown the "splash page" again, and can be sent straight to the main content page of the site.

function session_set() {
 if(is_front_page()) {                  // is user on front page?
 
  if (isset($_SESSION['allowed'])) {           // is session set?
   echo '<script type="text/javascript">
   window.location = "' . get_permalink(6) . '";    //if so, skip splash page and go to content
   </script>';
  }
 }
}

This function checks whether someone trying to access an inner page on the website has already declared they are over 21.  If so, they can continue, otherwise they are sent to the splash page where they declare they are over 21.

function no_direct_access() {
 if(!is_front_page() && $botvisit != 1) {             // not front page or bot visitor?
  if(!isset($_SESSION['allowed'])) {                  // session not set?
   echo '<script type="text/javascript">
   window.location = "' . get_bloginfo("url") . '";     // take user to splash screen
 
   </script>';
  }
 }
}

add_action('wp_head', 'session_set');
add_action('wp_head', 'no_direct_access');

Remember to also add the simple code from my Cookies and Session in WordPress article to your site's wp-config.php file to enable PHP sessions site-wide.

On the splash page, where the user declares they are over 21, you need something like the following code.  The first a tag is linked to the content homepage, and additionally sets the PHP session variable $_SESSION["allowed"] which is used in all the functions above.  The second a tag simply shows a JavaScript alert. 

<div id="age">
   <a href="<? echo get_permalink(6); $_SESSION['allowed'] = "over_21"; ?>" title="Go"></a>
   <a href="#" onclick="inform()" title="Not allowed" style="left:97px;"></a>
</div>

JavaScript function inform() invoked by the second a tag above.

<script type="text/javascript">
function inform() {
    alert("You must be the required age of 21 to view this site");
    return false;
}
</script>

 
«StartPrev12345678NextEnd»

Page 2 of 8