Wednesday, 30 November 2011

Rails3.1- Asset isn't precompiled error

Recently on a production env, I was annoyed with "<Asset> isn't precompiled" error, though it was compiled using rake assets:precompile. I wasn't able to figure out the root cause. However I was able to work around.

I'd assets included on the view, using <content_for>. I removed and required it within the controller specific asset file and things started working. So as far as I can say NEVER require assets within the view, using <content_for> or <provide>. Asset pipeline, chokes for some reason.

Tuesday, 29 November 2011

HTML5 File Api- Preview during image upload

File Api is one of the coolest addition with HTML5, though not all major browsers has started supported this(as of this writing). It allows the client to handle basic file validations and image preview before sending it to the server. Here's how I did:

HTML:

<input type="file" name="photo" accept="image/*"  />
<div class="preview"></div>

Jquery:


var MAX_FILE_SIZE = 5242880; // 5MB
var ACCEPT_FILE_TYPE = /image\/(jpg|jpeg|png)/i;

function handleFileSelect(evt) {
  var files = evt.target.files; // FileList object
  var form = $(evt.target).closest('form');

  // Loop through the FileList and render image files as thumbnails.
  for (var i = 0, f; f = files[i]; i++) {
    // Only process image files.
    if (!f.type.match(ACCEPT_FILE_TYPE)) {
      showFormError('File is not an image', form);
      continue;
    }

    if (parseInt(f.size) > MAX_FILE_SIZE) {
      showFormError('File size exceeds 5MB.', form);
      continue;
    }

    var reader = new FileReader();
    // Closure to capture the file information.
    reader.onload = (function(theFile) {
      return function(e) {
        // Render thumbnail.
        var img = ['<img src="', e.target.result,
                          '" title="', theFile.name, '"/>'].join('');
        form.find('.preview').prepend(img);
        form.find('.preview').show();
      };
    })(f);

    // Read in the image file as a data URL.
    reader.readAsDataURL(f);
  }
}

function showFormError(msg, form) {
  var errorDiv = document.createElement('div');
  errorDiv.className = 'errors';
  errorDiv.innerHTML = msg;
  form.find('div.errors').remove();
  form.prepend(errorDiv);
}

$('input[type=file]').change(handleFileSelect);

That's it... 

Thursday, 24 November 2011

Paperclip: Image or Photos from url

In general, Paperclip accepts file uploaded from local machine. However we can extend it to support image from url (Ex., Saving user avatars from twitter or facebook)

app/models/user.rb

  has_attached_file :avatar, ....
  attr_accessor :avatar_url
  def avatar_url=(img_url)
    io = open(URI.parse(img_url))
    # define original_filename meth on io, dynamically
    def io.original_filename; base_uri.path.split('/').last; end
    self.avatar = (io.original_filename.blank? ? nil : io)
  rescue Exception => ex
    puts ex.message
    Rails.logger.info "Error while parsing avatar: #{ex.message}"
  ensure
    io.close
    @avatar_url = img_url
  end

Glitch : OpenURI.open, returns an StringIO object, which might not be as efficient as a Tempfile. So considering adding this patch.


config/initializers/open_uri_patch.rb

require 'open-uri'

# make OpenURI to use tempfiles instead of io.
# Patched from http://snippets.dzone.com/posts/show/3994
OpenURI::Buffer.module_eval do
  remove_const :StringMax
  const_set :StringMax, 0
end

We are ready now..

Ex Usage :  u = User.new(:avatar_url => 'http://www.facebook.com/profile/pic')

Good luck :)


Wednesday, 23 November 2011

Rails: ffmpeg video generation from images

To generate video from a series of images/photos in rails, I use this code snippet:

    # generate the video and store it under the appropriate public path.
    def create_video_from(foto_paths = [], tyype)
      create_tmp_links(foto_paths)
      status = Paperclip.run('ffmpeg', ffmpeg_options)
      remove_tmp_links(foto_paths.length)
      FileUtils.mv('/tmp/output.mp4', Rails.root.to_s + "/public/#{tyype.to_s}_video.mp4")
    end

    def ffmpeg_options
      "-r 1 -f image2 -i '/tmp/img%03d.jpg' /tmp/output.mp4"
    end

    # ffmpeg requires filenames matching a pattern, so create them under /tmp
    def create_tmp_links(foto_paths = [])
      foto_paths.each_with_index do |path, i|
        tmp_path = "/tmp/img%03d.jpg" % (i + 1)
        File.symlink(path, tmp_path)
      end
      true
    end

    # remove those symlinks
    def remove_tmp_links(fotos_cnt = 0)
      fotos_cnt.times do |i|
        tmp_path = "/tmp/img%03d.jpg" % (i + 1)
        File.unlink(tmp_path)
      end
      true
    end

Hope it helps!

Tuesday, 22 November 2011

Rails: Aspect fit images

Aspect fitting images with a frame has always been a common requirement across websites that supports image browsing. Here's how I would like to do it in Rails web applications:

app/models/photo.rb

  def aspect_fit(frame_width, frame_height)
    image_width, image_height = self.data_dimension.split('x')
    ratio_frame = frame_width / frame_height
    ratio_image = image_width.to_f / image_height.to_f
    if ratio_image > ratio_frame
      image_width  = frame_width
      image_height = frame_width / ratio_image
    elsif image_height.to_i > frame_height
      image_width = frame_height * ratio_image
      image_height = frame_height
    end
    "width:#{image_width.to_i}px; height:#{image_height.to_i}px;"
  end
Note: self.data_dimension, will return a string of "widthxheight" for a photo. I store it when uploading images. You might also use, Paperclip::Geometry.from_file to get the dimension during run-time.

In Views:

<div class='photo'>
  <img src='<%= @photo.url %>' style='<%= @photo.aspect_fit(500, 500) %>'/>
</div>

Styles:

#photo { position: relative; height: 500px; width: 500px; background-color: black; }
#photo img { position: absolute; bottom: 0; left: 0; right: 0; top: 0; margin: auto; }

That should be it.

Rails: Simple File upload

Inspired from Paperclip, I always wanted to have a simple file upload solution in Rails, which doesn't require any thumbnail generation. Here it's:

app/models/post.rb

  attr_accessor :data
  FOTO_DIR = File.join(Rails.root, 'public/photos')
  FOTO_PATH = File.join(FOTO_DIR, ':id/:filename')
  ALLOWED_TYPES = ['image/jpg', 'image/jpeg', 'image/png']

  validates :data_filename, :presence => true
  validates :data_size, :inclusion => { :in => 0..(5.megabytes) }, :allow_blank => true
  validates :data_content_type, :inclusion => { :in => ALLOWED_TYPES }, :allow_blank => true

  after_save :save_data_to_file

  def data=(file)
    return nil if file.blank?
    @data = file.path # temp file path
    self.data_filename = file.original_filename.to_s
    self.data_content_type = file.content_type.to_s
    self.data_size = file.size.to_i
    self.data_dimension = get_geometry(file)
  end

  def path
    fpath = FOTO_PATH.dup
    fpath.sub!(/:id/, self.id.to_s)
    fpath.sub!(/:filename/, self.data_filename)
    fpath
  end

  def url
    path.sub("#{Rails.root}/public", "")
  end

private

  def save_data_to_file
    return true if self.data.nil?
    ensure_dir(FOTO_DIR)
    ensure_dir(File.join(FOTO_DIR, self.id.to_s))
    Rails.logger.info "Saving file: #{self.path}"
    FileUtils.cp(self.data, self.path)
    true
  end

  def ensure_dir(dirname = nil)
    raise "directory path cannot be empty" if dirname.nil?
    unless File.exist?(dirname)
      FileUtils.mkdir(dirname)
    end
  end

  def get_geometry(file)
    `identify -format %wx%h #{file.path}`.strip
  end
Issuing Photo.create(:data => params[:photo]) should create the file in filesystem and save the record in DB, where params[:photo] is the file field value.

Its quite easy to make slight changes to this code piece to make it fit for your requirement , rather than writing patches/hacks to any gem.

Monday, 21 November 2011

Rails: Basic vim config

Vim supports a whole lot of features, however I've listed the basic config to get started with development in rails.

~/.vimrc
set nocompatible          " We're running Vim, not Vi!
syntax on                 " Enable syntax highlighting
filetype plugin indent on " Enable filetype-specific indenting and plugins

" Load matchit (% to bounce from do to end, etc.)
runtime! macros/matchit.vim

augroup myfiletypes
  " Clear old autocmds in group
  autocmd!
  " autoindent with two spaces, always expand tabs
  autocmd FileType ruby,eruby,yaml set ai sw=2 sts=2 et
augroup END

nmap <leader>rci :%!ruby-code-indenter<cr>
Apart from this, you can also tweek any config under /etc/vim/vimrc , based on your preference.

Friday, 18 November 2011

Rails3.1 - Manage CSS/JS with Asset Pipeline

An Ideal way to mange your css/js using sprockets in rails (>~ 3) apps. Asset pipeline is enabled by default from rails 3.1 and it uses 'app/assets' to maintain the asset files. We can also have 'lib/assets' and 'vendor/assets' to host the assets.

These assets are included in the layout using

<%= stylesheet_link_tag    "application" %>
<%= javascript_include_tag "application" %>
and the app/assets/stylesheets/application.css will

  *= require_self
  *= require_tree .
Ideally, we wouldn't want to require the whole tree for stylesheets or javascripts. We only need application and the controller specific assets to be loaded on any given page and so I recommended having the following setup.

Typical application.css
*= require reset-min
*= require_self

In layout:

  <%= stylesheet_link_tag    "application", params[:controller] %>
  <%= javascript_include_tag "application", params[:controller] %>
  <%= yield(:head) %>
Hereby, we load only the common styles/scripts applicable for the app, from application and controller specific styles. yield(:head) is to require custom styles/scripts for a page.

Also, assets from vendor/assets or lib/assets can be required within application or controller specific files.

Note: with this approach, the custom files has to be added manually to the precompile list, for them to be precompiled. config.assets.precompile += %w( *.js *.css ) - should do the trick.

Tuesday, 15 November 2011

Setup rhodes environment on Ubuntu

Recently I was setting up rhodes on my ubuntu machine to mobile app development, where I'd to go through multiple references. So I decided to make a write up to serve as one reference for all.

Assumption - You have ruby and rubygems installed.

sudo apt-get install sun-java6-jdk
gem install rhodes
set-rhodes-sdk

Download android sdk from http://developer.android.com/sdk/index.html
untar the downloaded package ( android-sdk-linux)
mv android-sdk-linux /usr/local
cd /usr/local/android-sdk-linux/tools
./android

Download android ndk from http://developer.android.com/sdk/ndk/index.html
untar the downloaded package ( Ex., android-ndk-r7 )
mv android-ndk-r7 /usr/local

Run 'rhodes-setup' and specify JDK, android sdk and ndk paths.
For our case it will look like
JDK path - /usr/lib/jvm/java-6-sun
Android SDK path - /usr/local/android-sdk-linux
NDK path - /usr/local/android-ndk-r7

Generate the application:
rhodes app test_app http://localhost:3000/application
OR create an application in the cloud using rhohub.com (preferred option)

Now 'rake run:android' and have fun :)

Saturday, 12 November 2011

Grub 2 - Limit kernel entries

With Grub2 menu.lst has been removed and there's no way to change "#howmany" to limit the number of kernel entries to be displayed on the boot menu. So here's how we do it:

Find this section in /etc/grub.d/10_linux . Note this is not the entire file!. Added sections are in bold dark red:

prepare_boot_cache=

# Added to limit number of Linux kernels displayed.
COUNTER=0
LINUX_KERNELS_DISPLAYED=2
#


while [ "x$list" != "x" ] ; do
linux=`version_find_latest $list`
echo "Found linux image: $linux" >&2
.....
..... < omitted lines >
..... < several lines from the bottom of the file >

list=`echo $list | tr ' ' '\n' | grep -vx $linux | tr '\n' ' '`


# Added to limit number of Linux kernels displayed.
COUNTER=`expr $COUNTER + 1`
if [ $COUNTER -eq $LINUX_KERNELS_DISPLAYED ]; then
list=""
fi
#

done
 Save the file and run 'update-grub'.

Friday, 11 November 2011

New Rails 3.1 app

Generate new rails 3.1 app(with mysql db) with the following command:

rails new testapp -d mysql --skip-bundle

--skip-bundle will not run 'bundle install' during app creation.

Thursday, 10 November 2011

Truncate file linux

Simple bash script to truncate a huge log, may be log files

cat /dev/null > /var/log/production.log

Mysql - Enable slow query logging

Enabling slow query logs is one of the recommended options to determine long running queries, lock timeout issues and non-indexed queries.

Uncomment the below lines in ~/.my.cnf or /etc/my.cnf under #Logging

#log-output                     = FILE
#slow-query-log                 = 1
#slow-query-log-file            = /var/lib/mysqllogs/slow-log
#long-query-time                = 2
#log-queries-not-using-indexes  = 1

and restart the server.

Use mysqldumpslow to quickly parse through huge log files, as below.

mysqldumpslow /var/lib/mysqllogs/slow-log


Script to convert all tables to InnoDB

Here is a quick shell script to convert all tables in a database to InnoDB. No dependancies other than a command line prompt on a Unix like system and the standard MySQL tools:

for T in `mysql -u root -B -N -e “show tables” test`; do mysql -u root -e “alter table $T type=innodb” test; done

Replace “test” with the target database. This pattern is also great for optimizing or analyzing your MyISAM tables.

Wednesday, 9 November 2011

Paperclip fog storage : content_type fix

Add this initializer to set the correct content_type when you use fog storage with paperclip - version <= '2.3.12'

# patch from https://github.com/lisausa/paperclip/commit/e03007da2cedd7c32dbd5d1601d215897e00bc51
module Paperclip
  module Storage
    module Fog

      def flush_writes
        for style, file in @queued_for_write do
          log("saving #{path(style)}")
          directory.files.create(
            :body         => file,
            :key          => path(style),
            :public       => @fog_public,
            :content_type => file.content_type.to_s.strip
          )
        end
        @queued_for_write = {}
      end

    end
  end
end
 Done.

Tuesday, 8 November 2011

mysql gem dependencies in Ubuntu

Very Often when we setup a Rails application on a fresh Ubuntu machine, we end up facing error when installing mysql gem, when executing bundle install.


Make sure you have mysql gem dependencies installed, (and offcourse the actual mysql server as well)

sudo apt-get install libmysql-ruby libmysqlclient-dev

Done.