Rails Deep Dive: Application Setup, Loccasions
- Rails Intro, Deep Dive: RVM
- Rails Intro, Deep Dive: Installing Rails, Part One
- Loccasions: Installing Rails Part 2
- Rails Intro, Deep Dive: App Generation
- Rails Deep Dive: Application Setup, Loccasions
- Rails Deep Dive: Loccasions, Home Page
- Rails Deep Dive: Loccasions, Authentication
- Rails Deep Dive: Loccasions, Spork, Events and Authorization
- Rails Deep Dive: Loccasions, Making Events
- Loccasions: Pair Programming
- Loccasions: Hiring a Foreman, Inheriting Resources, & Occasions
- Loccasions: Going Client-Side with Leaflet, Backbone, and Jasmine
- Loccasions: Getting to Occasions
- Loccasions: Bubbly Map Events
- Loccasions: Retrospective
Up to this point in the Rails Deep Dive series, we’ve focused on digging down into the entrails of the framework, attempting to uncover some of the ways that Rails accomplishes its magic. Going forward, I want to create a Rails 3.1 application, focusing on how I would setup up the application, perform the development, and deploy the application. I think the series will benefit from having a specific goal in mind.
Our application will be called Loccasions. The purpose of Loccasions is to allow users to create Events and Occasions. An Event might be “I cleaned my room” or “It rained” or “A comet sighting”. Events contain Occasions, marking a time and place where the Event occurred. The application will present the occasions on a map, allowing the user to see how often and where an Event occurs. The idea is simple and the use case specific, so creating the app should be a snap (he says, knowing he will hit roadblocks….)
User Stories
When creating a new Rails application (or any application, really) it’s a good idea to have some user stories to direct the application and ensure we are staying on task. Normally, you would meet with the client and generate the high level user stories together. The key with user stories is to capture just enough detail to start working, avoiding the “analysis paralysis” that can cripple progress. For Loccasions, we will keep the user stories pretty high level, adding more as we go. Our first stories are:
- As an unregistered user, I want to see the home/landing page
- As an administrator, I want to be able to invite users to Loccasions
- As an invited user, I want to be able to create an account
- As a registered user, I want to be able to create Events
- As a registered user, I want to be able to create Occasions
- As a registered user, I want to see Occasions on a map
I think that is a good start.
Gems
The next decision concerns the gems we are going to leverage to take care of some of our functional needs. Obviously, Loccasions will need some kind of authentication, and the community has great gems in this area. Probably the most well known authentication gem is Devise written by Jose Valim and the incredible folks at Plataformatec. I think using Devise gives us a well-tested gem and a fantastic community for support.
One of the decisions I have made for Loccasions is how persistence will be handled. Rather than go the standard relational database route, like PostGIS or MySQL, I have chosen MongoDB for our back-end persistence store. First, I think the Event ==> Occasions model makes a good document db model. Second, I am relatively certain that Loccasions will use the spatial functionality that MongoDB provides. Also, if I am being honest, I really want to use MongoDB in a “realish” Rails app and this is opportunity knocking.
The use of MongoDB leads to another area where gems can help. In this case, I looked at MongoMapper and Mongoid and settled on Mongoid because it seems to have slightly better support for the spatial parts of MongoDB, as well as the existence of mongoid_spacial
It’s worth noting that this conclusion is based on a few minutes of looking at both sets of docs, so it could be wrong. However, this is how decisions are made, sometimes, when starting an application. Pick a direction and go. Also, it is highly likely that we’ll run into version issues between gem dependencies. If/When this happens, we may have to either sacrfice a gem or fork it and fix the issue ourselves.
Client Side Stuff
I am relatively sure that we’ll use a decent amount of javascript in Loccasions. My initial vision sees each Event page as a Single Page Application, allowing the user to create Occasions and add them to the map. The map and list of Occasions will stay in sync, which means we have two “views with our view”. This vision pushes me toward a client-side framework, and my current favorite is Backbone.js. The rails-backbone gem simplifies using Backbone with Rails, so we’ll put that gem on the list as well. (Note: There are two Backbone gems that are very closely named, ensure you are using codebrew’s gem)
Also, I have become a fan of Haml so I think we’ll use Haml instead of ERB for our view templates.
Testing
We will, as much as possible, employ a test-driven approach to creating Loccasions. In essence, this means we will write tests first to drive the design and implementation of the app. With that in mind, we need to select a testing approach, and I have decided on RSpec and Capybara. I like RSpec and think something like Cucumber is a bit more than I need for this series. Also, there is a gem that integraties Mongoid and Rspec (mongoid-rspec) that will simplify our testing.
The test-driven approach extends to the client-side of the application, as well, and using something like Jasmine keeps the specification approach consistent.
Source Control
I will be creating a Github repository for the Loccasions source. Before you start any development process, you should have a plan for source control. Git makes it criminally easy to get going with SCM, so there is no excuse.
Other Resources
One of the best tools in your Rails toolbelt is the Internet and standing on the shoulders of those that came before. For example, my inspiration for the Devise and Mongoid setup is one of Daniel Kehoe’s fantastic tutorials. I am sure we will be scouring the web for help and resources, and I hope to highlight what we find.
The Starting Line
Alright, I think that is enough planning. Time to stop dipping our toes in the water and jump in up to our necks. Of course, we need MongoDB running locally. Go install MongoDB on your platform….I’ll wait.
Done? Hopefully you have a default MongoDB setup ready to go. The last thing to do before we generate the application is to create a github repository for our app (Mine is here.)
We are getting closer. I am using Rails 3.1 RC5 and Ruby 1.9.2. Also, I am using RVM and I strongly recommend you set up RVM and a gemset before continuing.
rvm use 1.9.2@loccasions -- create # will create and switch to loccasions gemset and Ruby 1.9.2
We have a clean gemset, so we need to install a couple of gems before we can get to Rails.
gem install bundler
gem install rails --pre # We want 3.1
Remember, we are using MongoDB, so we don’t need any ActiveRecord pieces (-O) (we won’t be using migrations) Also, we are using RSPec, so no need to generate the Test::Unit files (-T).
rails new loccasions -O -T
cd loccasions
Now that we finally have an application structure, we need to pull in the aforementioned gems. Open “Gemfile” in your favorite editor (I use vim, b/c it is fantasmic) and make it look like:
source 'http://rubygems.org' gem 'rails', '3.1.0.rc5' gem 'devise', "~> 1.4.2" gem 'mongoid', "~> 2.1.8" gem 'mongoid_spacial', "~> 0.2.13" gem 'haml', '~> 3.1.2' gem 'bson_ext', '~> 1.3.1' gem 'rails-backbone', "~> 0.5.3" # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', "~> 3.1.0.rc" gem 'coffee-rails', "~> 3.1.0.rc" gem 'uglifier' end gem 'jquery-rails' group :test, :development do gem 'rspec-rails', '~> 2.6.1' gem 'mongoid-rspec', '~> 1.4.4' gem 'capybara', '~> 1.0.1' gem 'factory_girl_rails', '~> 1.1.0' gem 'database_cleaner', '~> 0.6.7' gem 'jasmine', '~> 1.0.2.1' end
A quick bundle install and we are ready to attack our first user story. Before we do that, let’s do the initial commit to our git repository and push it up to github. First, edit the .gitignore and make sure it makes sense:
.bundle db/*.sqlite3 log/*.log tmp/ .sass-cache/ *.swp .DS_Store
I added the *.swp and .DS_Store lines so that my vim buffers and Mac artifacts don’t get added to the repository.
git add . git commit -m "Initial commit"
Now, add your github remote repository as ‘origin’.
git remote add origin https://ruprict@github.com/ruprict/loccasions.git git push origin master
The minute I did that, I realized I had forgotten to create a .rvmrc file, so let’s do that and push it up as well.
rvm --rvmrc --create ruby-1.9.2-p290@loccasions
Now, cd .. and then cd loccasions to make the .rvmrc file trusted. It will prompt you to review the file, then type ‘yes’. Finally, add the .rvmrc to git and push it to github.
git add .rvmrc
git commit -m "Added .rvmrc"
git push origin master
In the next post of the series, we’ll start with the “unregistered user” story, which should lead us to make decisions about how we’ll layout the app. Let me know, in the comments, if you have issues or questions about the setup.
I found this blog post on Heroku very informative about nosql:
http://blog.heroku.com/archives/2010/7/20/nosql/
DAZ
gem 'bson_ext', '~%gt; 1.3.1'
the %gt; is supposed to be >
Once I changed that, everything worked brilliantly. Great article...I'm very much looking forward to the upcoming parts! Thanks for taking the time to do this!
Thanks for finding that...had a rougy '%' where I needed a '&'....fixed now.
Glenn
Nice work! Thank you very much.
I noted that you didn't mencion 'git init' before 'git add .', of course we all know about that, but, maybe you want to put it there for completeness only.
Walter.
I have a question about the gem listings in the Gemfile, which has been bugging me for a while now. How do you know what version numbers to put against each gem? I thought the purpose of Bundler was to work out all the dependencies for you, so that you don't need to specify the versions explicitly. I see lots of rails tutorials that take the approach you have, so it obviously seems like a good idea, but I wouldn't know where to start working out which version numbers I should put in. Can you put me straight?
Tim
The short answer is, I look at the latest on rubygems.org for each gem I want and start there. If Bundler then whines about a dependency, I'll change the version to whatever the dependency needs (had to do this with bson_ext, for example). The twitta-waka (or whatever it's called... the ~> thing) allows your app some flexibility without jumping to a breaking version (presuming the gem authors are following semantic versioning).
That make sense?
-- the twitta-waka allows bundler to upgrade to a higher minor version, but not a higher major version. If you don't put the twitta-waka in, then you risk your gems not working if one of your gems has a major release, you run 'bundle install', and the new gem stops working with one or more of your other gems or rails code (for whatever reason - it's just more likely to happen on a major version upgrade than a minor version upgrade).
-- to find the right gem version you look for the current highest version number on rubygems.org for each gem
-- you then plug the version numbers into your Gemfile, run 'bundle install' and if bundler doesn't complain, you're good to go. But if it does, you read the error messages and adjust accordingly (some googling may be necessary to find the right version I guess).
Or, do you just run 'bundle install' without any version numbers and then list the gems that have been installed to find their version numbers and plug those into the gemfile?
And even if it's not called a twitta-waka, I believe the world would be a better place if it were.
Tim
I would never recommend putting gems without versions into the Gemfile. Also, when you look on rubygems.org, each gem should list its dependencies, including the required versions. So, if you start with, say, Rails 3.1.0 final, for example, you can check each gem to ensure it works with Rails 3.1.0. In practice, however, I generally start with rubygems.org, get my versions there, and cruise until I hit an issue.
Hope this helps...
"After bundling, always check your Gemfile.lock into version control. If you do this, you do not need to specify exact versions of gems in your Gemfile."
[from: http://yehudakatz.com/2011/05/30/gem-versioning-and-bundler-doing-it-right/]
That seems to suggest I can just write, for example:
gem 'rspec-rails'
rather than:
gem 'rspec-rails', '~> 2.6.1'
unless I specifically I _want_ to use version 2.6.1 for whatever reason. If I understand Bundler's purpose correctly, it _should_ resolve dependencies across all gems in the Gemfile (and it too gets its dependency information from the gem's spec file on Rubygems.org as I understand it). So are you saying that if:
1) you _want_ to install not less than version x.y.z for a specific gem, and
2) you want to install new versions of that gem when they are released, and
3) a new gem version is released, and
4) you happen to run 'bundle install'
then to avoid dependency issues caused by the gem upgrade, you make sure you restrict the new version to a minor version upgrade using the twitta-waka.
But if that is true, then wouldn't a strategy of putting gems without version numbers into the Gemfile, running 'bundle install' and then filling in the version numbers from the Gemfile.lock after work?
Sorry for being a bit slow here. There's obviously something about the way Bundler/gems work that I'm not understanding. And I guess I just want to find the lazy option and throw gems into the Gemfile without caring too much about hunting down version numbers!
Tim
I don't think you are being slow, I think i am being lazy. The post you link from ykatz lists the right way to do things. He, after all, has probably forgotten more about how to setup a ruby/rails app than I'll ever know. According to him, you can rely on Bundler to manage your dependencies without putting versions into your Gemfile. As updates occur, running bundle will try to update your app or prompt you when conflicts occur. So, in short, I think you've answered your own question. In fact, I don't even think you need to go back and fill in version numbers from your Gemfile, provided you add the Gemfile.lock to your repository. BTW, Heroku asks you to do just that to ensure the versions of the gems heroku uses matches your dev environment.
Thanks for keeping at this...