Filed under: rubygems

paperclip processors and after_post_process

A few things to note when trying to process an uploaded file with paperclip:

  • The only way a post-processor will run is if a style is defined

class MyModel < ActiveRecord::Base

has_attached_file :mp3, :url => '/:class/:id/:style.:extension', :path => ':rails_root/assets/:class/:id_partition/:style.:extension', :whiny => true, :styles => { :text => { :do_something => true } }, :processors => [:my_processor]

# ...

end

  • To access the file after processing but before paperclip is finished, use the :after_process callback -- model instance attributes can be modified and will be saved automatically as part of the paperclip process

class MyModel < ActiveRecord::Base

# ...

after_post_process :do_one_more_thing

# ...

private

def do_one_more_thing

logger.info "MyModel [paperclip] after_post_process"

logger.info "MyModel [paperclip] file #{mp3.queued_for_write[:original].path}"

# Do more with the file

# Make model attribute changes as needed

# self.my_attribute = "x"

end

end

Net::Flickr::People#find_by_user_id

...a short, random post... It's the end of the month, and I need to meet my self-imposed quota so that the archives have a link to October 2008. ;-) After looking around at the different Ruby Flickr libraries, Net::Flickr seems to be the most active today. It's undergoing some major refactoring for its next release. The current release is tagged 0.0.1 and Pre-Alpha. I think it was tagged this way to give people a warm fuzzy feeling when they use it on production apps. :-) Anyway, 0.0.1 Pre-Alpha needs a lookup to find a person by user_id. There's code in Net::Flickr::Person that does a lookup by user_id, but you need to know the person's email address or username. 0.0.1 Pre-Alpha Net::Flickr::People#find_by_user_id [sourcecode="ruby"] module Net; class Flickr class People # Looks up a Flickr user based on their user_id. # # See http://flickr.com/services/api/flickr.people.getInfo.html for # details. def find_by_user_id(user_id, args = {}) args['user_id'] = user_id response = @flickr.request('flickr.people.getInfo', args) Person.new(@flickr, response.at('person')) end end end; end [/sourcecode] Based on the current code in SVN, the next release might need something like this: [sourcecode="ruby"] module Net; class Flickr class People # Looks up a Flickr user based on their user_id. # # See http://flickr.com/services/api/flickr.people.getInfo.html for # details. def find_by_user_id(user_id, args = {}) args['user_id'] = user_id response = Net::Flickr.instance().request('flickr.people.getInfo', args) Person.new(response.at('person')) end end end; end [/sourcecode]

json_pure 1.1.3 patch

Following up on a previous blog post about a JSON transformation issue, there is now a patched version of the json_pure 1.1.3 gem on GitHub. The issue had to do with transforming Hashes and Arrays to JSON when they contain objects (classes) that have their own to_json methods. A typical error message is:
lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:251:in 
`to_json': wrong # of arguments(2 for 0) (ArgumentError)
See the README file and the commit history for generator.rb for more info. No warranties or guarantees that this will work for other users and uses of the json_pure gem! See the GPL license. Feedback is welcome!

Ruby and JRuby JSON Time with json, json_pure, and Merb + DataMapper extlib

After trying to run a super-simple Ruby script in JRuby that ran fine through the MRI, I found myself trying to debug a JSON generator exception. The Ruby script required a RubyGem that had its own dependencies on the json gem and Merb + DataMapper extlib gem. I thought that I could simply rebuild the gem to use json_pure and run things on JRuby. Little did I know... First, here are the versions of everything involved:
  • Ruby 1.8.6
  • JRuby 1.1.4
  • rubygems 1.2.0
  • json 1.1.3
  • json_pure 1.1.3
  • extlib 0.9.6
Let's take a simple example of converting Time to JSON. My environment uses UTC as the timezone. Bear with me. The code samples are repetitive, but I had to go through them to figure things out. [sourcecode="ruby"] require 'rubygems' require 'json' p [ JSON.parser, JSON.generator ] p Time.now.to_json [/sourcecode] We'll run it using the MRI and JRuby. MRI [sourcecode="ruby"] [JSON::Ext::Parser, JSON::Ext::Generator] "\"Fri Sep 12 20:20:06 +0000 2008\"" [/sourcecode] JRuby [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "\"Fri Sep 12 20:20:55 +0000 2008\"" [/sourcecode] Seems like a reasonable conversion and in the same format. JRuby is using json_pure as expected. Good, it's consistent. Note that the output is not in ISO format. Now, let's specify the the json_pure gem. [sourcecode="ruby"] require 'rubygems' require 'json/pure' p [ JSON.parser, JSON.generator ] p Time.now.to_json [/sourcecode] Run it through the MRI and JRuby. MRI [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "\"Fri Sep 12 20:29:13 +0000 2008\"" [/sourcecode] JRuby [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "\"Fri Sep 12 20:29:45 +0000 2008\"" [/sourcecode] Again, everything is consistent. Good news again. Since things are going well, let's try using extlib in the script so that the Time is in ISO format. [sourcecode="ruby"] require 'rubygems' require 'extlib' require 'json' p [ JSON.parser, JSON.generator ] p Time.now.to_json [/sourcecode] Run it through the MRI and JRuby. MRI [sourcecode="ruby"] [JSON::Ext::Parser, JSON::Ext::Generator] "\"2008-09-12T20:44:06+00:00\"" [/sourcecode] JRuby [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "\"2008-09-12T20:44:31+00:00\"" [/sourcecode] Excellent! Everything is working wonderfully. No problems at all! At this point, let me do what I wanted to do in the first place and modify the script to be a basic variation of the problematic Ruby script that gave me a headache. I'm going to place the time in a Hash, but I won't include extlib yet. [sourcecode="ruby"] require 'rubygems' require 'json' p [ JSON.parser, JSON.generator ] p Time.now.to_json h = {"created_on" => Time.now} p h.to_json [/sourcecode] Run it through the MRI and JRuby. MRI [sourcecode="ruby"] [JSON::Ext::Parser, JSON::Ext::Generator] "\"Fri Sep 12 20:48:50 +0000 2008\"" "{\"created_on\":\"Fri Sep 12 20:48:50 +0000 2008\"}" [/sourcecode] JRuby [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "\"Fri Sep 12 20:49:26 +0000 2008\"" "{\"created_on\":\"Fri Sep 12 20:49:26 +0000 2008\"}" [/sourcecode] Fantastic! It's a useless script, but it's going places: The Time is in a Hash that is being converted to JSON. Ok, let's add extlib so that we can get the JSON Time ISO format...and because our problematic Ruby script uses a particular gem (a very useful gem for the Ruby script) that happens to depend on extlib. [sourcecode="ruby"] require 'rubygems' require 'extlib' require 'json' p [ JSON.parser, JSON.generator ] p Time.now.to_json h = {"created_on" => Time.now} p h.to_json [/sourcecode] Run it through the MRI and JRuby. MRI [sourcecode="ruby"] [JSON::Ext::Parser, JSON::Ext::Generator] "\"2008-09-12T20:51:43+00:00\"" "{\"created_on\":\"2008-09-12T20:51:43+00:00\"}" [/sourcecode] JRuby [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "\"2008-09-12T20:52:02+00:00\"" /home/share/storage/jruby-1.1.4/lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:251:in `to_json': wrong # of arguments(2 for 0) (ArgumentError) from /home/share/storage/jruby-1.1.4/lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:251:in `json_transform' from /home/share/storage/jruby-1.1.4/lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:245:in `each' from /home/share/storage/jruby-1.1.4/lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:245:in `map' from /home/share/storage/jruby-1.1.4/lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:245:in `json_transform' from /home/share/storage/jruby-1.1.4/lib/ruby/gems/1.8/gems/json_pure-1.1.3/lib/json/pure/generator.rb:218:in `to_json' from time_extlib_to_json.rb:9 [/sourcecode] EH?! Now why in the world would it give me an error? Everything was fine up until this point. Why was it giving me a "wrong # of arguments(2 for 0) (ArgumentError)" exception? Well, I had a great time with JRuby debugging sessions looking at the json_transform method in json_pure's generator.rb. Just like the exception said, I came to see that the Time.to_json method no longer accepted two arguments...even though the json_transform method was trying to pass them in through the s << value.to_json(state, depth + 1) line. When the exception was thrown, the Time instance, aka value, had a to_json method that took 0 arguments. [sourcecode="ruby"] def json_transform(state, depth) delim = ',' delim json_transform method call in generator.rb is in the module Hash, and it's responsible for calling to_json with two arguments, state and depth + 1, to convert a Hash key's value. [sourcecode="ruby"] module JSON module Pure module Generator module GeneratorMethods module Hash def json_transform(state, depth) [/sourcecode] Nice. So now what? Well, I tried requiring 'json/add/core' and running it under JRuby... [sourcecode="ruby"] require 'rubygems' require 'extlib' require 'json' require 'json/add/core' p [ JSON.parser, JSON.generator ] p Time.now.to_json h = {"created_on" => Time.now} p h.to_json [/sourcecode] It works because 'json/add/core' gives Time.to_json a variable number of arguments again. But it gave this JSON output: JRuby [sourcecode="ruby"] [JSON::Pure::Parser, JSON::Pure::Generator] "{\"json_class\":\"Time\",\"s\":1221254881,\"n\":82427000}" "{\"created_on\":{\"json_class\":\"Time\",\"s\":1221254881,\"n\":83467000}}" [/sourcecode] I'm not into that Time format at all! If I require 'extlib' after 'json' and 'json/add/core', I get the same error of course. Maybe I'll just drop using the gem that requires extlib and override Time.to_json myself...That doesn't seem right since that gem has other functionality that I need. Right now, I feel like this is both an extlib and json_pure issue. extlib's Time.to_json doesn't accept a variable number of arguments, and the 'json/add/core' time format isn't what I was looking for. Hmmm...

Update

Actually, I take back what I said about it being either an extlib or json_pure issue. This might just be a case of code coming together and clashing. :-(

Update 2

I guess eating helps clear the head! It looks like it's a json_pure issue since it happens on both JRuby and the MRI when the script requires 'json/pure'. Here are the parts of the code in the C extension where it differs from Ruby:
static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
{
    VALUE Vstate, Vdepth, result;
    long depth;

    rb_scan_args(argc, argv, "02", &Vstate, &Vdepth);
    depth = NIL_P(Vdepth) ? 0 : FIX2LONG(Vdepth);
    if (NIL_P(Vstate)) {
        long len = RHASH(self)->tbl->num_entries;
        result = rb_str_buf_new(len);
        rb_str_buf_cat2(result, "{");
        rb_hash_foreach(self, hash_to_json_i, result);
        rb_str_buf_cat2(result, "}");
    } else {
        GET_STATE(Vstate);
        check_max_nesting(state, depth);
        if (state->check_circular) {
            VALUE self_id = rb_obj_id(self);
            if (RTEST(rb_hash_aref(state->seen, self_id))) {
                rb_raise(eCircularDatastructure,
                        "circular data structures not supported!");
            }
            rb_hash_aset(state->seen, self_id, Qtrue);
            result = mHash_json_transfrom(self, Vstate, LONG2FIX(depth));
            rb_hash_delete(state->seen, self_id);
        } else {
            result = mHash_json_transfrom(self, Vstate, LONG2FIX(depth));
        }
    }
    OBJ_INFECT(result, self);
    return result;
}

static int hash_to_json_i(VALUE key, VALUE value, VALUE buf)
{
    VALUE tmp;

    if (key == Qundef) return ST_CONTINUE;
    if (RSTRING_LEN(buf) > 1) rb_str_buf_cat2(buf, ",");
    tmp = rb_funcall(rb_funcall(key, i_to_s, 0), i_to_json, 0);
    Check_Type(tmp, T_STRING);
    rb_str_buf_append(buf, tmp);
    OBJ_INFECT(buf, tmp);
    rb_str_buf_cat2(buf, ":");
    tmp = rb_funcall(value, i_to_json, 0);
    Check_Type(tmp, T_STRING);
    rb_str_buf_append(buf, tmp);
    OBJ_INFECT(buf, tmp);

    return ST_CONTINUE;
}

Update 3

Follow-up post: json_pure 1.1.3 patch Feedback is welcome!