I have been thinking about my recent posts regarding releases of Rails. There are plenty other people blogging about releases and the RubyOnRails blog site is the place to go for this information.
This isn't the purpose of this site. I blog to share what I've learned to hopefully be of use to someone else. So, that's all I'm posting about from now on. No fluff, just useful information.
Silly me.
Thursday, December 20, 2007
Tuesday, December 18, 2007
rescue_from
It is very common for me to have some rules regarding who can access data on the sites I build. For this example we have clients that have products. Users belong_to a client and therefore only have access to that client's product. A user should not be able to access a product that belongs to another client.
In the products controller we need to make sure that the products accessed belong to the client associated to the current user.
Here's a DRY approach to reach that goal:
Before the show, edit, update and destroy methods we are going to call the find_product method. This method will ensure that the product id being passed in belongs to the user who is logged in (by their client id).
If the product is not found, the find_by_client will set @product to nil.
If product.nil? we will raise a SecurityError. This is where the rescue_from class method comes in to play. It will catch that exception and redirect to the access_violation_url.
The access_violation_url is just a named route. In my case it is defined as:
I created a security controller to process and display the errors to the user. Now I don't have to create an error method/view for each controller class.
By the way, the rescue_from is a Rails 2.0 feature. So, you'll need to upgrade to use this very nice feature.
In the products controller we need to make sure that the products accessed belong to the client associated to the current user.
Here's a DRY approach to reach that goal:
class ProductsController < ActiveRecord::Base
before_filter :find_product, :only => [:show, :edit, :update, :destroy]
rescue_from SecurityError,
:with => Proc.new{|e| redirect_to(access_violation_url)}
...
...
private
def find_product
@product = Product.find_by_client(params[:id], current_client_id)
raise SecurityError.new if @product.nil?
end
Before the show, edit, update and destroy methods we are going to call the find_product method. This method will ensure that the product id being passed in belongs to the user who is logged in (by their client id).
If the product is not found, the find_by_client will set @product to nil.
If product.nil? we will raise a SecurityError. This is where the rescue_from class method comes in to play. It will catch that exception and redirect to the access_violation_url.
The access_violation_url is just a named route. In my case it is defined as:
map.access_violation '/security/access_violation',
:controller => "security",
:action => "access_violation"
I created a security controller to process and display the errors to the user. Now I don't have to create an error method/view for each controller class.
By the way, the rescue_from is a Rails 2.0 feature. So, you'll need to upgrade to use this very nice feature.
RESTful routes (namespaced)
I like having my administration (management) controllers namespaced. From a security perspective, it's easier to exclude a group of admin functions from certain users (or the general public) if you namespace the routes.
Put these namespaced controllers in RAILS_ROOT/app/controllers/admin.
This means for any namespace you are using in your controllers you should have a corresponding app/controllers subdirectory.
map.namespace(:admin) do |admin|
admin.resources :users
end
#The above will look for a controller class named:
class Admin::UsersController < ActionController::Base
...
end
Put these namespaced controllers in RAILS_ROOT/app/controllers/admin.
This means for any namespace you are using in your controllers you should have a corresponding app/controllers subdirectory.
Labels:
RubyOnRails
rake db:migrate:redo
Nice new rake command to rollback one migration and migrate back up. If you need to go further than one step back pass the STEP=x parameter.
#Roll back 3 migrations and migrate back to current version.
rake db:migrate:redo STEP=3
Labels:
Quick Tips,
RubyOnRails
Monday, December 17, 2007
Rails 2.0.2
The team didn't waste much time. A new release is out with some bug fixes and two major changes:
1) SQLite3 is the new default database. Use rails -d mysql newapp, to generate a mysql database.yml template.
2) Rails no longer checks for template changes in production mode.
Read more...
Apparently it hasn't propagated across all servers yet, so you may get some 404 errors trying a gem update. Just give it some time.
Speaking of gems...you should be using 0.9.5. :)
1) SQLite3 is the new default database. Use rails -d mysql newapp, to generate a mysql database.yml template.
2) Rails no longer checks for template changes in production mode.
Read more...
Apparently it hasn't propagated across all servers yet, so you may get some 404 errors trying a gem update. Just give it some time.
Speaking of gems...you should be using 0.9.5. :)
Labels:
RubyOnRails
Custom Rake tasks
Writing your own custom Rake task is very simple and almost not worthy of a blog post, but I can't remember everything, so it's here for reference.
In your lib/tasks directory create a file with the extension .rake. i.e. print.rake.
In your lib/tasks directory create a file with the extension .rake. i.e. print.rake.
namespace :print do
task :one do
attention "One"
end
task :two do
attention "Two"
end
task :three do
attention "Three"
end
task :first_user => :environment do
puts User.find(1).first_name
end
task :all => [:one, :two, :three]
def attention(msg)
puts "!!!!!!!!!! #{msg}"
end
end
#Sample Calls
rake print:three
!!!!!!!!!! "Three"
rake print:all
!!!!!!!!!! "One"
!!!!!!!!!! "Two"
!!!!!!!!!! "Three"
- Wrap tasks in the namespace block to namespace your tasks.
- then call by <namespace>:<task_name>
- Using => in your task declaration denotes a dependency.
- Use an array for multiple dependencies (i.e. the :all task)
- The :environment dependency will load the Rails environment
- :first_user task
- You don't need the block if the task is empty.
- the print:all task - no do..end defined
- You can define standard methods for code reuse.
- the attention method
Labels:
Quick Tips,
RubyOnRails
Thursday, December 13, 2007
backgrounDRb and RubyOnRails
Lately I've been running into the situation where I need to have a process running to periodically handle some task like sending emails or other bulk processing. Well, if you run into that situation I highly recommend using a very nice plugin by Ezra called backgrounDRb.
BackgrounDRb is ruby server that allows you schedule jobs to run at various intervals. Sounds like a good use for cron eh? Well, I could have used cron and wrote a script that loaded the rails environment in order to use my models but wait, why bother? BackrounDRb loads the rails environment and does the scheduling. I don't have to do anything but write my standard RubyonRails style lib. Very nice.
Here's a quick rundown on what you need to do to get this running.
That was super easy, and now you're mostly done.
You're backgroundrb.yml will look like:
A good start, but here's a sample one that has a little more info:
So, what is my_process_worker and do_process? Well that's your class and method you want to run. The start, end and repeat_interval fields are self explanatory.
The my_process_worker will correspond to a file in your lib/workers directory called my_process_worker.rb.
Okay, so you will have a lot more to do in the do_process method, but you get the point. By the way, do_process is just an example, it holds no special meaning to backgrounDRb.
So, the only thing that wasn't super obvious to me was, how do you start the process? Well, it's easy:
This loads your backgroundrb.yml file and, in the example, will call the MyProcessWorker do_process method.
The only thing left really is a little Capistrano example. Here are some tasks for you:
Just call the restart_backgroundrb task at the end of your deployment cycle, typically after a mongrel restart.
By the way, it's not just for use with RubyOnRails. You can use backgrounDRb for any of your Ruby programming needs.
Thanks to Greg for his review and help with this post.
BackgrounDRb is ruby server that allows you schedule jobs to run at various intervals. Sounds like a good use for cron eh? Well, I could have used cron and wrote a script that loaded the rails environment in order to use my models but wait, why bother? BackrounDRb loads the rails environment and does the scheduling. I don't have to do anything but write my standard RubyonRails style lib. Very nice.
Here's a quick rundown on what you need to do to get this running.
#install the plugin
script/plugin install http://svn.devjavu.com/backgroundrb/trunk
#add backgroundrb.yml to your config dir
#add backgroundrb to your scripts dir
#create lib/workers dir
#create test/bdrb_test_helper.rb
#all of the above done by:
rake backgroundrb:setup
That was super easy, and now you're mostly done.
You're backgroundrb.yml will look like:
---
:backgroundrb:
:port: 11006
:ip: localhost
A good start, but here's a sample one that has a little more info:
---
:backgroundrb:
:ip: 0.0.0.0
:port: 11006
:schedules:
:my_process_worker:
:do_process:
:trigger_args:
:start: <%= Time.now + 5.seconds %>
:end: <%= Time.now + 1.month %>
:repeat_interval: <%= 2.minutes %>
So, what is my_process_worker and do_process? Well that's your class and method you want to run. The start, end and repeat_interval fields are self explanatory.
The my_process_worker will correspond to a file in your lib/workers directory called my_process_worker.rb.
class MyProcessWorker < BackgrounDRb::MetaWorker
set_worker_name :my_process_worker
def create(args = nil)
# this method is called, when worker is loaded for the first time
end
#this is the method referred to in the backgroundrb.yml
def do_process
1.upto(10) do |i|
puts i
end
end
end
Okay, so you will have a lot more to do in the do_process method, but you get the point. By the way, do_process is just an example, it holds no special meaning to backgrounDRb.
So, the only thing that wasn't super obvious to me was, how do you start the process? Well, it's easy:
#to start
script/backgroundrb start
#to stop
script/backgroundrb stop
This loads your backgroundrb.yml file and, in the example, will call the MyProcessWorker do_process method.
The only thing left really is a little Capistrano example. Here are some tasks for you:
desc "Start the backgroundrb server"
task :start_backgroundrb, :roles => :app do
run "cd #{release_path} && ./script/backgroundrb start"
#you can pass in a rails_env like
#run "cd #{release_path} && RAILS_ENV=production nohup ./script/backgroundrb start"
end
desc "Stop the backgroundrb server"
task :stop_backgroundrb, :roles => :app do
run "cd #{release_path} && ./script/backgroundrb stop"
end
desc "Restart the backgroundrb server"
task :restart_backgroundrb, :roles => :app do
stop_backgroundrb
start_backgroundrb
end
Just call the restart_backgroundrb task at the end of your deployment cycle, typically after a mongrel restart.
By the way, it's not just for use with RubyOnRails. You can use backgrounDRb for any of your Ruby programming needs.
Thanks to Greg for his review and help with this post.
Labels:
RubyOnRails
Tuesday, December 11, 2007
exists? == not_nil?
Apparently Facets adds the exists? functionality in the form of not_nil?. It seems there's a lot of goodness in Facets that I will now have to investigate. Thanks to Trans for giving me the heads up.
Here's to not re-inventing the wheel. Cheers.
Here's to not re-inventing the wheel. Cheers.
Labels:
Ruby
exists?
I like readable code is it's one of the nice attributes of Ruby. Today I had some code that could use some improvement.
Ruby has a method called nil? that you can send to any object. When I want to test is that object is not nil? I could:
However, my particular method included as series of conditionals. Something like:
The should_i? method would return true if my_object existed and the my_var variable on that objected existed. However reading the code (in my mind), I see: my_object and not my_object.my_var is nil?. I get the point, but it could be a little nicer.
So I wrote the following:
Now that allows me to write this:
Now I read that as: my_object and my_object.my_var exists. To me it's much nicer.
I did post on the Ruby mailing list to see if a method like exists?, well, existed, but the responses were negative. Someone did post some example code which was just like the code I had written when the responses were slow to come. Good to see we were in agreement.
To make it even more readable (no && translation):
In the end it's just a preference. I prefer the exists? method.
Ruby has a method called nil? that you can send to any object. When I want to test is that object is not nil? I could:
#use unless
unless my_var.nil?
#use not
if not my_var.nil?
#use !
if !my_var.nil?
However, my particular method included as series of conditionals. Something like:
def should_i?
my_object && !my_object.my_var.nil?
end
The should_i? method would return true if my_object existed and the my_var variable on that objected existed. However reading the code (in my mind), I see: my_object and not my_object.my_var is nil?. I get the point, but it could be a little nicer.
So I wrote the following:
class NilClass
def exists?
false
end
end
class Object
def exists?
true
end
end
Now that allows me to write this:
def should_i?
my_object && my_object.my_var.exists?
end
Now I read that as: my_object and my_object.my_var exists. To me it's much nicer.
I did post on the Ruby mailing list to see if a method like exists?, well, existed, but the responses were negative. Someone did post some example code which was just like the code I had written when the responses were slow to come. Good to see we were in agreement.
To make it even more readable (no && translation):
def should_i?
my_object and my_object.my_var.exists?
end
#which means you could write the original code as:
def should_i?
my_object and not my_object.my_var.nil?
end
In the end it's just a preference. I prefer the exists? method.
Labels:
Ruby
Saturday, December 8, 2007
Mikey
So, I have a site that synchronizes data with another server. It does this via after_create, after_update and after_delete triggers on some models. All of those triggers use the same method that returns a connection object which has various methods such as:
While the RSpec tests were running, these triggers were being called which was junking up the SyncServer. This is not cool. In order to avoid this, I needed to be able to disable the synchronization. I definitely didn't want to do this in all my triggers.
Enter Mikey, he's going to replace the conn object I return:
Now the new conn method:
The sync? method just checks if the class variable do_synchronization is true. I set this class variable to false if the RAILS_ENV == test.
The result is my triggers don't break, the sync server isn't junked up and my tests pass. All good.
# SyncServer::connection method
module SyncServer
def self.connection
new_client.connection{|conn| yield conn }
end
end
SyncServer::connection do |conn|
conn.add_person(person_data)
# or
conn.update_person(person_data)
# or
conn.delete_person(person_id)
end
# The following is to demonstrate I could have
# multiple types of data I'm syncing
# and therefore multiple methods on the
# conn object.
SyncServer::connection do |conn|
conn.add_other_data(other_data)
conn.modify_other_data(other_data)
conn.delete_data(other_id)
end
While the RSpec tests were running, these triggers were being called which was junking up the SyncServer. This is not cool. In order to avoid this, I needed to be able to disable the synchronization. I definitely didn't want to do this in all my triggers.
Enter Mikey, he's going to replace the conn object I return:
#
# Because Mikey will eat anything
#
class Mikey
def method_missing(method, *args)
true
end
end
Now the new conn method:
module SyncServer
def self.connection
if sync?
new_client.connection{|conn| yield conn }
else
yield Mikey.new
end
end
end
The sync? method just checks if the class variable do_synchronization is true. I set this class variable to false if the RAILS_ENV == test.
The result is my triggers don't break, the sync server isn't junked up and my tests pass. All good.
Labels:
RSpec,
Ruby,
RubyOnRails
Friday, December 7, 2007
Thursday, December 6, 2007
RSpec and protect_from_forgery
This took longer than I wanted...
Working on edge Rails and using RSpec I ran into a problem with the new forgery protection features and testing.
I'm using the new cookie session store, so I have the secret key commented out in application.rb:
However, while using RSpec, this causes my tests to fail. So, somehow I needed to call the protect_from_forgery method and pass in the secret, but only for my tests. Turns out, this is easy, but was not obvious to me due to my lack of experience with RSpec.
In the before block of your view spec add the following line:
That will allow your tests to pass if you run into this problem. Why this was happening is another post involving RJS. I'll get to that one soon.
Working on edge Rails and using RSpec I ran into a problem with the new forgery protection features and testing.
I'm using the new cookie session store, so I have the secret key commented out in application.rb:
protect_from_forgery # :secret => 'somefunkycoolsecret'
However, while using RSpec, this causes my tests to fail. So, somehow I needed to call the protect_from_forgery method and pass in the secret, but only for my tests. Turns out, this is easy, but was not obvious to me due to my lack of experience with RSpec.
In the before block of your view spec add the following line:
@controller.class.protect_from_forgery :secret => "mysecret"
That will allow your tests to pass if you run into this problem. Why this was happening is another post involving RJS. I'll get to that one soon.
Labels:
RSpec,
RubyOnRails
Monday, December 3, 2007
Attachment_Fu and dynamic sizing
I had a requirement to create a thumbnail version of my original image, but the catch was that the size depended on some other attributes that the user could select. So, I needed to set the thumbnail size value to a method rather than a static value.
My has_attachment call looked something like:
I then had to make some changes to the attachment_fu plugin:
I added this method (probably could be more slick...) in technoweenie/attachment_fu.rb:
I then modified the resize_image_or_thumbnail method (also in technoweenie/attachment_fu.rb):
My has_attachment call looked something like:
has_attachment :content_type => :image,
:storage => :file_system,
:processor => MiniMagick,
:thumbnails => { :thumb => :user_selected_dimensions }
...
def user_selected_dimensions
# e.g. selected_dimension => "100x50"
self.selected_dimension.split("x")
end
I then had to make some changes to the attachment_fu plugin:
I added this method (probably could be more slick...) in technoweenie/attachment_fu.rb:
# Determine what the parameter is and handle it
# Options are
# 1. String "100x50"
# 2. Array [100,50]
# 2. Method to return one of the above
#
def evaluate_parameter(param)
return param if param.is_a?(String)
return param if param.is_a?(Array) && param.length > 1
param = param[0] if param.is_a?(Array)
if param.is_a?(Symbol) && self.respond_to?(param)
self.send param
else
param
end
end
I then modified the resize_image_or_thumbnail method (also in technoweenie/attachment_fu.rb):
def resize_image_or_thumbnail!(img)
if (!respond_to?(:parent_id) || parent_id.nil?) &&
attachment_options[:resize_to] # parent image
resize_image(img,
evaluate_parameter(attachment_options[:resize_to]))
elsif thumbnail_resize_options # thumbnail
resize_image(img,
evaluate_parameter(thumbnail_resize_options))
end
end
Labels:
RubyOnRails
Subscribe to:
Posts (Atom)
