my take on rails environment specific constants
It’s funny how every Rails application I - and possibly you - work on ends up needing some sort of per-environment global constants.
Examples may include the application url - It might be used in account activation emails and thus should be different between the development and production environments.
Or perhaps your application depends on external services that, depending on the environment, are available in different URIs.
There are a couple solutions out there but my needs were simple and straightforward, thus I developed a small rails plugin that is the simplest thing that could possibly work: AppConstants.
It's been useful for my current project and I hope it can be useful to someone else too ;)
monit thinking sphinx and rvm
In one of my Rails projects I'm using Sphinx to provide full-text search capabilities. To integrate both worlds I chose Thinking Sphinx, which is just great and so far has met all of my expectations.
Also, as I previously mentioned, I'm using RVM to manage my ruby installations on both my development and production machines and this setup is what motivated this post.
I use Monit to monitor the services running on my production server - nginx, mysql, php - and as of the first deploy of this application, it only made sense to also monitor Sphinx.
In order to create an initialization script, I would need at least a way to start and stop sphinx from the command line, which, using Thinking Sphinx, can be done using these rake tasks:
$ rake thinking_sphinx:stop
$ rake thinking_sphinx:start
It's worth mentioning now that I don't run sphinx as root. I run it with the same user my rails application uses. For the purpose of this post, let's call it deploy.
When I tried using my script I got errors such as these:
Missing the Rails 2.3.5 gem. Please `gem install -v=2.3.5 rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.
After some digging around I found that the GEM_HOME environment variable for my deploy user wasn't being set correctly - something to do with rvm, but not quite sure - and to fix this, well, I just had to set it, leaving the final working version of my script somewhat like this - simplified :
#! /bin/sh
### BEGIN INIT INFO
### END INIT INFO
set_path="cd /your/rails/app/root; export GEM_HOME=/path/to/your/gems; RAILS_ENV=production;"
case "$1" in
start)
echo -n "Starting sphinx: "
su - deploy -c "$set_path rake ts:start" >> /var/log/sphinx.log 2>&1
echo "done."
;;
stop)
echo -n "Stopping sphinx: "
su - deploy -c "$set_path rake ts:stop" >> /var/log/sphinx.log 2>&1
echo "done."
;;
*)
N=/etc/init.d/sphinx
echo "Usage: $N {start|stop}" >&2
exit 1
;;
esac
exit 0
There are 2 things happening here. First, regardless of the user I’m running this init script as, I drop privileges in order to execute the rake task with my deploy user. Second, I set the GEM_HOME environment variable to stop getting the ‘gem not found’ errors.
After that, monit was able not only to monitor my sphinx instance, but also (re)start/stop it with the correct user.
I’m no Linux wizard so if you wanna suggest improvements to this script, feel free to do so!
managing multiple ruby versions
Today I read a nice post - in Portuguese - by Fábio Akita on how to manage multiple ruby versions on your machine. I've tried it once with some tool I can't even remember the name but failed miserably.
But this time things look very different. The tool here is the rvm - short for Ruby Version manager - and it works just great.
Let's cut to the chase and imagine that you, like me, want to run/develop/test your code on both ruby 1.8.7 and ruby 1.9.1. These steps would get you up and running in a few minutes:
Install rvm:
$ gem sources -a http://gemcutter.org/
$ gem install rvm
$ rvm-install
$ echo "if [[ ! -z $HOME/.rvm ]] ; then source $HOME/.rvm ; fi" >> ~/.bash_profile
$ source ~/.rvm/scripts/rvm
Install the ruby interpreters you want to use:
$ rvm install ruby-1.8.7-p160
$ rvm install ruby-1.9.1
Now it's important to notice that at this point you have separate gem installations for each of the interpreters you've installed in the previous step. That said, just go ahead and switch between your interpreters and use your command line scripts - ruby, gem, etc... - as usual.
Switching between interpreters:
$ rvm ruby-1.8.7-p160 #switch to the specified version
$ ruby -v
ruby 1.8.7 (2009-04-08 patchlevel 160) [i686-darwin9.8.0]
$ gem install rails #note I'm not using sudo since the new gem paths point to the user's home directory
$ rvm ruby-1.9.1 #switch to the specified version
$ ruby -v
ruby 1.9.1p376 (2009-12-07 revision 26041) [i386-darwin9.8.0]
$ gem install rails #note I'm not using sudo since the new gem paths point to the user's home directory
And that's it, just go on and install rails, merb, sinatra or whatever rocks your boat!
rvm will work with MRI/YARV, JRuby, Ruby EE and Rubinius. Enjoy and don't forget to check rvm's website for the complete documentation! :)
rupy 2009 poznan
Next week the RuPy Conference 2009 will be held in Poznan, Poland and I was planning to talk there.
My talk had been approved and everything was looking good - I was gonna talk about JRuby in the Enterprise - until my move to ThoughtWorks became a reality less than a month ago.
This move led to a lack of support and time from my current company. I was counting on it to go there but since I'll be leaving soon, that wouldn't be possible.
Since then I've been in touch with Adam Parchimowicz - part of RuPy's organization team - and we tried to find alternatives to this. I wanted to thank him for all the effort he put into trying to make this happen. The terms we agreed on were good enough but this change of priorities kicked in too close to the event.
Long story short, I'd like to apologize for not being able to give my talk at RuPy this year.
Apart from that, I gave a very similar talk at the Rails Summit Latin America this year in Brazil and you can find the slides and sources for it in my Presentations page. ( Video coming soon )
my slides from rails summit 09
{% img /assets/images/rs2009.jpg %}
Rails Summit finished a few days ago and I have only one word to describe it: Awesome!
I met some really cool people, discussed a whole bunch of technical subjects and managed not to get so nervous in my first presentation ever - I'm not counting internal presentations I've done for my team...
My slides and source files can be found here. Feel free to contact me with questions if you got any.
soon off to conquer lands afar
Once more my life is taking a huge turn.
I've been pretty quiet for the past month and honestly don't know how I managed to hold in my excitement. I've just accepted an offer from ThoughtWorks to be based at their australian offices, either in Sydney or Melbourne, depending on where my 1st project will be.
For those of you who don't know, ThoughtWorks is a global IT consultancy that is really well known in the software development community. It's been advocating agile methodologies and test-driven development for several years now and seems to work really hard to attract and keep many bright people working there.
As a result, ThoughtWorks inspires companies and professionals around the world by delivering high quality projects and massively supporting/contributing to open source projects.
Needless to say, I am really excited to be joining a company with such an amazing culture and so many smart people.
The hiring process was long, tough and tiring - but yet enjoyable. It took 5 interviews, 1 code review and 1 pairing session, a process through which I talked to 8 thoughtworkers. They take pride on their hiring process and now I understand why.
Now I'm just going through the bureaucratic side of things: gathering documents, certificates, translations - all in order to apply for my VISA, which can take up to 3 months to get ready. In the meantime, you can still find me here in Madrid, where I'll be celebrating with a few 'cañas'. Feel free to join me! ;)
a few more thoughts on final classes
I said final classes are evil and that post got some attention with interesting comments. Maybe because of the title and the tone I wrote it, a few comments didn't get my real intention and perhaps I should have been more explicit about it. Go ahead and read it. I'll wait. :)
Anyway, I thought I'd expand a little more on that subject, explaining my motivation to write that post and going through the topics I think were raised by my dear readers.
First off, final classes are evil for testing. And that's what it was all about in my previous post.
If you depend on a final class, your code will be harder to test. Unless the final class provides an interface that captures its intent - or you wrap that dependency.
But this affirmation has some implications that were pointed out by a few comments, some of which I agree with - others, not so much. So let's start!
- Immutability
Someone said "Why make a class final ? To make it immutable". This is not entirely true. Only by marking it final you do not ensure immutability. There is no point in doing that if you provide mutators - e.g. setters. - and don't declare your members private and final.
I think it's important to make this clear and understand that the immutability part you achieve by marking a class final is the one of preventing inheritance. Subclasses could possibly contain malicious/careless code and change the internal state of the class.
But there is another way of preventing subclassing without marking the parent final: declare its constructor private and provide a static factory.
- Designing for extensibility
This is hard. It basically means that if you don't mark a class final, you should document it for inheritance.
And this is why inheritance is, in general, a bad OO practice. By documenting the class you basically break encapsulation since you tell the world about your internals.
Therefore, the recommendation is to mark a class final if you're not sure if it's safe to subclass it - or if you just don't wanna bother writing documentation and thinking too much about your "client" subclasses.
- Coding against interfaces
This one is simple but yet often forgotten. Do not code to concrete classes. Always choose interfaces where possible.
It roughly means to do this:
List args = new ArrayList();
instead of this:
ArrayList args = new ArrayList();
By doing so you have the flexibility to not care about the implementation you're working with, as long as it obeys the interface. That way, the implementations can be swapped at any time without breaking any client's code.
- The problem with testing
All items listed here so far are widely regarded as best practices and the bullet I raised about hard testing usually happens when you "violate" some of them.
Specifically, if you decide not to design a class for inheritance and mark it as final, it's wise - in my opinion - to try and capture the class's intent through an interface.
That way you can safely mark your class final and users of your class can easily use the interface to extend it - by favoring composition over inheritance - or by providing it to mocking frameworks for easy testing.
- Conclusion
I don't really think there is a rule of thumb here. Java's standard library shows many examples of both approaches and some of them are now considered bad practices but yet are there for backward compatibility. Nevertheless, these are points to consider when designing your classes.
As pointed out by Josua Bloch in his awesome book Effective Java, "If a concrete class does not implement a standard interface, then you may inconvenience some programmers by prohibiting inheritance".
As usual, comments are more than welcome :)
refactoring for readability
Yesterday I've done something I should do more often: Revisit some code written a while ago for our current project and make it better.
Let's face it. We all write crappy code the 1st time. The difference is in what we do about it afterwards.
We might decide it's good enough and keep moving, or we could (and should!) stop and refactor it!
The code I revisited worked as a refactoring exercise and it's initial version is shown below:
class Jphoto
...
#a few other methods ...
def post_photo(file_data, hotel_id, send_rss, options = {})
file_name = "tmp/#{Time.now.to_i}_#{rand(1000000).to_s(36)}"
File.open(file_name, "wb") do |f|
f.puts(file_data)
end
params = [Curl::PostField.file('photo',file_name),
Curl::PostField.content('hotel', hotel_id),
Curl::PostField.content('source','PhotoUploadTest')]
extract_extra_params!(params, options)
c = Curl::Easy.new("#{service_uri_base}/photoupld")
c.multipart_form_post = true
c.http_post(*params)
if c.response_code != 200
error_msg = "File upload failed with code: #{c.response_code}"
Rails.logger.info error_msg
raise error_msg
end
File.delete(file_name)
hotel = Hotel.find_by_id(hotel_id)
hotel.cache.destroy_all
send_upload_rss(hotel, original_upload_url(c.body_str) , options) if send_rss
end
private
def send_upload_rss(hotel, photo_url, options)
...
end
def manage_images_link(hotel_id)
...
end
def extract_extra_params!(params, options)
params << Curl::PostField.content('status', options[:status]) if options[:status]
params << Curl::PostField.content('upload_source', options[:upload_source]) if options[:upload_source]
params << Curl::PostField.content('uploader_ip', options[:uploader_ip]) if options[:uploader_ip]
params << Curl::PostField.content('uploader_email', options[:uploader_email]) if options[:uploader_email]
end
end
Look at the post_photo method. It has problems in so many levels that it’s hard to start.
Methods should do “one thing” and that method obviously does much more than that, mixing different levels of abstraction.
But let’s start with the easy parts first, keeping in mind that I was aiming for readability.
Lines 7 to 10 seem to be there just to make the reader’s life harder. It’s creating a temporary file through some custom logic instead of using the tools provided by the language. Unnecessary and only pollutes our eyes. My first measure was to use ruby’s TempFile class for this. Better, but we still have a long way.
Right at line 12 it creates some sort of default parameters list, after which it extracts some extra options. I don’t know what that method does but it’s clearly using output arguments, which we should avoid at all costs, as they lead to confusion. This is a big smell as well, and another refactoring step added to my list.
On line 21 starts the code that handles what to do when we get a response_code other than 200 from our request. Apart from the fact that this code doesn’t feel right here, we just happen to know that in HTTP, 200 means success, but that might not be clear to someone looking at the code for the 1st time.
Then the code goes on to delete the temp file, clear the hotel’s cache and send the rss if the rss’ flag is true.
Let there be refactoring….
Geez, how many lines have I used to explain what the code does? Since I don’t wanna bore you to death, here is my refactored version of this method, trying to avoid as much as I can the problems I highlighted previously:
rails summit 2009 im speaking
I'll be speaking at this year's Rails Summit Latin America in Sao Paulo, Brazil. It will be a good opportunity to meet some amazing people and visit friends back home! :)
Overall I'll be spending 12 days in Brazil, with 2 of them dedicated to the conference. The other 10 I'll be in Rio de Janeiro visiting my family and friends. I strongly advise you to spend some time in Rio too, if at all possible. It's an amazing city and you can contact me if you have any questions.
Back to the conference, my session is called JRuby in the enterprise world: Using Rails with legacy code, and will be given in the form of a tutorial. I will walk you through some problems we had while making this kind of integration at my company, focusing mostly on dependency management.
At the end I hope you'll have a good understanding of what JRuby is capable of in a legacy environment.
If you're planning to attend and would like to hear anything specific about JRuby, please let me know, I can try and squeeze in.
C u there!
rails rumble 09
Update: The service is now down while we move it from the VPS provided by Rails Rumble to our own. I'll let you know once it's up.
Last weekend Philip, Pedro and myself got together for this year's Rails Rumble.
We haven't had really decided what to do until a few days before the competition, but I had this really simple idea and decided to go with it. Seems people liked it, given a few positive comments we received.
So, after 48 hours - which were not used to work full-time in the application - The Bird Watcher was born.
The Bird Watcher is a simple way to show the world what's going on on Twitter for any topic you define. Go ahead and take a look at the website to see a live example.
We're planning to keep the service up after the competition is over and we have some nice features lined up to go live on the next release.
In short, it was an interesting weekend and showed me that this team works really well together.
Cheers