Technology Musings

May 26, 2010

Snippets / A Simple HTML Geolocation Map with Twitter and Google Maps

JB

New Medio just released <a href="http://www.newmedio.com/site/postings/17?section_id=2">a cool new tool to do geolocation/geotagging maps of Twitter posts</a>.  Can be used HTML-only (no Javascript coding required!) or with a Rails plugin. 

June 26, 2009

Snippets / What's Taking So Long?

JB

There are a lot of performance analysis tools for Rails logs.  However, sometimes you just need something quick and dirty.  This one helped me out.  1-line perl script to show all Rails log lines which took more that 100milliseconds to produce:

perl -n -e 'print if(m/\((\d+\.\d+)ms\)/ && $1 > 100);' log/development.log

UPDATE - Here's another handy one:

 tail -20000 log/production.log|grep 'Completed in'|cut -d" " -f3,11|sort -n

December 18, 2008

Snippets / Docx and Open Packaging Conventions for Ruby and Rails

JB

I just finished the first iteration of my Ruby on Rails plugin for handling Microsoft's new Open Packaging Conventions file format, which is their new container format for XML-based file formats in Microsoft Office.  DOCX, Microsoft's new XML-based format for Word is probably the most widely used of these. 

Open Packaging Conventions is basically a zipfile containing one or more components, and those components' relationship to each other is defined by other files within the zipfile.

To add rubyopc to your Rails app, do:

script/plugin install http://rubyopc.googlecode.com/svn/trunk/rubyopc

Here is how you would make a DOCX file with it:

OpenPackagingConventions::Package.with_package("test.docx") do |p|
p.add_part("/word/document.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", <<EOF)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
<w:body>
<w:p w:rsidR="00EA68DC" w:rsidRPr="00C703AC" w:rsidRDefault="00EA68DC" w:rsidP="00EA68DC">
<w:pPr>
<w:rPr>
<w:lang w:val="es-ES_tradnl"/>
</w:rPr>
</w:pPr>
<w:r>
<w:t>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc at risus vel erat tempus posuere. Aenean non ante. Suspendisse vehicula dolor sit amet odio. Sed at sem. Nunc fringilla. Etiam ut diam. Nunc diam neque, adipiscing sed, ultrices a, pulvinar vitae, mauris. Suspendisse at elit vitae quam volutpat dapibus. Phasellus consequat magna in tellus. Mauris mauris dolor, dapibus sed, commodo et, pharetra eget, diam.
</w:t>
</w:r>
<w:r w:rsidRPr="00C703AC">
<w:rPr>
<w:lang w:val="es-ES_tradnl"/>
</w:rPr>
<w:t>
 Nullam consequat lacus vitae mi. Sed tortor risus, posuere sed, condimentum pellentesque, pharetra eu, nisl.
</w:t>
 </w:r>
</w:p>
</w:body>
</w:document>
EOF

p.add_part_to("/word/document.xml", "/word/styles.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", <<EOF)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="paragraph" w:styleId="Normal">
<w:name w:val="Normal" />
<w:rPr>
<w:b />
</w:rPr>
 </w:style>
</w:styles>
EOF

This will create a small word document called "test.docx".

You can find the official documentation for these file formats here.

 

August 25, 2008

Snippets / Rails Sorting

JB

I always have trouble formulating exactly how sorts should work on Rails controllers that are called from sortable_element.  Therefore, I created the following method for ApplicationController which helps out a lot:

  def sort_assoc_members(assoc, ary, position_method = :position)
ary.each_index do |ary_idx|
assoc.find(ary[ary_idx]).update_attributes(position_method => (ary_idx + 1))
end    
end

Then, to use it, just create a sort action like this:

def reorder
sort_assoc_members(WhateverItem, params[:whatever_list])
end

This makes life much easier.

August 13, 2008

Snippets / TinyMCE + Prototype AJAX + RAILS

JB

NOTE - I have no idea why the formatting here is all messed up. It should be usable anyway since the snippets are fairly small

Using TinyMCE from Ruby on Rails is sometimes a bit tricky.  We tend to assume that all HTML components work the same - they can be added and removed at will.  However, TinyMCE components have to be explicitly added and removed - and as such they can be really painful.

In addition, TinyMCE components have to be saved back to the form in order to be serialized by Prototype (which is used by Rails for remote forms - remote_form_for, link_to_remote, etc.).

Therefore, I came up with the following, which doesn't completely remove the pain, but it at least eases it somewhat.  First of all, it modifies the Prototype library slightly to trigger a tinyMCE.triggerSave(true, true) every time a form is serialized (i.e. for remote forms and links).  Now you don't have to remember to do that anymore! Next, it provides a simple RJS helper to unregister/reregister TinyMCE editors when doing Ajax page manipulation in Rails.

Anyway, here are the steps:

Step 1

Put the following code into application.js:

//Register all TinyMCE instances on the page
function register_all_tiny_mce_editors() {
tinyMCE.init({
mode : "textareas",
theme : "advanced",
editor_selector : "editor"
});    
//NOTE - if you have other registration functions, put them here, too.
}
//Unregister all TinyMCE instances on the page
function unregister_all_tiny_mce_editors() {
tinyMCE.triggerSave(true, true);
for(editor_id in tinyMCE.editors) {
editor_elem = $(editor_id);
if(editor_elem) {
editor = tinyMCE.editors[editor_id];
tinyMCE.remove(editor)
}
}
} 
//Convince Prototype to do a TriggerSave anytime it is doing form serialization
var original_form_serialize_elements = Form.serializeElements;
Form.serializeElements = function(elements, gethash) {
try {
//Copy TinyMCE values back onto the regular form elements
tinyMCE.triggerSave(true, true);                
} catch(err) {
//Don't do anything - just means TinyMCE isn't loaded
}
return original_form_serialize_elements(elements, gethash);
}

Step 2

Put the following code into your layout:

<%# Modify this to wherever you put TinyMCE %>
<%= javascript_include_tag "tiny_mce/tiny_mce.js" %>
<script type="text/javascript">
tinyMCE.init({
mode : "textareas",
theme : "advanced",
editor_selector : "editor"
});
<%# Add/modify the init statement to include any other options you want  %>
<%# Be sure to use the same code as you used above in the register_all_tiny_mce_editors() function %>
</script>
<%# PUT ALL OTHER JAVASCRIPT INCLUDES ___AFTER___ THIS ONE OR ELSE TINYMCE COULD BREAK!! %>

Step 3

Now, put the following in application_helper.rb:

  def tmce_update(page, &block)
page << "unregister_all_tiny_mce_editors();"
yield
page << "register_all_tiny_mce_editors();"
end

Step 4

Now, in all RJS files which could cause TinyMCE textareas to appear/disappear, you can do the following:

tmce_update(page) do
#Perform RJS operations which affect TinyMCE areas here 
end

And now all TinyMCE areas will start behaving for you!  Note that this ONLY works with RJS-based Ajax calls - it doesn't work if you use the :update parameter, but you shouldn't be using that anyway.  Always do Ajax updates with RJS!

July 07, 2008

Snippets / Scoped has_many :through

JB

In Rails, has_many :through is an excellent way to do join tables.  For example, I can have a person, a project, and a person_project to define the people I have assigned on projects.  This would be setup as follows:

class Person < ActiveRecord::Base
has_many :person_projects
has_many :projects, :through => :person_projects
end 
class Project < ActiveRecord::Base
has_many :person_projects
has_many :people, :through => :person_projects
end
class PersonProject
belongs_to :person
belongs_to :project
end

So, if I have a Project object called proj, I can do proj.people and get access to all the people on the project. I can also use it as a straight arra, and push individual Person objects onto proj.people, and the appropriate records in PersonProject will be created.

However, let's say that we add an additional value to PersonProject called "role".  Let's say this can be set to 'ADMIN', 'DESIGN', or 'TECH'.   Now pushing onto the proj.people array doesn't help much, because it doesn't fill out the role.  I created a snippet of code inspired by Josh Susser's solution, but which I think works a little better.  With this new code, I can rewrite the Project class (and the Person class, if I wanted) like this:

class Project < ActiveRecord::Base
has_many :person_projects
has_many :people, :through => :person_projects
scoped_has_many_through :admins, :through => :person_projects, :source => :person, :scope => { :role => "ADMIN" }
scoped_has_many_through :techs, :through => :person_projects, :source => :person, :scope => { :role => "TECH" }
scoped_has_many_through :designs, :through => :person_projects, :source => :person, :scope => { :role => "DESIGN" }
end

So the code to do that is as follows, although there's probably a more Rails-ish way to do this:

module ActiveRecord
class Base
def self.scoped_has_many_through(assoc, opts)
has_many_scope = opts.delete(:scope)
conditions_scope = Hash[*has_many_scope.to_a.map{|ent|
["#{opts[:through].to_s.pluralize}.#{ent[0]}", ent[1]]
}.flatten]
opts[:conditions] ||= {}      
if opts[:conditions].is_a?(Hash)
opts[:conditions] = opts[:conditions].merge(conditions_scope)
else
raise "Conditions must be a hash for conditions_scope!"
end
has_many assoc, opts do        
#Using define_method instead of def in order to make it lexically scoped
define_method(:construct_owner_attributes) do |reflection|
atts = super(reflection)
return atts.merge(has_many_scope)
end
end
end
end
end 

I have a few things like this.  I might package them together into a plugin sometime.  On the one hand, code snippets this small seem silly for a plugin.  On the other hand, plugins make it easily reusable, and perhaps I could batch several of them together, even if they aren't related.