Archive for the 'Development' Category

Prototype JS default function arguments

August 03rd, 2010 | Category: Development

$H() is just one more reason to use Prototype. Coming from a Ruby background, I’ve become very fond of using hashes. I often use them in handling optional arguments to methods, and setting default values for those arguments. I was creating a function in JS, and realized using such methodology would allow for a much more descriptive function.

function handle_notice(options) {
  var default_options = $H({show_notice: false, show_ignore: false})
  options = default_options.merge($H(options))
  ...
}

The above sets up a hash of default options, then merges or updates the default_options with the options hash passed to the function. This allows me to use any combination of arguments, or pass none to use the default.

1 comment

Handy ActiveRecord Migration Methods

April 23rd, 2010 | Category: Development,Rails,Uncategorized

We are undergoing a massive DB schema redesign. Much of the work is just tightening up the schema through optimizing indexes, setting default boolean and integer fields, and updating NULL allowed fields. Here is a little module I wrote to save massive amounts of code.

module MigrationHelper
  def index_exists?(table, lookup)
    lookup = Hash[:columns, Array(lookup)] unless lookup.is_a?(Hash)
    lookup.symbolize_keys!
    lookup.each{|key,value|lookup[key]=(value.is_a?(Array))?(value.collect{|v|v.to_s}):(value.to_s)}
    indexes(table).collect(&lookup.keys.first).include?(lookup.values.first)
  end
 
  def change_column_nulls(table_name, nullable, *column_names)
    column_names.each do |column_name|
      table_columns = columns(table_name) 
      column_def = table_columns.select{|c|c.name == column_name.to_s}
      raise "Unknown column name #{column_name} for #{table_name}\nAvailable columns: #{table_columns.collect(&:name).to_sentence}" if column_def.empty?
      change_column table_name, column_name, column_def.first.type, :null => nullable
    end
  end
 
  def change_column_defaults(table_name, default, *column_names)
    column_names.each do |column_name|
      change_column_default table_name, column_name, default
    end
  end
end

I fond it necessary to have the index_exists? method mainly for my development purposes. When I’m optimizing tables by doing complex indexes, I often do them right on the DB first before throwing them into a migration. By having this check I can easily skip over it if it already exists. It takes the same options as add_index. You can pass table and column names, or just table and index name. Here are some usage examples:

add_index :table, :column unless index_exists?(:table, :column)
add_index :table, [:column1, :column2] unless index_exists?(:table, [:column1, :column2])
add_index :table, [:column1, :column2, :column3], :name => "some_index_name" unless index_exists?(:table, :name => "some_index_name")

The method change_column_nulls is used to mass change the NULL allowed columns for a given table. Really not much to it, but saves a lot of code over doing change_table or change_column. Here is the usage:

change_column_nulls :table, false, :column1, :column2

And lastly change_column_defaults is just a wrapper around change_column_default for mass assignment. Usage is the same as change_column_nulls passing the default values as the second argument. I thought about combining the two, but it didn’t offer very much savings.

These were tested using MySql 5. I’m not sure about the compatibility with other servers.

No comments

Rails ActiveRecord MySql varbinary

March 26th, 2010 | Category: Development,Rails

Although ActiveRecord has the following warning about database specific column types: “You may use a type not in this list as long as it is supported by your database (for example, “polygon” in MySQL), but this will not be database agnostic and should usually be avoided. ActiveRecord” You may have optimizations specific to a database server like MySql’s varbinary. Here is how to create the a table with a varbinary(1000) column.

create_table :page_error_sources do |t|
  t.column :source, 'varbinary(1000)', :null => false
  t.timestamps
end
No comments

Solr 1.4 Upgrade P2 – Out with Rsync CollectionDistribution in with JavaReplication

March 03rd, 2010 | Category: Development,Linux

Solr 1.4 made replication from Master to Slave servers a whole lot easier.  Before solr1.4 we were using rsync via the snapshooter and snappuller scripts As seen here.  This method worked OK, but intermittently we would see the snapshooter or puller fail for various Java reasons (Memory usually).

Please see Solr Java-Based Replication for setup overview.  I will cover specific modification I had to make compared to what is in their documentation.  In my solrconfig.xml on my Master server, I have the following:

<requestHandler name="/replication" class="solr.ReplicationHandler" >
      <lst name="master">
          <!--Replicate on 'startup' and 'commit'. 'optimize' is also a valid value for replicateAfter. -->
          <str name="replicateAfter">startup</str>
          <str name="replicateAfter">commit</str>
      </lst>
  </requestHandler>

Then on the slave servers, I have:

<requestHandler name="/replication" class="solr.ReplicationHandler" >
      <lst name="slave">
          <!--fully qualified url for the replication handler of master . It is possible to pass on this as a request param for the fetchindex command-->
          <str name="masterUrl">http://{solr_host}:{solr_port}/solr/${solr.core.name}/replication</str>
          <!--Interval in which the slave should poll master .Format is HH:mm:ss . If this is absent slave does not poll automatically. 
           But a fetchindex can be triggered from the admin or the http API -->
          <str name="pollInterval">00:00:30</str>
          <str name="httpReadTimeout">10000</str>-->
       </lst>
  </requestHandler>

Substitute {solr_host} and {solr_port} with your specific settings. IMPORTANT: Note the ${solr.core.name} variable.  This makes it so the slaves will poll from the correct MultiCore path on the Master server.

3 comments

Solr 1.4 Upgrade – Out with Faceting, In with MultiCore

February 01st, 2010 | Category: Development

I finally found some time, though it was a bit forced, to look into the new CoreAdmin functionality released in Solr 1.4.  Our app was using the same index for multiple apps distinguished through Faceting.  This worked fine for a while, but the larger our indexed data got, the slower the queries were returned.  The responsiveness of every app was now dependent on the total indexed size.  This was not ideal, so we upgraded to 1.4 to see how using multiple cores could help.

Upgrading was quite easy.  See http://wiki.apache.org/solr/Solr1.4 for upgrading notes.  What I will focus on are the details of using and automating the MultiCore functionality.  To enable the dynamic SolrCore functionality, you just need to put the following XML in solr.xml inside your solr_home directory.

<solr persistent="true" sharedLib="lib">
 <cores adminPath="/admin/cores">
 </cores>
</solr>

Don’t worry about creating any cores at this time.  The next thing I did was create a directory called “default” in my solr_home directory to store all of my shared configs for each core.  I then copied the conf/ directory from my solr_home into the newly created default dir.

The final step is to create the cores needed.  You can automate this by posting the following request and checking the XML response, or by just pasting this into your browser.

http://{host}:{port}/{solr_home}/admin/cores?action=CREATE&name={core_name}&instanceDir=default&dataDir={/full/path/to/solr/home}/{core_name}

Here is how you automate the request using xml-simple and ruby

#set the url string equal to the above command
response = Net::HTTP.get_response(URI.parse(url)).body
xml = XmlSimple.xml_in(response, {'VarAttr' => 'name', 'ContentKey' => '-content', 'KeyAttr' => 'name'})
raise "Failed to create solr core" if xml["lst"]["responseHeader"]["int"]["status"].to_i != 0

Master Solr server after upgrade to 1.4 and multi-core


Solr slave server after upgrade to 1.4 and multi-core

No comments

Aggregation with MySQL and ActiveRecord

July 31st, 2009 | Category: Development,Rails

I had some data that I wanted to aggregate by year for a flash graphing app called amCharts. Here was the SQL query I used to gather the data:

SELECT AVG(column1) AS column1, AVG(column2) AS column2 FROM table1 t1
JOIN table2 t2 ON t2.id = t1.tabel2_id
GROUP BY YEAR(t2.datetime_field)

Rather than using this sql in ActiveRecord via find_by_sql, I figured I’d make it pretty and use the built in find method. Here is what I came up with:

Table1.find(:all, :select => 't2_id, AVG(column1) as column1, AVG(column2) as column2', :joins => :t2, :group => 'YEAR(t2.datetime_field)')

The :select option allows you to define the attributes you want returned with the AR object. I’m using MySQL to do the averaging of the data via the AVG function. I would normally use the :include option for joins, but I couldn’t get it to work with this query, so the :joins option seemed to work well by passing it the association definition from the model. The :group option simply works the same as the SQL group by. Just pass it a sql fragment, in my case just the year field portion of the datetime field.

No comments

Passing around arguments in rake tasks

April 03rd, 2009 | Category: Development,Rails

So I had the need to pass arguments from one rake task to another.  This is easy to do from the command line, but gets a bit tricking when its being called from within another rake task.  So here is the task that is being called:

  1 namespace :database do
  2   desc %{Dump DB to create restore data in case of error}
  3   task :dump, :tables, :needs  => [:setup] do |t, args|
  4     filename = "#{DB_NAME}#{'-'+args.tables.values.join('-') unless args.tables.nil?}-#{DB_DUMP_TIME}.sql.gz"
  5     puts "Dumping the database to '#{filename}'"
  6 
  7     cmd = ""
  8     cmd << "echo 'SET AUTOCOMMIT=0;'|gzip> #{filename};"
  9     cmd << "echo 'SET FOREIGN_KEY_CHECKS=0;'|gzip>> #{filename};"
 10     cmd << "mysqldump -u #{DB_USER} -p'#{DB_PASSWORD}' "
 11     cmd << "-h #{DB_HOST} #{DB_NAME} "
 12     cmd << "--tables #{args.tables.values.join(' ')} " unless args.tables.nil?
 13     cmd << "|gzip>> #{filename};"
 14     cmd << "echo 'SET FOREIGN_KEY_CHECKS=1;'|gzip>> #{filename};"
 15     cmd << "echo 'COMMIT;'|gzip>> #{filename};"
 16     cmd << "echo 'SET AUTOCOMMIT=1;'|gzip>> #{filename};"
 17 
 18     if system(cmd)
 19       puts "Successfully ran databse:dump to restore run:"
 20       puts "rake database:reload DB_DUMP_TIME='#{DB_DUMP_TIME}'"
 21     else
 22       puts "rake database:dump was unsuccessful"
 23     end
 24   end

So this task is used to dump the database for our app. I added the ability to pass in an optional hash to allow it to only dump the specified tables. Here are the important lines to note.

task :dump, :tables, :needs  => [:setup] do |t, args|

The “tables” argument specifies what arguments are being passed in. The “t” and “args” block defines the task and the arguments. Your “tables” hash is now available through “args” values.

Now when I call this task from within another rake task, I can do something like this.

tables = {:table1 => 'users', :table2 => 'roles'}
Rake::Task["database:dump"].invoke(tables)

Note, I define the hash outside of the invoke because it is used by two other tasks, but it could be directly passed to the task as an argument.

No comments

Rails, Time Zones, and Scheduling

November 06th, 2008 | Category: Development,Rails

The time zone support in rails has vastly improved since the integrated TZInfo into Rails 2.1. However if you have any scheduled tasks stored in your DB, you’ll notice it quickly breaks down. Here is a short and sweet explanation:
I have an app that delivers system notification to users at a specified time they set. I have a field in my “reminders” table called “deliver_at” which is a datetime field. The data is stored as a UTC timestamp. Everything works as expected until daylight savings time, or the user changes their time zone. This is because the “deliver_at” timestamp has no context of what offset was used when the record was created. So when daylight saving rolls around your messages are now delivered an hour off. Or if the user moves to a new timezone, it will be off by x hours. Here is the solution I came up with.
I added a field to the “reminders” table called “offset”.

add_column :reminders, :offset, :decimal, :precision =>; 4, :scale => 2, :null =>; false, :default => 0.0

This field is recorded when the record is first created. You can override the attribute using write_attribute and read_attribute, but I was already doing a seporate update call, so I just stuck it there. *Important, make sure you are setting the Time Zone on login.

self.update_attributes!(:delivered_at => Time.zone.now, :next_reminder_at => date, :offset => user_utc_offset)

Here is my user_utc_offset method:

def user_utc_offset
    TimeZone[self.user.timezone].utc_offset.to_f / 1.hour.to_f
end

I’m using floats because not all the offsets are whole numbers. Same reason I used a decimal field in the DB. Then when I calculate my next reminder delivery, I basically do this:

offset_adjustment = adjust_for_difference_in_offsets
		  case self.reminder.repeat
		      when "daily"
			  next_reminder = reminder.deliver_at + 1.day - offset_adjustment.hours
		      when "weekly"
			  next_reminder = reminder.deliver_at + 7.days - offset_adjustment.hours
		      when "monthly"
			  next_reminder = reminder.deliver_at + 1.month - offset_adjustment.hours
		   end

I have a case statement to calculate daily, weekly, and monthly reminders. Here is the method to calculate the offset difference.

def adjust_for_difference_in_offsets
    user_utc_offset - self.offset.to_f
  end

I use the above for the mailer, but we need to do the same for displaying the datetime stamp to the user. So I created a helper method that does like so:

def reminder_repeat_datetime(reminder)
    offset_adjustment = reminder.adjust_for_difference_in_offsets
    case reminder.repeat
      when "monthly"
        repeat = "#{reminder.deliver_at.strftime('%d')}th"
      when "weekly"
        repeat = "#{reminder.deliver_at.strftime('%A')}s"
      when "daily"
        repeat = "Daily"
      when "once"
        repeat = "#{reminder.deliver_at.strftime('%B %d, %Y')}"
    end
    "#{repeat} at #{(reminder.deliver_at - offset_adjustment.hours).strftime('%I:%M %p')}"
  end

A lot of the above is just pretty formatting of the datetime for the user. You could most likely just get away with something like the last line. That is called from my index view in my reminders controller like so:

<%= reminder_repeat_datetime(reminder) %>

So now no matter if the offset changes because of DST or the user changes their timezone, the offset will be calculated and applied to the datetime field.

No comments

My simple ruby backup

November 04th, 2008 | Category: Development,Linux

After using a Linux dev laptop for almost a year, I went back to a MacBook Pro for work. I had forgotten the pains of finding a good backup solution for a Mac. I needed something that would incrementally back up my work laptop and my home Linux box to the same external hard drive. After trying various solutions(Time Machine, CCC, iBackup, etc), they all seemed to either not work at all or hog up way too much room on the drive. So I decided to just write my own backup solution using Ruby and Rsync.

This initial release is very basic and only requires you have ruby and rsync installed. It supports Mac or Linux and can back up to any drive that supports hard links (ext2, ext3, hfs, hfs+). Future releases will include integration with s3 as well as Cron scheduling. For now I just wanted to share this with others having similar gripes.

Steps to getting up and running:
1. Download Ruby Backup Zip or Ruby Backup tarball.
2. Extract the contents of the above package.
3. Edit the backup_settings.yml.sample file and save it as backup_settings.yml
4. run the script via “ruby ruby_backup.rb” or “./ruby_backup.rb”

1 comment

Gem cache search depricated in boot.rb

October 23rd, 2008 | Category: Development,Rails

I was sick of getting the following deprication warning running rubygems version 1.3.0 and a rails 1.2.6 project.

“Gem::SourceIndex#search support for String patterns is deprecated
./script/../config/boot.rb:20 is outdated”

So I made some modifications to the boot.rb file so that it checks for the rails gem similar to the rails 2.x apps.  The gem itself is still loaded in the 1.2 fashoin.  Here is a diff on the changes

20,29c20,25
<       rails_gem = Gem.cache.search('rails', "=#{rails_gem_version}.0").sort_by { |g| g.version.version }.last
< 
<       if rails_gem
<         gem "rails", "=#{rails_gem.version.version}"
<         require rails_gem.full_gem_path + '/lib/initializer'
<       else
<         STDERR.puts %(Cannot find gem for Rails =#{rails_gem_version}.0:
<     Install the missing gem with 'gem install -v=#{rails_gem_version} rails', or
<     change environment.rb to define RAILS_GEM_VERSION with your desired version.
<   )
---
>       begin
>         gem "rails", rails_gem_version
>         gem_dir = Gem.path.select{|p| File.directory?("#{p}/gems/rails-#{rails_gem_version}")}.first
>         require "#{gem_dir}/gems/rails-#{rails_gem_version}/lib/initializer"
>       rescue Gem::LoadError => load_error
>         $stderr.puts %(Missing the Rails #{rails_gem_version} gem. Please `gem install -v=#{rails_gem_version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)

Hope this helps with those annoying warnings.

1 comment

Next Page »