Ruby on Rails



Upload files in a BLOB field of an Oracle database



Written by Anthony Heukmes on Tue Mar 31 10:49:16 UTC 2009

0 comment



Customers used to the Java & Oracle worlds are often scared by the Ruby on Rails switch. They want to be sure that this new tool will do at least as much as the old one. This is a perfectly understandable reaction.
To do so, they regularly ask for a POC (Proof of Concept) development to confirm that a given functionality can be easily implemented using Ruby on Rails.

During one on my last meetings, the following reflexion came to my ears : "Are you sure that it will be possible to upload files in our Oracle database? I tried myself another scripting language (PHP) and I encountered many issues with BLOB!".

So here is my POC responding to this question.
I specified Oracle in the title of this article but the following Ruby code has nothing specific to this database.

First of all, we need a table and a model to store data related to our file. You'll aslo need a controller and views.
You can use the scaffold script provided by Rails to generate this structure :


$ ./script/generate scaffold FileUpload name:string content_type:string content_size:integer content:binary


Here is my migration file :

class CreateFileUploads < ActiveRecord::Migration

def self.up
create_table :file_uploads do |t|
t.string :name
t.string :content_type
t.integer :content_size
t.binary :content
t.timestamps
end
end

def self.down
drop_table :file_uploads
end
end


Note the type of the content column : binary. This will generate a BLOB field in your database during the migration :


$ rake db:migrate


Nothing special about the model :

class FileUpload < ActiveRecord::Base

end


You can then modify the creation form (new.html.erb) :



<% form_for @file_upload, :html => { :multipart => true } do |f| %>

<%= f.error_messages %>

<p>

<%= f.label :content %><br />

<%= f.file_field :content %>
</p>

<p>

<%= f.submit 'Create' %>

</p>

<% end %>


The form has been defined has a multipart and the content field is now a file_field.
You can then work on the create method of your controller :

def create

@file_upload = FileUpload.new(params[:file_upload])
if @file_upload.valid?
@file_upload.name = params[:file_upload][:content].original_filename
@file_upload.content = params[:file_upload][:content].read
@file_upload.content_type = params[:file_upload][:content].content_type
@file_upload.content_size = params[:file_upload][:content].size
@file_upload.save
flash[:notice] = 'FileUpload was successfully created.'
redirect_to @file_upload
else
render :action => "new"
end
end


Nothing complicated here, the params[:file_upload][:content] holds an instance of ActionController::UploadedStringIO on which you can perform various operations as reading data, get content type and size, ... All this information is then stored in the database.

In the show.html.erb page, replace the content by a download link :



<p>

<b>Content:</b>

<%= link_to "Download", :action => "download", :id => @file_upload.id
%>
</p>



Finally, insert the download method in your controller and you're done!

def download

file = FileUpload.find(params[:id])
if file
send_data(file.content, :type => file.content_type , :filename => file.name)
else
flash[:error] = "Invalid file ID!"
redirect_to :action => "index"
end
end


One more time, it's very simple! The FileUpload record is extracted from the database using his ID. The associated file is then returned thanks to send_data method.

You can now save any kind of file in your Oracle database!

Ruby on Rails - Why is it necessary to include all helpers in all views? (helper :all)



Written by Anthony Heukmes on Wed Mar 25 21:55:41 UTC 2009

0 comment



I have various methods providing default behaviors in the application_helper.rb file of my current project.
These methods are commons to all my controllers but some of them may decide to override this default behavior if necessary.
To do so, they can redefine the methods in their own helper.

Let's say that I have the say_hi method in application_helper.rb :
def say_hi

"Hi from ApplicationController"
end"


If I call it in the views related to my controllers 'Controller1' and 'Controller2', the text "Hi from ApplicationController" will be correctly displayed.
Now, I want to change this greeting message in the pages of Controller1. I can redefine the method say_hi in controller1_helper.rb :
def say_hi

"Hi from Controller1"
end"


Everything works fine.
But now, if I apply the same solution in controller2_helper.rb :
def say_hi

"Hi from Controller2"
end"


The message displayed in the pages of Controller2 will be correct. But on the other hand, pages of Controller1 will display "Hi from Controller2" instead of "Hi from Controller1".
This is a strange behavior, I was not excepting a such result.

This issue is caused by this sentence in your ApplicationController :
helper :all


The goal of this method is to include all custom helpers in all views.
All methods of all helpers will then be included in the current view.
Controller2 is the last one in the list (alphabetical order) so it is the last included and his 'say_hi' method will be used.

You can open the file actionpack/lib/action_controller/helpers.rb to see how it works :
def helper(*args, &block)

args.flatten.each do |arg|
case arg
when Module
add_template_helper(arg)
when :all
helper(all_application_helpers)
when String, Symbol
file_name = arg.to_s.underscore + '_helper'
class_name = file_name.camelize

begin
require_dependency(file_name)
rescue LoadError => load_error
requiree = / -- (.*?)(.rb)?$/.match(load_error.message).to_a[1]
if requiree == file_name
msg = "Missing helper file helpers/#{file_name}.rb"
raise LoadError.new(msg).copy_blame!(load_error)
else
raise
end
end
add_template_helper(class_name.constantize)
else
raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})"
end
end


def all_application_helpers

extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
end


When the helper method is called with the :all parameter, it will re-invoke itself with the result of all_application_helpers as a parameter.
This method will return an array containing the names of all helpers defined in the app/helpers folder (in my case, 'application', 'controller1' and 'controller2').
The new call to the helper method will then browse each name and include the corresponding helper.

So I removed the helper :all line in my ApplicationController and everything worked fine.

But I'm wondering on the interest of this method.
Why would the methods associated to Controller2 be available in Controller1?
I have no problem against the existence of this method but it's for me a mistake to include it by default in a fresh new Rails application.

[The inside of Rails] Controllers and instance variables



Written by Anthony Heukmes on Tue Mar 03 22:44:24 UTC 2009

0 comment



For one of my latest projects, I had to implement a "generic" controller allowing management of various types of ActiveRecord objects.
An administrator should for example be able to validate comments & links suggestions before they appear on the website.
This validation should be done through a common interface (a common controller /activations) to avoid code duplication.

Thanks to Ruby dynamic nature it was of course very easy to implement.

But the following tips helped me to achieve my goals :

The following code allows you to work with a class when his name is only known at runtime :

klass_name = "Article"

klass = Kernel.const_get(klass_name)
klass.find(:all, :order => 'created_at DESC')



It's sometimes also very useful to create/get instance variables dynamically :

var_name = "article"

instance_variable_set("@#{var_name}", klass.find(params[:id]))
instance_variable_get("@#{var_name}")



Talking about instance variables... Do you know how it is possible to access instance variables you defined in your controllers into your views?
In most frameworks, you have to specify explicitly which data you want to make available for your views.
For example, using Java and Spring, you have to create a Map object with all your data and then use this map as a parameter for the render method.
With Ruby on Rails it's done automatically and it's a good news! But how does it work?

It's very simple.
The following method is defined in the ActionView::Base class (actionpack/lib/action_view/base.rb) :

def copy_ivars_from_controller

if @controller
variables = @controller.instance_variable_names
variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) }
end
end



... and this method will be called from the render method of your view.
What it does is simply get all the instance variables of the current controller (subtracting protected vars) and then recreate each of them in the current view using instance_variable_set.

ActiveRecord : Building a Rails application based a legacy database



Written by Anthony Heukmes on Fri Feb 20 15:45:30 UTC 2009

3 comments



In a perfect world, a new data schema is created with a new Rails application. The database evolves with the application and will respect all the ActiveRecord conventions.
It is the ideal case because no configuration is required and you can fully benefit of migrations.
The development speed reaches his maximum.

But legacy databases are everywhere in professional environments.
A big Oracle schema which doesn't respect any ActiveRecord convention and that you cannot modify because other applications are already based on it.
The only thing you can do is create views in your schema and it is often very useful. But it's not enough.

Does that mean you have to forget about Rails and come back to Java development? Fortunately, the answer is no! :-)

As you already know, a schema is "compatible" with ActiveRecord if he follows a few conventions. So the name of the table must be the pluralized name of the model (User -> Users)
and your primary key must be an auto-incremented value called ID.
The MUST verb is however not right here. If you follow the conventions, you will not have to configure your model, the mapping between your Ruby object and your table will be done automatically. But if the conventions are not respected, you'll always have the possibility to configure the mapping yourself.

Let's take the following schema :



It clearly doesn't follow the ActiveRecord conventions. The name of the "my_users" table should be "users", and the primary key should be "id" instead of "my_pk."

I'm working with an Oracle database that doesn't support auto incrementation like MySQL does. To achieve the same goal, I had to create a sequence named "my_users_autoi".

My User.rb class looks like this :


class User < ActiveRecord::Base

set_table_name 'my_users'
set_primary_key 'my_pk'
set_sequence_name 'my_users_autoi'

end


As you can see, there is no magic here. You can now manipulate your model, all CRUD operations are available. And if you try to create a new user, you'll notify that the db sequence is used to assign the primary key.

A user can write articles. So there is a one-to-many association between the two entities. The article class respects the two previous conventions but the name of the foreign key is not correct. It should be user_id. We have to configure our model and add the relationship in the User class :


class Article < ActiveRecord::Base

belongs_to :user, :foreign_key => 'by_user'

end

class User < ActiveRecord::Base
...
has_many :articles, :foreign_key => 'by_user'
end


You can then start manipulating your data :

((
usr = User.create(:pseudo => 'antho', :email => 'ahe@me.com')
Article.create(:title => 'My first article', :user => usr, :creation_date => Date.today)
usr.articles.first.title # => 'My first article'
))

And voilaaa!

The Ranking table is however a little more problematic. Users can rate articles, giving them a note between 1 & 5.
The primary key of this table is composite : it is based on BY_USER and FOR_ARTICLE.
ActiveRecord doesn't support composite primary keys but fortunately there is a library supporting this feature.
Install it following the instructions on the website of his author.

When it's done, create the Ranking class and update your previous classes to reflect this new association :


class Ranking < ActiveRecord::Base

set_table_name 'ranking'
set_primary_keys :by_user, :for_article

belongs_to :user
belongs_to :article

end

class User < ActiveRecord::Base

set_table_name 'my_users'
set_primary_key 'my_pk'
set_sequence_name 'my_users_autoi'

has_many :articles, :foreign_key => 'by_user'
has_many :rankings, :foreign_key => 'by_user'

end

class Article < ActiveRecord::Base

set_sequence_name 'articles_autoi'

belongs_to :user, :foreign_key => 'by_user'
has_many :rankings, :foreign_key => 'for_article'

end


Users can now rate your articles :


rk = Ranking.new
rk.rank = 5
rk.by_user = usr.my_pk
rk.for_article = usr.articles.first.id
rk.save


Don't miss the fact that you have to specify explicitly the primary key (user.my_pk & article.id).

And that's it! I think you can now affirm that a Rails application can be easily created on a legacy database!

Ruby on Rails plugin : manage the titles of your pages very easily!



Written by Anthony Heukmes on Tue Feb 17 20:35:01 UTC 2009

0 comment



EasyTitles is a Ruby on Rails plugin that allows you to easily manage the titles of your pages.
Instead of creating @title variables inside your controllers & views, EasyTitles gives you the possiblity to centralize your titles in YAML files.
This plugin also supports internationalization (I18n) if you use a version of Rails >= 2.2.

The big advantage is that all titles are centralized in one place, making them easy to manage.

Let's say that you have a REST "Articles" controller (with basic CRUD methods), the only one thing you have to do is to create a YAML file respecting the following format :


articles:
index: "Listing articles"
show: "Article details"
new: "New article'"
edit: "Edit article"


You can then add a call to the easy_title helper method in the title tag of your layout :


<title><%= easy_title %></title>


This method will determine the title to display in function of the current controller name & action name.
So if you are on the /articles/show page, the title "Article details" will be displayed.

EasyTitles also allows you to set default titles :


default: "This title will be displayed for any controller/action without a defined title"
articles:
default: "This title will be displayed for articles methods without a defined title"
index: 'Listing articles'
show: 'Article details'
new: 'New article'
edit: 'Edit article'


Installation





  1. script/plugin install git://github.com/ahe/easytitles.git

  2. Create a titles folder in your config folder (RAILS_ROOT/config/titles).

  3. If you are not using I18n (internationalization) create a all.yml file in your titles folder.

  4. Otherwise, create a yml file for each locale (en.yml, fr.yml, …) in your titles folder.

  5. Put all your titles in the yml file(s) while respecting the above format.

  6. Add the easy_title call in your title tag : <title><%= easy_title %></title>