Vendoring rails using a symlink
Been meaning to do this for ages!
First of all clone rails from git hub into a suitable folder
git clone git://github.com/rails/rails.git rails.git
I like to give repositories like this a .git extension so can easily differentiate from local things, but you can call the folder whatever you want.
After the repository is cloned be sure to checkout the branch you want, in my case git checkout origin/2-3-stable
Now you can vendor your rails projects by going into the root folder of the project and running
ln -s /path_to/rails.git vendor/rails
Adjust the path as required.
Updating Passenger
Updating the passenger gem is a bit of a pain. Its dead easy to just to the gem update and forget to do the other stuff you need to do, especially if you update lots of gems at the same time.
Anyhow after updating passenger be sure to run passenger-install-apache2-module as root. Follow the instructions and update the passenger config (in/etc/apache2/other/passenger.conf on my work box)
Contextual Controllers
I've been struggling alot recently with controllers and with code written by my colleagues. During this period I think I've been making quite a few mistakes. One of the rules I generally like to make about things particularly in software is that if its stupid or doesn't work its probably your fault. This is a really important rule which is real easy to apply to anyone but yourself. So its really easy for me to spot others making mistakes by not applying this rule. When I hear people say Selenium doesn't work, or SASS is rubbish I can comfortably be a wise old owl and think, "ah they should just take a little more time and they will realise that these judgements are made hastily.
Another rule I like to apply is that we (human beings) are mostly ignorant about everything. Socrates (who at the time was considered the wisest of the Greeks) used to tell people seeking his wisdom that he knew nothing. This wasn't some glib bit of rhetoric to reinforce his social status. All his years of study had made him fundamentally aware of how vast knowledge is, how much depth any subject can have, and how little he really did know and understand. This ignorance goes far deeper than just a lack of factual knowledge and goes from the global to the intimate. Using this rule is really dangerous, its so easy to apply it to others and to forget to apply it to yourself, doing this results in smug ignorance. In fact the only value of this rule is self application. It is perhaps a more dangerous expression of the buddhist concept of a "begginers mind"
So what does this have to do with controllers
Well I've been struggling with existing code in our application and in particular with trying to keep code out of controllers. I don't want code in controllers for a number of reasons
- They seem to be much harder to spec than models
- It seems real easy for various bits of business logic to end up in them - where it is really hard to test
- Its real easy for important things like error messages to end up in controllers, and again it feels that this is the wrong place for them
- I'm just not comfortable with them, they don't feel like good objects.
Now I've recently been dealing with alot of existing code in controllers that I've not been happy with. In our application we are now treating addresses as a polymorphic nested entity that other entities can have. In addition we have introduced the concept of NamedAddress, which are special in the fact that they have a name and also in the way they persist. Both our controller and views; and our features and steps have struggled to isolate the addressable bit from the relationship with the particular parent.
During this process I've been learning lots, improving the code and generally working really hard on improving quality. Trouble with working really hard is that its really easy to work really stupidly and I've been doing that too. I've also been assuming that existing code is stupid. Take the following code in the index method of the OrdersController
def index
@pending_orders = @customer.pending_orders
@completed_orders = @customer.completed_orders
end
I broke this code (well actually the view it shows) by the following refactoring of routes
map.resources :customers do |customers|
customers.resources :addresses
customers.resources :orders
end
to
map.resources :customers, :has_many :addresses, :shallow => true
map.resources :customers, :has_many :orders, :shallow => true
This is a really stupid refactoring. Why?
So back to this index method which I'm now having to look at because my features are broken. Well its obviously wrong, because its an Order controller with customer code in it. I've just spent ages making sure that my Address controller doesn't have code that refers to a specific parent, and refactored lots of features and in particular steps that should have been Addressable not Customer/Address or Order/Address. And now on top of that there are all these Order features and in particular steps that have @customer in them. So now I'm peeved and am about to start to work even harder to get this stuff sorted when ... I have to go home.
Time passes ... and I realise that "its my fault" and that "I am stupid"
Remember "if its stupid and it doesn't work its probably your fault". Remember that stupid refactoring! and the fact that your added shallow routes to the customer order relationship at the same time as to the customer address relationship. Understand that applying shallow routing will change/break numerous named routes and url generation methods. Realise how stupid it is to do this in for two relationships at the same time. Realise that doing this refactoring for one relationship should be a seperate issue and commit - and here I am trying to do this for two relationships in the middle of doing something else!! Realise that I shouldn't even be looking at the OrdersController never mind making flawed judgements about the code in it. Understand that in the 'shop' part of this application that orders fundamentally belong to a customer and cannot exist without them. Realise that my colleagues implicitly get this possibly due to their greater experience with ecommerce. Realise that if the index method in the OrdersController is flawed its only very minor (perhaps just the customer needs to made available to the view). Realise that controllers are more contextual than models; and that the implied one to one relationship between controllers and models is a figment of my misunderstanding. Realise that a simple one line before filter can express a controllers context and allow a controller to fulfill a significantly different role.
There is lots more, but I'm really tired now.
So once again programming provides that wonderful opportunity to learn, and reinforces the need for my to keep my little rules and diligently apply them to myself.
Using fbrp
FBRP is my rails starter application. When you base an application on it you need another git repository to push the application to. So I have to set up the application in a way that it can get updates from FBRP and yet still be independent.
Base and Master Branches
Most Git apps have a master branch. When you set up a new application from FBRP your master branch will be tied to FBRP. This is not desirable. So instead we create a 'base' branch for fbrp
gch -b base
Now we modify .git/config.
[remote "fbrp"]
url = git://github.com/diabolo/fbrp.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "base"]
remote = fbrp
merge = master
What this does is tie the base branch in our new application to the master branch of FBRP. Now when FBRP is update we can pull directly into the base branch
gch base
git pull
After that we can merge back into the master branch doing a rebase
gch master
git rebase base
Setting up local remote
We can clone our new project and put it on little-un. In the following our new project is 'tc4'
git clone --bare tc4 tc4.git
scp -r tc4.git deploy@little-un:/srv/git
Now we remote into little-un and change permissions
sshlun
sudo chown -R git:git /srv/git/tc4.git/
Finally we go back to our dev box and clone a new project
mv tc4 tc4.old
git clone git@little-un:/srv/git/tc4.git
If we do a
gb -a
We get
* master
origin/HEAD
origin/base
origin/master
If we wish to get updates from fbrp to our new clone we have to create a local base branch and update our .git/config as above
gch -b base
modify .git/config by adding
[remote "fbrp"]
url = git://github.com/diabolo/fbrp.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "base"]
remote = fbrp
merge = master
Now we should have
Branch | Push | Pull
------ | ------------- | ------
Base | no | yes (little-un)
Master | yes (github) | yes (little-un)
Initial Application Steps
- database.yml
- update session key in environment.rb
- update application name and repo location in deploy.rb
- create local database tables
Then migrate and run
rake spec
rake features
Change Rails Environment when running scripts
Always seem to forget this and take ages finding it again
RAILS_ENV=production script/...
New Rails Project (part 3) - Default Layout
Here we discuss getting a simple structure in place for laying out the pages of our application ...
Using HAML and SASS
Our setup is dependent on HAML and SASS. Lets just add this to the HAML branch first of all and see how things go.
gch haml
We are going to add some SASS stylesheets but first of all we'll use a reset style sheet.
Our SASS stylesheets will separate 3 concerns
- layout
- typography
- appearance
Layout
Layout comprises of two sass stylesheets, grid-base and structure.
grid-base defines a virtual grid using SASS arithmetic, which by default is divided into 24 columns. The grid is all about the width of these columns. If you use percentages you can create a liquid layout. If you use ems you can create a fixed width layout which can resize quite nicely. The approach is to setup a large number of SASS mixins. This is based on the code in Blueprint. The innovation is to not let any of the blueprint class styles get into your code. Instead we use the SASS mixins to apply the code to our semantic css styles. Some examples
In blueprint html ends up like
<div id=wrap class="span-8"> ...
With sass its just
<div id=wrap> ...
but in structure.sass we have
#wrap
+span-8
this includes the sass mixin +span-8 into the css for #wrap.
This pretty much gives us the best of both worlds - a powerful grid and semantic html source. We also end up with less junk in our css as only the sass mixins that are used end up in the final stylesheets.
Typography
Typography uses one stylesheet (typography.css). It is mostly concerned about getting vertical rhythm in our document. Here we define a desired default line-height in pixels and then use a combination of SASS arithmetic and constants to apply this line height to our typographical styles which are em based. This allows us to create styles with different font sizes that all line up.
Appearance
This is controlled by screen.sass and is all about defining the colour, position and maybe horizontal margins of elements.
New Rails Project (part 2) - Plugins
Now we have a deployed application we can add some plugins to get those extra bits of functionality we want.
Missing Directories
What we've done so far has created a rails project that is to minimal. Applying git and uploading the project has removed any empty directories. If you get errors just create the directories. e.g.
mkdir -p vendor/plugins
Installing plugins on GIT branches
Might be a good idea to install plugins on branches. With sufficient git-fu we should be able to remove plugins later if we really wanted to. This could be a good tip for experimenting with plugins. So for each plugin do
git branch plugin_name
git checkout plugin_name
git add .
git commit -m "installed plugin_name"
git checkout master
git merge plugin_name
Haml and Sass
Haml and Sass are no brainers for me. There are alternatives, but everything I've done with Haml and Sass has pleased me
Assuming you've installed the haml gem, at the root of your project run
haml --rails .
Testing Framework
Here things get a little more difficult. Basically I'd like to use the full RSpec stack with plain text user stories, but it just seems to be too hard. Also my current user doesn't seem that interested. Really finding that learning curve for RSpec is so steep that I end up not testing.
An alternative is "shoulda". This builds on top of rails in built tests instead of replacing them. Its been suggested that its a gentle way to get into BDD. Anyhow I am tempted by it.
script/plugin install git://github.com/thoughtbot/shoulda.git
Attachments
So far I've used attachment_fu and been fine with it. However there is an alternative called paperclip by the same people who have done shoulda. This seems simpler to use and gets mucho praise in the blogsphere.
script/plugin install git://github.com/thoughtbot/paperclip.git
Tagging
So far have used acts_as_taggable_on_steroids however a newer plugin acts_as_taggable_on exists which supports multiple tag sets. This means that on my list of products I could have a :category, :location tags etc.. This extra functionality might be useful.
script/plugin install git://github.com/mbleigh/acts-as-taggable-on.git
Creating a New Rails Project
A step by step guide to creating a new rails project. The project will be called np. We start with ...
rails np cd np
Setup GIT
git init
Remove log files
We want the log folder to be empty
rm -f log/*.log
Stop Git From Ignoring Empty Folders
We want git to check in all empty folders so we keep the project structure and don't get missing folder errors later. Unfortunately the only way we can do this is put a hidden file in each folder. We use a .gitignore file for this, and the following command puts one in each empty folder
find . ( -type d -empty ) -and ( -not -regex ./.git.* ) -exec touch {}/.gitignore \;
found that my blog is not showing the backslashes which are escaping each bracket in the above command, so try this one instead which doesn't use the brackets
find . -type d -empty -and -not -regex ./.git.* -exec touch {}/.gitignore \;
Remove database.yml and make a template
mv config/database.yml config/database.yml.example
Make Initial Commit
git add .
git commit -a -m "initial commit"
Setup .gitignore
Because we now have no empty folders we can do this before the initial commit
mate .gitignore
Set contents to be
log/*.log
tmp/*
public/cache/**/*
doc/api
doc/app
config/database.yml
vendor/plugins/doc/**/*
Commit changes
ga .
gca
Test Clone
cd ..
git clone --bare np np.git
mv np np.old
git clone np.git
cd np
rake -T
script/console # wil generate error about database.yml
Move Clone to Git Server
cd ..
scp -r np.git deploy@little-un:/srv/git/
Change Ownership of Clone on Server
sshlun
sudo chown -R git:git /srv/git/np.git
Make Local Clone From Server
rm -rf np
git clone git@little-un:/srv/git/np.git
Setup Database
Each development environment will have its own database settings, and our production environments will in turn have their own secure settings. So we don't want to put database settings in our repository. We can put a template in our repository to guide setup.
git mv config/database.yml config/database.yml.example
git commit -a -m "removed database.yml from repo - added template"
Now each time you clone the repository you will have to setup an appropriate database.yml. Git will ignore this file because you told it to in '.gitignore'.
TIP Keep a local database.yml file on your development box that is configured for your local database settings. Use this as your template.
Capify and Setup Staging (Early Production) Environment
Doing this really early because I think its really important to do as early as possible. Really like to be able to push with git and then deploy.
capify .
Now you need to create a deploy.rb file. I already have one for my production environment, so its just a question of modifying it for this application.
We must modify
- the application name (must not contain spaces)
- the repository location
We can then check it by
cap deploy:check
This should product a couple of errors one telling you to run cap:deploy:setup. Make sure you get this (you don't want to overwrite an existing production app). Now setup and check again
cap deploy:setup
cap deploy:check
Now we should be ready for our first deployment. Be aware thought that we have to setup our web server before things will work. Also don't forget to commit changes to repo
ga .
gca -m "capified project and completed deploy:setup"
Web Server Setup
On web server ...
Again copying an existing setting. I'm using Apache and Passenger on Ubuntu. Existing sites are in /etc/apache2/sites-available. Create a new site by copying existing one and modifying. My new one came out as
<VirtualHost *>
ServerName tc2.little-un
ServerAlias tc2.andrew.premdas.org
ServerAdmin webmaster@localhost
DocumentRoot /srv/rails-apps/tc2/current/public
</VirtualHost>
Then as root
a2ensite tc2
apache2ctl configtest
n.b. should get warning about public folder not existing
Cap Cold Deploy
Back on dev box ...
Now may have done enough to do a cold deploy - which is a first deploy that does some additional stuff
cap deploy:cold
Got error that database table did not exist, so created on server
Errors and Rails Version
Didn't have Rails 2.1 on my server when I did this. Can either freeze rails in application or update server. I'm updating the server for now! Probably should freeze.
Cap Deploy
Have to do at least one cap deploy to get my production database.yml setup (which is done by the capfile)
Just about done - got working production application. Now can get started with plugins and migrations
Getting a Rails action to return to where it was called from
Often in Rails you will want to use a partial in more than one place. Such partials might add or delete things. Once this is done you will want to return to where you were. The following works without javascript and Ajax ...
The information we want is called the HTTP_REFERER. This is contained within the request object which is hugely complex. The request contains an env hash and the HTTP_REFERER is in there.
request.env["HTTP_REFERER"]
Now you can use this in your controller code e.g.
respond_to do |format|
format.html { redirect_to request.env["HTTP_REFERER"]}
end
Rails Debugging
Easy peasy once you know how.
- start up your server with the
-u - put
debuggerin your code where you want the debugger to start - run that code e.g. in a web browser
The web browser will hang, and a debugging session will be started in the terminal you ran the server. Main things to remember are
hget helpllist the code where you arereloadif debugger doesn't pick up changes in your code give it a little kickcontcarry on execution
That should be enough to keep me happy
Older posts: 1 2