Posted by Chris Blackburn Thu, 18 Jun 2009 21:03:00 GMT

Running RCOV, and having it complain about a p articular plugin that it should not have been looking at in the first place, made me want to find a way to exclude it. In my usual practical fashion, I hit google up for a quick way to exclude the unwanted plugin.

Here it is re-posted from Dan Mange’s Blog

config.plugins = Rails::Initializer.new(config).send(:find_plugins, config.plugin_paths).map {|path| File.basename(path)}
config.plugins -= %W(plugin_one plugin_two)

UPDATE: JJ Barrett has an update for Rails 2.x… http://www.jjbarrett.net/archives/plugin-ordering-and-exclusion-in-rails-20

  config.plugins = config.plugin_locators.map do |locator|
                     locator.new(Rails::Initializer.new(config)).plugins
                   end.flatten.map{|p| p.name.to_sym}
  config.plugins -= [:do_not_load_plugin_1, :do_not_load_plugin_2]

Posted by Chris Blackburn Fri, 29 May 2009 22:23:00 GMT

‘jaap’ wrote a rake task to convert your Rails application from using Gettext translation into the new I18n support, which is really great!

WARNING: Before you run the new rake task rake gettext_to_i18n:transform, read “So How Do You Fix It?” below

OK, so you’ve followed along in the article and now you want to test out your new localization system. Problem is when you try to start the server up script/server you get this error message:

/Users/cblackburn/Source/ruby/bols/lib/active_support/memoizable.rb:71:in `path': can't modify frozen object (TypeError)

…or something similar.

What happened? Since you have frozen Rails and the rake task does not exclude the frozen vendor/rails directory, it modified memoizable.rb in ActiveSupport causing this error.

So How Do You Fix It?

Do one of the following:

  • Backup lib/active_support/memoizable.rb before you run the rake task, then restore it after.
  • Restore lib/active_support/memoizable.rb using your SCM.

For this project in my case it was

svn revert lib/active_support/memoizable.rb

Posted by Chris Blackburn Thu, 06 Nov 2008 06:00:00 GMT

Between the time your migration files get generated and deploying to production with your new application you will likely need to add indexes to one or more of your tables. How do you know which tables need indexes and which fields in those tables to index?

Here are some tips to help figure out where the likely bottlenecks will occur with regard to your database tables:

  1. Always index @*id@ foreignkey fields. Let’s say you have 2 tables, @users@ and @addresses@. You may have migration file code in @./db/migrate@ that looks like this:
  ...
  def self.up
    create_table "users", :force => true do |t|
      t.column :login, :email             :string
    end
  end
  ...
  ...
  def self.up
    create_table "addresses", :force => true do |t|
      t.column :user_id       :integer
      t.column :address       :string
    end
  end
  ...

You will always want to index the foreignkey @userid@, with few exceptions. So simply add the @add_index@ line like this:

  ...
  def self.up
    create_table "addresses", :force => true do |t|
      t.column :user_id       :integer
      t.column :address       :string
    end
    add_index "addresses", :user_id
  end
  ...

This will facilitate much faster associations when you lookup related models like this view code:

  <%=h user.address.address %>

  # OR as the case may be...

  <%=h address.user.email %>
  1. As a rule of thumb index any column for which you will be using to lookup a row or set of rows. For example, if you will be doing something like this:
  u = User.find_by_email('test@example.com')

… then you will want to index the email field.

  1. Run explain plans on your associations to determine where indexes would benefit the queries. Now this one is more tricky unless you know about the query_analyzer plugin. This nifty little tool will automatically dump out explain plans into your logfiles in development mode that look like this example from the README file:
# development.log

P Load (0.008669)
 => SELECT p.* FROM p INNER JOIN d ON p.id = d.p_id WHERE (d.p_id = 2 AND ((d.type = 'P')))

Analyzing P Load

select_type | key_len | type   | Extra       | id | possible_keys | rows | table  | ref    | key
 ----------------------------------------------------------------------------------------------------
SIMPLE      |         | ALL    | Using where | 1  |               | 74   |    d   |        |
SIMPLE      | 4       | eq_ref | Using where | 1  | PRIMARY       | 1    |    p   | d.p_id | PRIMARY

Now what this tells you is that the table @d@ is missing an index because MySQL is having to look at “ALL” the records. Whenever you see a @type@ of @ALL@ and/or @possible_keys@ is empty it should be a red flag telling you to index something.

If we had followed tips 1 and 2 this explain plan would be much different, showing reference types of @ref@ and @eq_ref@ which is typically a good thing.

For more information see the README file here: http://agilewebdevelopment.com/plugins/query_analyzer.

Posted by Chris Blackburn Tue, 18 Sep 2007 05:00:00 GMT

If you are getting an error message like this:

ActiveRecord::StatementInvalid: Mysql::Error: Table configurable_settings doesn't exist

… when trying to use Jacob Radford’s http://agilewebdevelopment.com/plugins/acts_as_configurable

Just do this:

./script/console development ConfigurableSetting.create_table

Of course replace ‘development’ above with whatever environment you need.

Posted by Chris Blackburn Thu, 31 May 2007 23:48:00 GMT

If you are experiencing the error: “undefined class/module MyClass” when fetching data from memcached, be assured that you are not alone. It is a known bug and the simplest way I know of to get around it is to reference the class or classes right before you retrieve data from the cache.

For example, if the following code causes the problem:

if not (genres = Cache.get(key))
  genres = Genre.find(:all, :condition => "platform_id = 1")
  Cache.put(key, genres, 60*60*24) # cache for 1 day
end

… then this code will work around it:

Genre
if not (genres = Cache.get(key))
  genres = Genre.find(:all, :condition => "platform_id = 1")
  Cache.put(key, genres, 60*60*24) # cache for 1 day
end

Notice the ‘Genre’ reference before the if statement. Some have reported success by using the ‘model’ statement within the controller, however that is deprecated. This workaround will get you going again.

Posted by Chris Blackburn Tue, 22 May 2007 23:46:00 GMT

Have you ever wanted to look at the call stack without raising an exception to do it?

caller.each {|c| puts c}

Posted by Chris Blackburn Thu, 18 Jan 2007 06:00:00 GMT

If you are getting this error message when trying to use RoR ActiveRecord, there is a simple workaround… read on.

MySQL has a problem with how Rails sends the username and password when initiating the connection through the socket.

A quick fix/workaround is to create a MySQL user without a password. Add it to your database.yml file and restart your Mongrel/Webrick server.

Posted by Chris Blackburn Fri, 23 Mar 2007 05:00:00 GMT

As promised here is the second article about validating credit cards with Ruby.

This edition wraps a module and class around the code in preparation for future enhancement.

Feel free to change whatever you want. If you do please send me an update and at least give me credit for the original.

To use it just do something like this in your controller:

if params[:cc]
  begin
    cc = Payment::CreditCard.new(params[:cc])
    cc.valid?
    @user.update_attribute(:verified_at, Time.now)
  rescue Exception => e
    logger.debug e.inspect
    flash[:notice] = "Your profile has been updated. However, #{e}."
    return
  end
end

This assumes you have a form like this somewhere in your view:

<%= text_field :cc, :name, :class => "text-n" %>

<%= select :cc, :card_type, FundingSource.get_card_types %>

<%= text_field :cc, :number, {:size => 16, :maxlength => 16, :class => "text-n"} %>
(15-16 digits)

<%= date_select :cc, :expiration, :start_year => Time.now.year, :end_year => Time.now.year+10, :use_month_numbers => true, :discard_day => true, :include_blank => true, :order => [:month, :year] %>

<%= text_field :cc, :security_code, :size => 4, :class => "text-s" %>
(3 Digit Code on back of credit card)

Obviously, this code comes with no warranty of any kind and could hurt your application, your data, your home, your feelings, etc. Don’t sue me. Other than that, use it as you see fit. Just please give me some credit.

module Payment
    class CreditCard

        CARD_TYPE = {
            :master_card => 0,
            :visa => 1,
            :american_express => 2,
            :diners_club => 3,
            :discover => 4
        }

        ###################################################################
        # Construct the object and do minimal validation
        # Params:
        #   :name                 - cardholder's name (optional, for future use)
        #   :card_type            - type of card (required)
        #   :number               - 15-16 digit card number (required)
        #   :security_code    - 3-4 digit security code (required, only
        #                                           needs to exist and be the right length)
        #   'expiration(1i)'  - expiration year (required).
        #   'expiration(2i)'  - expiration month (required).
        ###################################################################
        def initialize(attributes = {})
            # Cardholders Name
            if (@name = attributes[:name]).nil? or @name.empty?
                raise "Cardholder Name is required"
                return
            end
            # Card Type
            @card_type = attributes[:card_type]
            @card_type = convert_cc_type(@card_type)
            if @card_type.nil? or !CARD_TYPE.has_value?(@card_type)
                raise "A valid Card Type is required"
                return
            end
            # Card Number
            if (@number = attributes[:number]).nil? or @number.to_i.nil? or
                    @number.length < 15
                raise "A valid Card Number is required"
                return
            end
            # Security Code
            if ((@security_code = attributes[:security_code]).nil? or
                    @security_code.to_i.nil? or
                    (@security_code.length != 3 and
                        @card_type != CARD_TYPE[:american_express]) or
                    (@security_code.length != 4 and
                        @card_type == CARD_TYPE[:american_express]))
                raise "A valid Security Code Number is required"
                return
            end
            # Card Expiration
            if ((@card_expiration_month = attributes['expiration(2i)']).nil? or
                    @card_expiration_month.to_i.nil? or
                    @card_expiration_month.to_i < 1 or
                    @card_expiration_month.to_i > 12)
                raise "A valid Card expiration month is required"
                return
            end
            if ((@card_expiration_year = attributes['expiration(1i)']).nil? or
                    @card_expiration_year.to_i.nil?)
                raise "A valid Card expiration year is required"
                return
            end
            @card_expiration_date = Time.gm("#{@card_expiration_year}".to_i,
                @card_expiration_month).next_month
            if (@card_expiration_date <= Time.now)
                raise "Card is expired."
                return
            end
        end

        ###################################################################
        # Check number format for given card type and check whole number
        # against the Mod 10 algorithm
        ###################################################################
        def valid?
            valid_format = false
            pass_check = false
            # check format
            case @card_type
                when CARD_TYPE[:master_card]
                    valid_format = @number[/^5[1-5][0-9]{14}$/] == @number
                when CARD_TYPE[:visa]
                    valid_format = @number[/^4[0-9]{12}$|^4[0-9]{15}$/] == @number
                when CARD_TYPE[:american_express]
                    valid_format = @number[/^3[4|7][0-9]{13}$/] == @number
                when CARD_TYPE[:diners_club]
                    valid_format = @number[/^30[0-5][0-9]{11}$|^3[6|8][0-9]{12}$/] == @number
                when CARD_TYPE[:discover]
                    valid_format = @number[/^6011[0-9]{12}$/] == @number
            end
            raise "credit card number is invalid." if valid_format == false

            # check Mod 10
            reverse_card_num = @number.reverse
            sum = 0
            reverse_card_num.scan(/./).each_with_index do |digit, index|
                digit = digit.to_i
                digit *= 2 if index % 2 != 0
                if digit.to_s.length == 2
                    first_num = digit.to_s[0..0]
                    second_num = digit.to_s[1..1]
                    digit = first_num.to_i + second_num.to_i
                end
                sum += digit
            end
            pass_check = sum % 10 == 0 ? true : false
            raise "credit card is invalid." if pass_check == false
            true
        end

        ###################################################################
        # Return a safe (masked) credit card number
        # char is the mask character, count is the number of last x digits
        # to display unmasked
        ###################################################################
        def masked_number(char = 'X', count = 4)
            len = @number.to_s.length
            card_number = char * (len - count)
            card_number << @number[-count..-1]
        end

    ###################################################################
    private
    ###################################################################

        ###################################################################
        # This allows the user to pass raw_type = 'visa',
        # 'american express', etc.
        ###################################################################
        def convert_cc_type(raw_type)
            card_type = nil
            if raw_type.is_a?(String)
                card_type = CARD_TYPE[raw_type.downcase.gsub(' ', '_').to_sym]
            else
                card_type = raw_type
            end
        end

    end

end

Posted by Chris Blackburn Tue, 20 Mar 2007 05:00:00 GMT

Recently, while working on one of our client’s projects, I found myself needing to validate credit card numbers. Of course the most secure way to do it is to use your merchant services (i.e., Verisign PayFlowPro, etc.). However most often those services cost anywhere from $15/month and 3 cents per transaction and up.

For most purposes the business wants to simply prevent its customers from fat-fingering their credit card numbers when typing it in. But there are several pieces of information that can be validated for any given credit card like: expiration date, billing address, security code, cardholder’s name, etc.

For our purposes we simply wanted to protect customers from their own fat fingers. The Luhn algorithm does nicely for that purpose, and for the most part, keeps honest people honest.

Here it is using Ruby:

def validate_credit_card(number)
  reverse_card_num = @number.reverse
  sum = 0
  reverse_card_num.scan(/./).each_with_index do |digit, index|
    digit = digit.to_i
    digit *= 2 if index % 2 != 0
    if digit.to_s.length == 2
      first_num = digit.to_s[0..0]
      second_num = digit.to_s[1..1]
      digit = first_num.to_i + second_num.to_i
    end
    sum += digit
  end
  pass = sum % 10 == 0 ? true : false
end

To use it just pass in your 15-16 digit credit card number and it will return a boolean for pass or fail.

When I have some more time I’ll post some additional validation code that CBCI currently uses for credit cards.

Posted by Chris Blackburn Mon, 23 Apr 2007 05:00:00 GMT

We needed a convenient way to store a series of values in a single field. A bitfield would not do because it would only allow a value of 1 or 0 for each field, and we wanted to be able to store at least 3 values for each field.

So I found Gabriel Gironda’s acts_as_bitfield plugin and made a few tweaks. ActsAsBytefield is the result. It allows storage of 256 values in each field, or 255 discrete values ranging from 0-255 (unsigned char or byte) for each value in a MySQL varchar(255) field.

NOTE: Gabriel’s site has been down for some time. My own repository has also been down but is now back up. Sorry for the inconvenience.

Values greater than 255, or less than 0 wrap around. For example, setting a bytefield column to -1 will actually set it to 255, and setting it to -2 will actually set it to 254, etc.

Installation

./script/plugin install acts_as_bytefield

OR
./script/plugin install https://svn.cbciweb.com/svn/plugins/acts_as_bytefield

Documentation (RDoc)

rake rdoc

Testing

The tests require rspec

rake test

Usage

  • Create a string column in your table - varchar(255)
  • Add this directive to your model:
 acts_as_bytefield :bytefield_column_name, :fields => [:field_name_one, :field_name_two]

You will then be able to use the model in the following manner, for example:

 class SomeModel < ActiveRecord::Base
   acts_as_bytefield :bfield, :fields => [:test, :production]
 end

 obj = SomeModel.new(:test => 1)

 obj.test          #=> 1
 obj.test?         #=> 1
 obj.production?   #=> 0

 obj.test = 0
 obj.production = 65
 obj.save

 obj.test?         #=> 0
 obj.production?   #=> 65

 # The field that's storing the value:
 obj.bfield        #=> "\000A"