10.30.2008
Is JSF Fixable?
I've been wanting to write about JSF for a while now. I was an early adopter and big proponent of JSF when it first appear at the surface of Java land. Since then, I've worked on many projects that used this framework and slowly but surely, my opinion changed about this framework.
In Subbu opinion, JSF is not fixable because it was not designed with web architecture in mind. The framework is not RESTful and requires POST for everything. JSP proponent argue that RESTful is coming to JSF and it is even already supported right now when you use JBoss Seam.
It's true, that JSF is getting better. Facelets will become part of JSF (it should have been there from day though - why make a brand new framework and use broken things like JSPs). JSF 2.0 will make some space for GETs and JBoss Seam fixes a lot of other issues with JSF.
However, if JSF is not Fixable, it is not solely because it is not RESTful. JSF is broken at many places.
The JSF request processing life-cycle is one of the worst thing about JSF. There is so many steps in the life-cycle, different ways to exit the normal processing and this gives something many entry points for custom components (one for each steps in the life-cycle). Things can go wrong at many places without even touching your application code.
Closely related to the request processing life-cycle, is the component model. In JSF, components are a kind of black box. But when you think that black box should be mostly independent on the framework (beside the connecting part with the framework), there is so many implementation leak in JSF, that your component must be ported to every JSF implementation. I say ported and not tested... Because the order in which the component tree is processed can make you component fail. You have to make so many assumption based on the JSF implementation (things that are not present in the specification) that you actually need to choose your JSF implementation carefully and will never be guaranteed that your component will survive even minor updates in the JSF implementation.
Want to add to functionality to an existing component? There is no easy way to do that. The best way, is to take the source code (if available) for the component and refactor it with some new name and then hack you way through it. And when I say hack, I mean it in a bad way! I've never saw a component source code that was easily readable and understandable. Large component sets like ADF Faces are notably ugly. Even the Swing component model (which is quite complex) is super simple compared to JSF component model.
It looks like in JSF, the worst thing about leaking abstractions made it into the framework and the good thing about encapsulation where left out.
And before I get flamed that you will be able, with JSF 2.0 - which is still in draft, to create composite components soon... I have to respond that soon is not now.
One of a good indication that JSF is broken beyond fixable, is that you cannot do simple thing simply. Want to create a new component, then you have something like 3 files to edit. Want to create a new converter, again, three files needs to be edited. I know that good thing came in sets of three, but this is not the kind of good I want.
Another good indication that JSF is broken beyond fixable, is when thing go wrong. Good luck finding you problem... Facelets help you a bit by giving you a more precise location of the issue. But it does not always work. When you look at stack trace, you have to scroll hundreds of line before you see the line from you application. But what the hell do you do when you don't even have any of you application code in the stacktrace? Why on earth, you are have a stacktrace that is 30-40 level deep before even reaching an action method? Just to process a fringing POST.
In JSF, the framework is everywhere except where you need it.
Yes, there is some good things about JSF, I like the expression language (even is it is a bit inconsistent and not powerful enough to my taste). But the bad things are so deeply rooted. If you are just a user of the framework and that you are writing really simple applications, then it does look like JSF is great, but whenever you start digging a bit and adding a bit of complexity (Ajax anyone? Without another component set that takes a few MB?), the framework start eating up your nerves.
So would I ever recommend JSF again? Maybe maybe not, in the Java world, there is so many framework to choose from, that your cannot recommend one with the certainty that it will still be around one or two years from now. The only sure thing, is that JSF will be there because it is the standard... And this is very sad!
10.20.2008
Making Mr Bones work with jRuby
Want to make some GEM, Mr Bones (github) is going to help you setup your directory structure and comes with Rake tasks to package your application into a nice little Gem. The great thing about Mr Bones is that it does not add any dependencies to your Gem (like hoe does).
To create a new project, you just type bones [project_name] and you can start developping your application or library. You are done? type bones gem:install and your code is packaged and installed.
Now onto lib/bones/main.rb. In main.rb, there are calls to rake. This is one place I use the RUBY constant mentionned earlier to execute rake from the same interpreter location and not from the system PATH. To do that, I just dodiff --git a/lib/bones.rb b/lib/bones.rbindex 50a63b6..35dcd75 100644--- a/lib/bones.rb+++ b/lib/bones.rb@@ -4,8 +4,9 @@ module Bones# :stopdoc:VERSION = '2.1.1'PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))- WIN32 = %r/win32/ =~ RUBY_PLATFORM- DEV_NULL = WIN32 ? 'NUL:' : '/dev/null'+ HOME = File.expand_path(ENV['HOME'] || ENV['USERPROFILE'])+ DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:"+ RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']). sub(/.*\s.*/m, '"\&"') # Ruby Interpreter location - taken from Rake source code# :startdoc:
# Returns the path for Mr Bones. If any arguments are given,@@ -16,6 +17,7 @@ module Bonesargs.empty? ? PATH : File.join(PATH, args.flatten)end
+# call-seq:# Bones.require_all_libs_relative_to( filename, directory = nil )#@@ -46,7 +48,7 @@ module Bonesload bones_setup
rakefiles = Dir.glob(File.join(Dir.pwd, %w[tasks *.rake])).sort- import(*rakefiles)+ import(*rakefiles) unless rakefiles.empty?end
# TODO: fix file lists for Test::Unit and RSpec
diff --git a/lib/bones/main.rb b/lib/bones/main.rbindex b7ec28b..1cc074a 100644--- a/lib/bones/main.rb+++ b/lib/bones/main.rb@@ -181,9 +181,9 @@ class Mainif test(?f, File.join(output_dir, 'Rakefile'))beginFileUtils.cd output_dir- system "rake manifest:create 2>&1 > #{::Bones::DEV_NULL}"+ system "#{::Bones::RUBY} -S rake manifest:create 2>&1 > #{::Bones::DEV_NULL}"@io.puts "now you need to fix these files"- system "rake notes"+ system "#{::Bones::RUBY} -S rake notes"ensureFileUtils.cd pwdend@@ -363,7 +363,7 @@ class Maindef mrbones_dirreturn @mrbones_dir if defined? @mrbones_dir
- path = (::Bones::WIN32 ? ENV['HOMEPATH'].tr("\\", "/") : ENV['HOME'])+ path = ::Bones::HOMEpath = File.join(path, '.mrbones')@mrbones_dir = File.expand_path(path)end
diff --git a/lib/bones/tasks/setup.rb b/lib/bones/tasks/setup.rbindex 27d659b..a722c13 100644--- a/lib/bones/tasks/setup.rb+++ b/lib/bones/tasks/setup.rb@@ -5,7 +5,7 @@ require 'rake/clean'require 'fileutils'require 'ostruct'
-class OpenStruct; undef :gem; end+class OpenStruct; undef :gem if defined? gem; end
# TODO: make my own openstruct type object that includes descriptions# TODO: use the descriptions to output help on the available bones options@@ -123,36 +123,17 @@ import(*rakefiles)%w(lib ext).each {|dir| PROJ.libs << dir if test ?d, dir}
# Setup some constants-WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM unless defined? WIN32--DEV_NULL = WIN32 ? 'NUL:' : '/dev/null'--def quiet( &block )- io = [STDOUT.dup, STDERR.dup]- STDOUT.reopen DEV_NULL- STDERR.reopen DEV_NULL- block.call-ensure- STDOUT.reopen io.first- STDERR.reopen io.last- $stdout, $stderr = STDOUT, STDERR-end+DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:"++DIFF = if system("gdiff '#{__FILE__}' '#{__FILE__}' > #{DEV_NULL} 2>&1") then 'gdiff'+ else 'diff' end unless defined? DIFF++SUDO = if system("which sudo > #{DEV_NULL} 2>&1") then 'sudo'+ else '' end
-DIFF = if WIN32 then 'diff.exe'- else- if quiet {system "gdiff", __FILE__, __FILE__} then 'gdiff'- else 'diff' end- end unless defined? DIFF--SUDO = if WIN32 then ''- else- if quiet {system 'which sudo'} then 'sudo'- else '' end- end--RCOV = WIN32 ? 'rcov.bat' : 'rcov'-RDOC = WIN32 ? 'rdoc.bat' : 'rdoc'-GEM = WIN32 ? 'gem.bat' : 'gem'+RCOV = "#{RUBY} -S rcov"+RDOC = "#{RUBY} -S rdoc"+GEM = "#{RUBY} -S gem"
%w(rcov spec/rake/spectask rubyforge bones facets/ansicode).each do |lib|begin
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rbindex 5a60dc1..1965e1c 100644--- a/spec/spec_helper.rb+++ b/spec/spec_helper.rb@@ -3,6 +3,7 @@unless defined? BONES_SPEC_HELPERBONES_SPEC_HELPER = true
+require 'rubygems'require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bones]))
--
9.09.2008
Google Chrome... One week after.
- I assigned a shortcut for GMail. It now lives in its own window. What I like, is that when I'm in the GMail window and press Ctrl-T to open a new tab, instead of opening it in the GMail window, the tab is opened in my browser windows. This is how it should work. Great!
- In the rare cases when I close the browser and reopen it, all my tabs reload quickly without bogging down my system like Firefox did.
- Google Apps works better in Chrome that in Firefox... especially when the browser reload the tab when it's being opened.
- Lack of extensions. They say they will address the issue in a later version. In the meantime, bookmarklets are alleviating a bit the lack of extensions,
- Full page zoom please! The zoom feature of Chrome only increase font size. This is lame. Give me full page zoom like in Firefox 3,
- Directly open PDF and some other file types. When I download some file and the server set the content-disposition to attachment, Chrome absolutely want to save the file. Please give me the option of just opening the file with the associated application (saving it to a temporary location),
- They really should encrypt and password protect saved password. They cannot claim better privacy without this. I feel that should have done it from the start,
- Going to http://askville.amazon.com/ give me a page telling me that I'm not using a supported version of Safari,
- I encountered some layout issues. Most of them are minor. But when I access my internal time-sheet application (Oracle Applications), Some buttons used to submit the time-sheet are invisible.
- First week, first security update... Maybe this should go in part in the pros, but there is no notification (if you leave your browser open all the time) unless you go to the Tools | About Google Chrome menu.
9.03.2008
Google Chrome First Impression
- It's fast! Right when you start it, it really scream... It feels faster that Firefox 3. When you click, it looks like your actions are instantanious. For some reason, I'm even able to watch some higher definition movien in Flash that I were constantly pausing when using Firefox 3 (I don't beleive the browser makes Flash run better, but thats the feeling I get). Switching between tabs is also instantanious.
- Damn my screen look larger. The minimalist user interface make your browsing surface a bit larger. Granted, you could do that with IE and Firefox in Kiosk mode. But here, you have no choice... Just a big browsing area.
- Where are my mouse gestures? I was using a mouse gesture extension in Firefox. There seem to be no extensions and no way to write them for Chrome (I'm probably wrong here... I guess the APIs are not realsed yet but they'll be coming in a futher release).
- Better Privacy. Not so fast. They claim better privacy... But their password manager is not encrypted nor password protected! I don't realsy care about the other privacy features they offer. I don't believe they are much better that the ones in Firefox. I don't feel that the Internet is a dangerous place anyway.
- Gears bundled in. The Gears API are natively supported in the browser. They even automatically grant www.google.com the necessary rights. They shouldn't do that. However, they don't grant docs.google.com the Gears rights.
- I already miss extensions.
8.01.2008
7.08.2008
iPhone 3G in Canada - Do complain to Fido and/or Rogers
It looks like complaining to Fido (Fido is a subsidiary of Rogers) or Rogers about the iPhone 3G rate plan may lead to some changes...
I'm a current Fido subscriber, after a two week vacation, away from Canadian news, I saw the announced rate plan for the soon to be release iPhone 3G in Canada. While I was not expecting prices lower that AT&T's in the US, I was in complete disbelief when I saw the proposed rates. So as any good customer would do, yesterday I went to their web sites and wrote a complain (I complained about their highly priced rate plans and said that I would not buy the iPhone and consider switching to one of their competitor. I closed my complain telling them that I would write to the CRTC about how they abuse their GSM monopoly in Canada to offer non-competitive pricing).
Just a few minutes ago, I received a call from someone named S. Martin from Fido who wanted me to listen to what he had to say about my complain. The guy is certainly not a classic PR representative with only corporate mambo gumbo going out of his mouth. He seemed to trully care about me not leaving Fido for Bell. He did repeat some of the PR stuff I saw on some news site. The interesting part was that he told me that the proposed rate plans were not definitive, that they may change. He told me not to do anything before the iPhone 3G is launched, that nothing was final until July 11th. They will listen to the market and adjust.
He also told me that the CRTC (the equivalent of the FCC here in Canada) did not rule wireless pricing so it was useless for me to write them a letter. That I would be wasting my time. I guess this really mean that they don't want get the wrath of the CRTC if too many people write about this issue.
The conversation with M. Martin was always cordial and on a gentle tone.
So I guess the only thing to do right now, is to be patient and wait... Do not crumble under the iPhone 3G hype (too soon) and wait a little bit for them to see that we are not interested in their overpriced rate plans. They will have to adjust their pricing model and offer something a lot better than what they are offering right now.
2.27.2008
JNA love NXT
I started working on something to bring some Ruby love to my Lego Mindstorm NXT. I know, there is already one or two projects that aims to do that, but the first one is dormant and the other one is at a fairly early stage.
The first thing that needs to be done before accomplishing anything useful on the NXT, is to connect to it via USB or (preferably) Bluetooth. This can be done in two ways : use the Lego Fantom API or use direct communication via a serial link.
Communicating via a serial link from Ruby involves using the ruby-serialport gem which seems to be no longer maintained. The other options to work with the serial port (besides using platform specific stuff) would be to use JRuby and the Java Communication API. But Java Comm only support Solaris and Linux. There is also RXTX but it is not that simple to get started with. If serial port communication was the only option, I would probably choose ruby-serialport to get things done (which ruby-nxt is using).
However, there is the option of using the Lego Fantom API. This API comes as a shared library that gets installed whenever you install the Lego Mindstorm software. It is available for Windows and Mac OS. This API is used by the Lego Mindstorm software so I assume it is well tested and will be maintained as long as there is a market for the NXT. It has the added benefit to completely abstract the actual communication link between the PC and the NXT. This mean that you no longer have to choose to work with USB or Bluetooth, the same code will work with both. While I would have likes to see the Fantom API available for Linux, this is not a big showstopper for me as I guess that most Mindstorm users use either Windows or Mac OS.
To use the Fantom API from Ruby, I need a way to map shared library functions to Ruby. This can be done using Ruby DLX, Ruby/DL or ruby-dl2. Again, none of these projects seems to be active at the moment. On the other hand, in Java land, there is JNA. Which is currently gathering some buzz and is being actively maintained. JNA allows to map shared library functions to Java interfaces is a really easy and concise way. Even if I want to ultimately use Ruby to talk to my robot, I don't care if my Ruby code must run under JRuby. So JNA is a good fit for me to get quickly started and not care to much about the low level communication stuff needed to connect to the NXT.
It is now time to start playing around and send some commands to my Lego robot.
So the first thing that need to be done, is to create the Java interface that will be used to map the Fantom API. Here is a minimalist version of it (it just contains the API function needed for this example):
Fantom.java
package treelaws.fantom; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.ptr.ByteByReference; import com.sun.jna.ptr.IntByReference; import com.sun.jna.win32.StdCallLibrary; import java.nio.Buffer; /** * Provides access to the Lego Mindstorm Fantom Library * @author Emmanuel Pirsch */ public interface Fantom extends StdCallLibrary { Fantom INSTANCE = (Fantom) Native.loadLibrary("fantom", Fantom.class); /** * * @param searchBluetooth indicate if we also search bluetooth devices. * @param bluetoothSearchTimeout the timeout in second when searching bluetooth devices. * @param status OUT * @return */ Pointer nFANTOM100_createNXTIterator(boolean searchBluetooth, int bluetoothSearchTimeout, Status status); /** * * @param iNXTIterator * @param resourceName length must be at least 256. * @param status */ void nFANTOM100_iNXTIterator_getName(Pointer iNXTIterator, byte[] resourceName, Status status); void nFANTOM100_iNXTIterator_advance(Pointer iNXTIterator, Status status); Pointer nFANTOM100_iNXTIterator_getNXT(Pointer iNXTIterator, Status status); void nFANTOM100_destroyNXTIterator(Pointer iNXTIterator, Status status); int nFANTOM100_iNXT_sendDirectCommand(Pointer iNXT, boolean requireResponse, Buffer commandBuffer, int commandBufferLength, byte[] responseBuffer, int responseBufferLength, Status status); }
So now, I can easily connect to the NXT using something like :
String name= "T-NXT";
Status status= new Status(); Pointer iNXTIterator= fantom.nFANTOM100_createNXTIterator(true, 30, status); try { while(!Status.Statuses.NO_MORE_ITEMS_FOUND.equals(status.getStatus())) { byte[] resourceName= newResourceName(); fantom.nFANTOM100_iNXTIterator_getName(iNXTIterator, resourceName, status); System.out.println(asString(resourceName)); if (asString(resourceName).contains(name)) { Pointer iNXT= fantom.nFANTOM100_iNXTIterator_getNXT(iNXTIterator, status); if (Status.Statuses.SUCCESS.equals(status.getStatus())) { return iNXT; } else { throw new UnableToCreateNXTException(); } } fantom.nFANTOM100_iNXTIterator_advance(iNXTIterator, status); } } finally { if (iNXTIterator != null) { fantom.nFANTOM100_destroyNXTIterator(iNXTIterator, status); } }
And then start a robot program (already uploaded to the NXT) with :
String filename= "scorpion.rxe" Status status= new Status(); byte[] filenameBytes= filename.getBytes(); ByteBuffer command= ByteBuffer.allocate(filenameBytes.length+1+1); command.put((byte)0x00); command.put(filenameBytes); command.put((byte)0x00); fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status);
And this is just the beginning... Over the next few weeks (as time permit), I will add support to the rest of the Fantom API and add some nice Java classes to wrap this functionality and completely hide the Fantom API. Once this is done, I will release this code and start working on the JRuby side of things.
For completion, here are the NXT, Status and FantomUtils classes (The NXT class is the starting point of the Fantom API wrapper):
NXT.java
package treelaws.mindstorm; import com.sun.jna.Pointer; import java.nio.ByteBuffer; import treelaws.fantom.Fantom; import treelaws.fantom.Status; import static treelaws.fantom.FantomUtils.*; /** * * @author Emmanuel Pirsch */ public class NXT { private static Fantom fantom= Fantom.INSTANCE; private String name; private Pointer nxtPointer; public NXT(String name) { this.name= name; this.nxtPointer= connect(name); } public void startProgram(String filename) { Status status= new Status(); byte[] filenameBytes= filename.getBytes(); ByteBuffer command= ByteBuffer.allocate(filenameBytes.length+1+1); command.put((byte)0x00); command.put(filenameBytes); command.put((byte)0x00); fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, command.capacity(), null, 0, status); System.out.println(status.getStatus().toString()); } public void stopProgram() { ByteBuffer command= ByteBuffer.allocate(1); command.put((byte)0x01); fantom.nFANTOM100_iNXT_sendDirectCommand(nxtPointer, false, command, 1, null, 0, new Status()); } private Pointer connect(String name) { Status status= new Status(); Pointer iNXTIterator= fantom.nFANTOM100_createNXTIterator(true, 30, status); try { while(!Status.Statuses.NO_MORE_ITEMS_FOUND.equals(status.getStatus())) { byte[] resourceName= newResourceName(); fantom.nFANTOM100_iNXTIterator_getName(iNXTIterator, resourceName, status); System.out.println(asString(resourceName)); if (asString(resourceName).contains(name)) { Pointer iNXT= fantom.nFANTOM100_iNXTIterator_getNXT(iNXTIterator, status); if (Status.Statuses.SUCCESS.equals(status.getStatus())) { return iNXT; } else { throw new UnableToCreateNXTException(); } } fantom.nFANTOM100_iNXTIterator_advance(iNXTIterator, status); } } finally { if (iNXTIterator != null) { fantom.nFANTOM100_destroyNXTIterator(iNXTIterator, status); } } throw new NXTNotFoundException(); } }
Status.java
package treelaws.fantom; import com.sun.jna.Structure; import java.util.EnumSet; import java.util.HashMap; /** * * @author Emmanuel Pirsch */ public class Status extends Structure { public int code; public byte[] filename= new byte[MAX_FILENAME_LENGTH]; public int lineNumber; public Statuses getStatus() { return Statuses.fromCode(code); } static int MAX_FILENAME_LENGTH= 101; public enum Statuses { SUCCESS, FIRST(0), PAIRING_FAILED(5), BLUETOOTH_SEARCH_FAILED(6), SYSTEM_LIBRARY_NOT_FOUND(7), UNPAIRING_FAILED(8), INVALID_FILENAME(9), INVALID_ITERATOR_DEREFERENCE(10), LOCK_OPERATION_FAILED(11), SIZE_UNKNOWN(12), DUPLICATE_OPEN(13), EMPTY_FILE(14), FIRMWARE_DOWNLOAD_FAILED(15), PORT_NOT_FOUND(16), NO_MORE_ITEMS_FOUND(17), TOO_MANY_UNCONFIGURED_DEVICES(18), COMMAND_MISMATCH(19), ILLEGAL_OPERATION(20), BLUETOOTH_CACHE_UPDATE_FAILED(21), NON_NXT_DEVICE_SELECTED(22), RETRY_CONNECTION(23), POWER_CYCLE_NXT(24), FEATURE_NOT_IMPLEMENTED(99), FW_ILLEGAL_HANDLE(189), FW_ILLEGAL_FILENAME(190), FW_OUT_OF_BOUNT(191), FW_MODULE_NOT_FOUND(192), FW_FILE_EXISTS(193), FW_FILE_IS_FULL(194), FW_APPEND_NOT_POSSIBLE(195), FW_NO_WRITE_BUFFERS(196), FW_FILE_IS_BUSY(197), FW_UNDEFINED_ERROR(198), NO_LINEAR_SPACE(199), FW_HANDLE_ALREADY_CLOSED(200), FW_FILE_NOT_FOUND(201), FW_NOT_LINEAR_FILE(202), FW_END_OF_FILE(203), FW_END_OF_FILE_EXPECTED(204), FW_NO_MORE_FILES(205), FW_NO_SPACE(206), FW_NO_MORE_HANDLES(207), FW_UNKNOWN_ERROR_CODE(208), LAST(999); private int code; private static HashMap<Integer, Statuses> code_map= new HashMap<Integer, Statuses>(); static { for(Statuses s : EnumSet.allOf(Statuses.class)) { code_map.put(s.code, s); } } Statuses() { this.code = 0; } Statuses(int code_offset) { this.code= -142000 - code_offset; } static Statuses fromCode(int code) { return code_map.get(code); } } }
FantomUtils.java
package treelaws.fantom; import com.sun.jna.Native; /** * * @author Emmanuel Pirsch */ public class FantomUtils { static int DEFAULT_RESOURCE_NAME_ALLOCATION_SIZE = 256; public static final byte[] newResourceName() { return new byte[DEFAULT_RESOURCE_NAME_ALLOCATION_SIZE]; } public static final String asString(byte[] s) { return Native.toString(s); } }
2.12.2008
Making IRB work properly under Windows when using an international keyboard
Until today, I've been looking for a way to solve a problem I have running irb under Windows. The problem is that I'm using an international keyboard layout and keys like [, ], { and } (which are really useful in Ruby) could not be inputted in irb when readline is enabled.
I did some research on the web, but the solution did not worked for me (and it looks like I'm not the only one). A few weeks ago, I stopped looking for a solution as I decided to do my RoR work on a Linux Virtual machine. For some reason, I had to start working on Windows again. So today, I decided to dig a bit deeper. As the proposed solution used an .inputrc file, I investigated the format of the file and came across a post that described a problem using the Meta key shortcut (M-) in a .inputrc file. The response to the problem suggested to use "\e" instead of "M-". So I did that and guess what? it worked!
So putting everything together :
- Add the HOME environment variable => HOME=%USERPROFILE%
- Create a .inputrc file in your home folder (cd %USERPROFILE%) with the following content :
- For added completion/history management, create a .irbrc file in your home folder with the following content:
"\e[": "[" "\e]": "]" "\e{": "{" "\e}": "}" "\e\": "\" "\e|": "|" "\e@": "@" "\e~": "~"
require 'irb/completion'
ARGV.concat([ "--readline", "--prompt-mode", "simple" ])
module Readline
module History
LOG = "#{ENV['HOME']}/.irb-history"
def self.write_log(line)
File.open(LOG, 'ab') {|f| f << "#{line}\n"}
end
def self.start_session_log
write_log("\n# session start: #{Time.now}\n\n")
at_exit { write_log("\n# session stop: #{Time.now}\n") }
end
end
alias :old_readline :readline
def readline(*args)
ln = old_readline(*args)
begin
History.write_log(ln)
rescue
end
ln
end
end
#Readline::History.start_session_log
require 'irb/ext/save-history'
IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history"
2.04.2008
Chased by the statically typed ghosts
It looks like statically typed ghosts are out there again. They like to chase the Poor little dynAmiCally typed Man. Most of the time, the ghosts run faster than the little Pac-Man but their bloat makes them crawl when they turn corners or go in tunnels. Little Pac-Man goal is to clear the level in the most efficient way. Little Pac-Man has some special features to help him mind his business. For each level that he has to go through, he can use specials pills. These pills allows him to open up the ghosts and mixes in some different behaviour into the ghosts. De-routed by this ability, the ghosts turns blue. Little Pac-Man understand that this power comes at a certain price, this is why he uses it sparingly and do not abuse it to eat the ghosts (unless he knows he can eat them all in one shot as this will make him score big-time!) as they will quickly return to their static behaviour. As we can see in the picture, it takes four (4) ghosts to take on Pac-Man.
1.29.2008
Another take on a jQuery.equalizeCols plugin
Using CSS, it is very difficult to create multiple columns with the same height. One must use Javascript to modify <div> height so multiple div share the same height.
This blog post offer a jQuery plugin to do that. However, this particular script cannot be applied many time to the same elements. I had to adjust the height of multiple column that have their content updated with some Ajax requests. So I needed to be able to readjust the height of the columns after the content has been updated. Using the script from the post, my column would grow after each update.
I did like how it was adjusting the height of the column. It add a div with the missing height to all the columns that needs to be adjusted.
So I came up with the following script. This script can be applied many times to the same elements without any adverse effects.
1: jQuery.fn.equalizeCols = function() {
2: jQuery(this).filter(function() {return jQuery(this).find(".auto_fill").length == 0;}).each(function() {
3: jQuery(this).append(jQuery("<div class='auto_fill'></div>").height(0));
4: }).css("height", "auto");
5:
6: var max_height= 0;
7: jQuery(this).each(function() {
8: var self= jQuery(this);
9: var height= self.height() - self.find(".auto_fill").height();
10: max_height=height > max_height ? height : max_height;
11: });
12:
13: jQuery(this).each(function() {
14: var self= jQuery(this);
15: var height= self.height() - self.find(".auto_fill").height();
16: self.find(".auto_fill").height(max_height - height);
17: });
18: }
On line 2-3, an empty <div> is added to each element that may have their height adjusted. The <div> has an initial height of 0 pixels. Note that then <div> will be added only if the containing <div> does not already have this empty <div>.
On line 6-11, the maximum height is calculated.
On line 13-17, the empty div height is adjusted so the total height of the containing div is equal to the previously calculated maximum height.
So to adjust the height of some elements, you just have to do this :
1: $(".columns").equalizeCols();
1.22.2008
1.18.2008
Fixing attachment_fu on Windows
Like many others, I've encountered issue when developing Rails applications using attachment_fu on Windows. After doing some research, I've come up with the following solution to the problem.The problem has two parts :
- Size is not included in the list error message,
- Timeout error when uploading to S3.
Fixing "Size is not included in the list" error message
Some people have reported that there is a timing issue, when trying to get the file size, with Tempfile on Windows. It seems that the size of the file is not properly reported by Windows after writing data to it. Proposed solutions for this problem include :- Sleeping in a loop as long as the file size is 0,
- Reading back the entire file in memory.
I think I found a better and less patchy solution for this issue: forcing the OS to flush the file to disk before reading it's size.
Here is the code to do it :
@tmpfile.flush
@tmpfile.stat.size
Fixing the Timeout error when uploading to S3
This issue is related to opening files for reading on Windows. On Windows, you have to open the file in binary mode. So patching attachment_fu is simple :module Technoweenie
def save_to_storage
(temp_path ? File.open(temp_path, "rb") : temp_data), # added , "rb"
bucket_name,
:content_type => content_type,
:access => attachment_options[:s3_access]
@old_filename = nil
true
I've also included a fix from someone else (which was not enough in itself to solve my S3 upload problem):
def temp_data
f.binmode
return f.read
Wrapping it up
So I put all this code in lib/attachment_fu_patch.rb and required it in environment.rb.Problem fixed!
Note, I did not test it on other OSes, but these fixes should not have any adverse effects.