Mimicking Rails 3’s ActiveRecord query interface in Rails 2.x
NOTICE: This is only for educational and fun purposes, nothing else. I'm not trying to say that you'll get all the pros of the new query interface in Rails 2.x, it isn't even close.
The first time I used the new ActiveRecord query interface (Rails 3) I started to get boring with the one in Rails 2.x, I can't do anything, I have to use the old query interface since converting my projects from Rails 2 to Rails 3 is not an option, so I thought to make a sort of placebo, just for fun, just to be used in my local console, and here is.
module FakeActiveRecord3Methods
def self.included(base)
base.class_eval do
%w/select group order limit offset joins having/.each do |method|
named_scope method.to_sym, lambda { |arg| { method.to_sym => arg } }
end
named_scope :where, lambda { |arg| { :conditions => arg } }
named_scope :includes, lambda { |arg| { :include => arg } }
end
end
end
Click here to see a Gist in GitHub
I used named scopes because they are chainable, if I would have added instance methods to ActiveRecord::Base it would haven't worked as I want to, maybe there's a way to make it work that way, but this was a 5-minutes module, I didn't want to spent too much time on it.
well, I have to say I don't use this module in production servers, only locally in development mode. This is how I configure my development environment to use it:
In my environment/development.rb
config.after_initialize do
[User, And, All, The, Models, You, Want].each do |model|
model.class_eval do
include FakeActiveRecord3Methods
end
end
end
Click here to see a Gist in GitHub
I add the models I use the most, if I need to use the FakeActiveRecord3Methods module in other model I include it in the console. To make this even easier I added to my ~/.irbrc the following code:
class Class
public :include
end
It allows us to include a module this way:
Profile.include FakeActiveRecord3Methods
otherwise Ruby will throw an exception saying we're trying to access a private method.
It might seem cumbersome to make all of this but it isn't that hard.
It's a shame I won't be able to prove that FakeActiveRecord3Methods works in most of the cases but you can test with the examples shown in Rails Guides: Active Record Querying.
Here are some queries using this module anyways. I think all of them are self-explanatory.
User.select("id, email").limit(2)
=> [#<User id: 1, email: "e@mail.com">, #<User id: 2, email: "bar@foo.com">]
User.select("id, email").where(["created_at > ?", 1.month.ago]).order("email DESC").limit(1)
=> [#<User id: 5, email: "foo@bar.com">
User.limit(5) User.select(:id) User.select(:id).limit(10).offset(100) User.order :created_at
Joins
users = User.select("users.id, profiles.company").joins(:profile)
users.first.company # => Foo Company
users.first.profile.company # => Foo Company
Includes
users = User.limit(10) users.first.name # => Foo users.first.profile.company # => it will query the database users = User.includes(:profile).limit(10) # it will bring the profile as well users.first.profile.company # it won't query the database
Of course, you should be aware that in named scopes the order really matters, be careful and remember, in Rails 3, the where, select, group, order, limit, offset, joins and includes returns an instance of ActiveRecord::Relation which is not the case in the FakeActiveRecord3Methods module, it returns an instance of ActiveRecord::NamedScope::Scope and that's because named scopes returns an instance of such class.
By the way, if you want to read the Rails 2's ActiveRecord Query Interface, you can do so clicking here
Thanks for reading.
My friend the Rails console – Part II
You can find the first part clicking here.
Despite all this Rails 3 thingy, we have to face it, we won't ditch Rails 2.3.x anytime soon, so I'm still in the mood to post things about Rails 2.3.x. This time I'll try to provide you guys with some Rails 3 version of the tricks or tips shown here.
Let's get it started.
Accessing helpers
When we fire up the Rails console automatically we have an instance of ActionView::Base called helper; if you have helper :all in your ApplicationController all the files in the app/helpers folder will be available in the helper object.
If we included all the helpers, using the Double-TAB trick (completion) would be a little bit annoying because we might have a lot of helpers starting with the same words. Let's fix it.
waza = ActionView::Base.new waza.extend WazaHelper waza.waza_helper_method(10) # => <waza>10</waza>
Remember that all the helpers are Ruby modules, and we can't instance modules, only mix them into classes
Now, let's say we haven't included all the helpers and we need to use one of those, say WazaHelper again.
helper.waza_helper_method # => method not found include WazaHelper waza.waza_helper_method(10) # => <waza>10</waza>
ActionController logging
In the first part of this post I spoke about calling controller actions from console, well, now I want to see what is being logged when I call those actions:
ActionController::Base.logger = Logger.new(STDOUT) reload!
Or you can use a helper from my .irbrc:
log_ac_to STDOUT
Log your sessions to a separate log file
Let's say you have a buggy model and you are in one of those never-ending debugging sessions, and you'd like to send all the ActiveRecord logs to a separate file:
my_logger = Logger.new('log/my_logger.log')
my_logger.info('testing my logger') # go to see my_logger.log
ActiveRecord::Base.logger = my_logger
reload!
NOTE: Logger.new will create the log/my_logger.log if it doesn't exists, if it does, it'll use it.
Now, If you want to send the logs back to the console
ActiveRecord::Base.logger = Logger.new(STDOUT) reload!
Or you can use a helper from my .irbrc:
log_ar_to :waza #ActiveRecord will log to log/waza.log log_ar_to STDOUT #ActiveRecord will log to console
Environment shortcuts in Rails 2
There are several ways to specify the environment:
$ RAILS_ENV=production script/console $ script/console production
but there are a shorter way to do it:
$ alias sc='script/console' # you can put this in your ~/.bash_profile or ~/.bashrc $ sc p # => production $ sc d # => development $ sc t # => test
The "p", "d" and "e" shortcuts were implemented in Rails 2, you can see the implementation clicking here
Sadly, this won't work in Rails 3, these shortcuts have been deprecated and I haven't found either any documentation about the shortcuts nor any deprecation warning in Rails 3.
The good news is that Rails 3 comes with some predefined shortcuts by default
$ rails --help #it will show the list of commands and their shortcuts console Start the Rails console (short-cut alias: "c") server Start the Rails server (short-cut alias: "s")
Let's make it even shorter
$ alias r='rails' # you can put this in your ~/.bash_profile or ~/.bashrc $ r c
Using different ruby versions in the console
$ script/console --irb ruby1.9
In Rails 3 the --irb option has been deprecated, if you use this option Rails 3 will respond with:
DEPRECATED: Invoke `/your/choice/of/ruby script/rails console` instead
so you have to do:
$ ruby1.9 script/rails console
The Rails Runner
Suppose you want to run some specific code in the Rails context, you can use script/console but sometimes you want to execute just a single line, that's what the runner was made for:
$ alias sr='script/runner' $ sr 'MyJob.new.do_it'
You can send a file as argument too and specify an environment
$ sr -e production 'deploy.rb'
Want to send arguments to the deploy.rb file?
$ sr -e production 'deploy.rb' -- arg1 arg2
If you want to schedule a background job to run every 15 minutes:
0,15,30,45 * * * * PATH_TO_APP/script/runner -e production 'MyJob.new.perform'
The ~/.irbrc file
Everytime you fire the rails console or even just irb, this file is read. You can load gems, insert helpers, extend the ruby core, etc.
You can find my personal .irbrc file in this git repository http://github.com/rafmagana/irbrc
Hacking IRb
There's a number of gems that will help to make the console even better:
Utility Belt - http://utilitybelt.rubyforge.org/
Wirble - http://pablotron.org/software/wirble/
Pretty Print - http://is.gd/dAg40
Awesome Print - http://github.com/michaeldv/awesome_print
But, what if you don't find what you need in any library.? My first approach is to look for others .irbrc's and copy what I find useful, if I don't find what I need I make my own helpers.
Here I'll show you some simple examples.
Helpers
Sometimes I find myself writing 'clear' but that method doesn't exist in IRb, so
def clear
system('clear')
end
The following is a very common trick among ruby developers
class Object
def local_methods
(methods - Object.instance_methods).sort
end
end
it returns an array of methods defined in the parent class of the object
class Waza
def say_hello(name)
"hello, #{name}"
end
end
w = Waza.new
w.local_methods # => ["say_hello"]
Nice!, let's get a little bit further
class Class
def class_methods
(methods - Class.instance_methods - Object.methods).sort
end
end
class Waza
def self.am_i_a_class?
"yes, and an object too"
end
end
Waza.class_methods # => ["am_i_a_class?"]
Interacting with the Mac pasteboard (clipboard)
Mac OS has some commands to access the pasteboard from terminal (http://is.gd/dAQjQ), pbcopy and pbpaste:
$ echo "http://download/file.zip" | pbcopy (or you can press Cmd-C in Safari or wherever) $ wget `pbpaste` # wget http://download/file.zip (Cmd-V)
I really use these commands almost as a daily basis, but I wanted to use them in IRb, so I did it.
In my .irbrc you can find I extended the Object class with two methods:
Object#to_pb => copy text to the Mac OS's pasteboard
Object#paste => paste text from the Mac OS's pasteboard
UPDATE: I made a gem called PasterboaRb that adds these methods to the Object class.
"my text".to_pb
If you go to any Mac application and press Cmd-V the "my text" text will be pasted.
"ruby".to_pb waza.say_hello paste # => "hello, ruby"
The Mac OS has 4 pasteboards: general, ruler, find and font.
"mac".to_pb :general # default "rails".to_pb :find "ruby".to_pb :ruler "rack".to_pb :font waza.say_hello(paste :find) # => "hello, rails" waza.say_hello(paste :ruler) # => "hello, ruby" waza.say_hello(paste :general) # => "hello, mac" waza.say_hello(paste :font) # => "hello, rack"
I really don't know what "ruler" and "font" are useful for, I couldn't find any documentation, but:
"general" - it's the default pasteboard; Cmd-C and Cmd-V use this pasteboard
"find" - Used by Cocoa applications to hold the value of the find field. (Whaaaat?)
Do the following:
"find rails".to_pb :find
Then open Safari or Textmate or whatever Cocoa application you want, now press Cmd-F and the text field will be automatically populated with the value of the "find" pasteboard, in this case, "find rails".
Real-life examples?
If I need to share an objects through several IRb sessions, I mean, in different terminals.
Terminal 1
w = Waza.new w.name = "rails" w.to_yaml.to_pb
Terminal 2
w = YAML::load paste w.name # => "rails"
Sometimes I want to copy to the pasteboard something that a method returns, like usernames, email addresses, passwords, etc:
User.find(10).email.to_pb
Then I can paste the email pressing Cmd-V wherever I want. Yes, I'm that lazy.
That's it, hope it helps.