Year2012

RubyMine Issues

RubyMine is my preferred IDE for Ruby development. I chose it for local and remote debugging from the browser. It has some space for improvement but it’s also pretty good. And support is excellent.

These are the issues that I’ve reported. This list is mostly for my own use.

  1. Replace in Path should be integrated into Find in Path
  2. The ‘Replace in Path’ functionality is completely broken
  3. Number of ‘Found usages’ depends on wether the button ‘Merge usages from the same line’ is pressed or not
  4. Exclude external libraries from a custom scope of ‘Find in Path’
  5. In a big text file, it takes up to 2 seconds to move the caret only 1 char
  6. Depending on path tooltip position, displayed document is garbled
  7. Depending on caret position, displayed document is garbled
  8. Replace in Path (with a grouping RegEx) makes a mess (depending on input)
  9. File comparison should behave differently for ending lines
  10. Breakpoints dialog floats on top even if the parent project is closed (it has no closing button either)
  11. Extract CSS is broken
  12. Dream remote debugging with the PassengerDebugger option
  13. How to remotely debug Rails in Apache + Passenger + RubyMine

Orderly require scripts from a bookmarklet

Abstracting a bit my bookmarklet for injecting jQuery, I’ve come to this one where you can orderly require needed scripts before executing a given payload.

javascript:(function () {

    var debug = !false;
    var requested = false;

    orderly(
        require_jQuery('1.8.3', 'j'),
        require_SimpleModal('j'),
        function() {
            payload(window.j);
        }
    );

    function payload(jQuery) {
        jQuery(function($) {
            $('body').click(function() {
                $('span:first').modal({
                    overlayClose: true,
                    opacity: 80,
                    overlayCss: {backgroundColor: "#000"},
                    maxHeight: 480,
                    maxWidth: 640,
                    containerCss:{
                        backgroundColor:"#fff",
                        borderColor:"#fff",
                        padding:20
                    }
                });
            });
        });
    }

    function require_jQuery(version, symbol) {
        return {
            url: 'http://ajax.googleapis.com/ajax/libs/jquery/' + version + '/jquery.min.js',
            is_loaded: function() {
                return !requested && typeof window[symbol] != 'undefined' && window[symbol].fn.jquery == version
                    ||  requested && typeof window.jQuery  != 'undefined' && window.jQuery.fn.jquery  == version;
            },
            before: function() {
                window.oldJQuery = window.jQuery;
                window.jQuery = window.undefined;
            },
            success: function() {
                jQuery.noConflict();
                window[symbol] = jQuery;
                console.info('jQuery ' + jQuery.fn.jquery + ' has been injected. (as "' + symbol + '")');
            },
            ensure: function () {
                window.jQuery = window.oldJQuery;
            }
        };
    }

    function require_SimpleModal(jQuerySymbol) {
        return {
            url: 'http://cdn.jsdelivr.net/simplemodal/1.4.2/jquery.simplemodal.1.4.2.min.js',
            is_loaded: function() {
                return !requested && typeof window[jQuerySymbol].modal != 'undefined'
                    ||  requested && typeof window.jQuery.modal        != 'undefined';
            },
            before: function() {
                window.oldJQuery = window.jQuery;
                window.jQuery = window[jQuerySymbol];
            },
            ensure: function () {
                window.jQuery = window.oldJQuery;
            }
        };
    }

    function orderly(i) {
        var stuff = Array.prototype.slice.call(arguments);
        if (typeof i === 'number') {
            stuff.shift();
        }
        else {
            i = 0;
        }
        if (i == stuff.length) return;

        console_log('orderly ' + i);
        switch (typeof stuff[i]) {
            case 'string':
                break;
            case 'function':
                stuff[i]();
                orderly.apply(null, [i+1].concat(stuff));
                break;
            case 'object':
                load_script(stuff, i);
                break;
            default:
                throw 'Expected a valid argument (' + i + ')';
                break;
        }
    }

    function load_script(stuff, i) {
        var current = stuff[i];
        var url = current.url;
        console_log('load_script: request for ' + url);

        requested = false;
        var is_loaded = current.is_loaded;
        if (is_loaded()) {
            console_log('load_script: already available');
            orderly.apply(null, [i+1].concat(stuff));
            return;
        }
        var before = current.before;
        var success = function() {
            call_function(current.success);
            call_function(current.ensure);
            orderly.apply(null, [i+1].concat(stuff));
        };
        var failure = function() {
            call_function(current.failure);
            call_function(current.ensure);
        };
        call_function(before);

        console_log('load_script: requesting');
        var s = document.createElement('script');
        s.setAttribute('src', url);
        document.getElementsByTagName('head')[0].appendChild(s);

        requested = true;
        var time = 0;
        var id = setInterval(function () {
            console_log('load_script: retry #' + time);
            if (is_loaded()) {
                clearInterval(id);
                console_log('load_script: done');
                success();
                return;
            }
            ++time;
            if (time == 50) {
                clearInterval(id);
                console_log('load_script: giving up');
                failure();
                return;
            }
            console_log('load_script: waiting');
        }, 100);
        console_log('load_script: requested');
    }

    function call_function(f) {
        return typeof f == 'function' ? f() : null;
    }

    function console_log(msg) {
        if (debug) console.log(msg);
    }
})();

The require_* functions could be made available from a central repository, so that you can find, copy and paste what you need. But going a bit forward, one could also make orderly support URIs like ‘ord://symbol:module/version’ or ‘ord://symbol:host/module/version’ like ‘ord://j:jquery/1.8.3’ or ‘ord://j:jquery/modal/1.4.2’. They could be used like this:

orderly(
        'ord://j:jquery/1.8.3',
        'ord://j:jquery/modal/1.4.2',
        function() {
            payload(window.j);
        }
    );

and because they are strings, they would not interfere with objects and functions. Execution will then be in two steps: first, request the definition object from the repository and, finally, request the script as specified by the definition object. This will be very similar to what Bundler does for Ruby.

Content thieves

From time to time I google “Login Dongle” to see if there are any new reviews of my plugin.

Sometimes I find reviews stolen from others. That’s a common issue these days.

But today, I’ve found a new breed of stolen content that deserves some attention.

original content
How to Improve the Security of your WordPress Blog
published on 2012-08-07 by Amit Agarwal

 

stolen content
How to Secure of your WordPress Blog: Improving WordPress Security
published on ??? by ???

Usually, a thief steals content by (automatically) copying it from its original site to their own. You can easily find stolen content by googling a short sentence of any post, between quotes. For example, today I see that that content has been stolen 355 times !!

But in this case the thief used substitutions, thus cleanly defeating the above search check. For example, let’s compare side by side the snippets that refer to my plugin in both versions of the content: original and stolen.

Here is the original version.

This plugin takes a very unique approach to protect your WordPress. It generates a bookmarklet with a secret question that you can add to you bookmarks. While on the WordPress login page, enter you credentials and then press this bookmarklet to get into your WordPress – the button on the login screen won’t work.

Here is the stolen one.

This tool requires a very exclusive strategy to secure your WordPress. It produces a bookmarklet with a key query that you can add to you favorites. While on the WordPress sign in web page, get into you experience and then media this bookmarklet to get into your WordPress – the option on the sign in display will not perform.

As you see, the stolen version has been obtained from the original one using these blind (i.e. automatic) substitutions:

plugin tool
takes requires
unique exclusive
approach strategy
protect secure
generates produces
secret key
question query
bookmarks favorites
login sign in
page web page
enter get into
credentials experience
press media
button option
login sign in
screen display
won’t will not
work perform

I’ve used bold to highlight substitutions that don’t work in the context.

There are two interesting reasons that explain why substitutions are highly effective for a thief even if they are not perfect.

The first is that the thief’s purpose is not to publish understandable news but it is to provide fresh content for their ads. And the second is that Google search engine finds substituted content too, so their ads get the same chances to get printed as the content they steal.

For example, in the results of the Login Dongle search, the original content appears at position 30 and the stolen one at 49. Not bad.

 

Fixing “uninitialized constant MysqlCompat::MysqlRes”

This is an old problem that affects an old Ruby gem: mysql (2.8.1). Apparently, it has a little bug somewhere that makes it look for a file in the wrong folder. I get this error always after installing a new app. The last time has been some minutes ago, after installing TracksApp 2.1, which is based on the old Rails 2.3.

The MySQL page about Ruby adapters speaks about two of them. One called MySQL/Ruby, and the other (after a huge name creation effort)… Ruby/MySQL. They are respectively provided by the gems mysql (2.8.1) and ruby-mysql (2.9.10). As you may have noticed, the first gem is the old version of the second. That is also confirmed by the dates they got updated, which is: 2.8.1, on 2009-08-21 and 2.9.10, on 2012-07-12.

Both adapters are authored by Tomita Masahiro. I do appreciate the effort he did, and his generosity in making them available to me, no doubt about that. But in my opinion, the name change was a mistake. I suppose he did it to tell the world they were two very different adapters (the old one C-based and the new one Ruby-based). Nonetheless, they really were two subsequent versions of the same thing.

The problem with TracksApp is exactly that. If Tomita Masahiro had stuck to the mysql name, then gem “mysql” in TracksApps would have referenced the latest version (what today is instead ruby-mysql) and all would have worked fine. But now, even with ruby-mysql installed, that gem requirement in TracksApp makes my system install the old mysql gem. That in turn makes Rails prefer mysql over ruby-mysql (why? lexical order?) and due to the known bug all my apps break.

My solution is to manually uninstall mysql each time it gets installed by some app. In fact I don’t know how to make my global Rails environment ignore mysql (2.8.1) even if some app requires it. I’ve tried with “bundle install –without mysql”, but mysql is a gem, not a group, so it does not work… My fix works because ruby-mysql perfectly replaces mysql and it’s just a drop-in, sharing the same adapter name (mysql).

Clearly, TracksApp is a new version of an old software, even if it is still based on Rails 2.3. If I was developing it, I’d have updated the Gemfile…

 

How to remotely debug Rails in Apache + Passenger + RubyMine