Django/Rails cheat sheet

Hey, Django dev, come here for a sec!
I heard you’re on Rails now, looking for some quick answers?
Yeah, I’m your man, got them right here.
ORM
I’m gonna be straight with you, Django’s ORM is nicer than Rails’s.
There, I said it. Now that it’s out of my system let’s find some models:
# One model
Dj> Model.objects.get(id=1)
Ra> Model.find(id: 1) # when using a PK
Ra> Model.find_by(name: "A name") # when using any other field
So far so good, Rails uses find
when looking up by ID, find_by
when looking up by anything else.
# Single record filtered collection
Dj> Model.objects.filter(name="First")
Ra> Model.where("name = First")
# Multiple record filtered collection
Dj> Model.objects.filter(name__startswith="First")
Ra> Model.where("name LIKE 'First%'")
I can almost hear you saying “Is that (gasp) SQL?”. Yep, it is, it’s good to get back to the basics sometimes, am I right or am I right? Basically, there’s no field lookups, but Django’s field lookup documentation’s got the SQL equivalents for you.
Let’s add some ordering:
Dj> Model.objects.all().order_by('created_at')
Ra> Model.all.order(created_at: :desc) # or 'created_at DESC'
and selecting single and multiple elements:
Dj> Model.objects.all()[5:10]
Ra> Model.all.offset(5).limit(5)
Dj> Model.objects.all().first()
Ra> Model.first # and .second and .third, etc.
Dj> Model.objects.all().last()
Ra> Model.last
Selecting specific fields:
# Single array of fields
Dj> Model.objects.values_list('field', flat=True)
Ra> Model.pluck(:field)
# Multiple fields
Dj> Model.objects.values('field', 'otherfield')
Ra> Model.pluck(:field, :otherfield) # array of arrays
Ra> Model.select(:field, :otherfield) # array of partial objects
When passing multiple fields Rails’s pluck
will return nested arrays of fields values. If you’d rather have something like an array of hashes with field-value pairs like Django’s values
you can use select
. Notice however that select
initializes objects with only the selected fields, it’s not an actual hash.
Now let’s join stuff!
# Query spanning relations
Dj> Model.objects.filter(other_model__id=3)
Ra> Model.joins(:other_models).where("other_models.id = 3")
Notice the joins
, the argument needs to match the relation name in the ActiveRecord, like belongs_to
or has_one
. Also note the prefixing of the ID in the where
, it needs to match the other model’s table, which should just be the plural of the model’s name if you follow Rail’s database conventions.
Creating is quite similar:
Dj> Model.objects.create(field="foo", other_field="bar")
Ra> Model.create(field: "foo", other_field: "bar")
As is updating:
Dj> Model.objects.get(5).update(field="bar")
Ra> Model.find(5).update(field: "bar")
or, less efficiently...
Dj> m = Model.objects.get(5)
Dj> m.field = "bar"
Dj> m.save()
Ra> m = Model.find(5)
Ra> m.field = "bar"
Ra> m.save
And finally deleting/destroying:
Dj> Model.objects.get(5).delete()
Ra> Model.find(5).destroy
Both of these have a collection-specific cousin, update_all
and destroy_all
.
Dj> Model.objects.filter(field="foo").update(field="bar")
Ra> Model.where(field: "foo").update_all(field: "bar")
Dj> Model.objects.all().delete()
Ra> Model.all.destroy_all
In Rails you’re bound to encounter two similarly named methods, for example create
and create!
. This pattern is common in Ruby in general, the latter will raise an exception if it fails and the former will return false
.
Django’s lovely aggregation/annotation functions are present in Rails via the calculate
method, the more common ones have shortcuts methods:
Dj> Model.objects.count()
Ra> Model.count
Dj> Model.objects.all().aggregate(Sum("num_field"))
Ra> Model.sum(:num_field)
Dj> Model.objects.all().annotate(Max("other_model__num_field"))
Ra> Model.joins(:other_models).maximum("other_models.num_field")
Again, notice how in Rails you need specifically join to the other model before specifying the column in maximum
.
Management tasks
You’ve probably memorized a bunch of management commands for Django for several tasks and most likely created custom ones for your projects.
Rails of course has the same concept, some of them are specific to Rails, like commands to generate migrations, and others are defined using Ruby’s Rake
and defined in a Rakefile
. Here are a few of the most common ones:
# Start a project
Dj> django-admin startproject myproject
Ra> rails new myproject
This will generate all the basic folders for you to start working on.
# Create a migration
Dj> django-admin makemigration
Ra> rails generate migration AddDescriptionToModel
As you know Django’s migration system, and in general, is based around models. I wrote an overview of the differences between the two architectures if you want more details, for now just now that Rails does not do migrations based on models. generate migration
will generate a file in db/migrate
with a timestamp for you to add the migration commands of your database.
# Execute the migrations
Dj> django-admin migrate
Ra> rake db:migrate
# Rollback
Dj> django-admin migrate 0001_initial
Ra> rails db:rollback # the latest migration
Ra> rails db:migrate MIGRATION_ID # usually a timestamp
Notice how this one is a Rake task, this will run through the migrations and apply the ones that have not been already applied.
# List the migrations and their status
Dj> django-admin showmigrations --list
Ra> rake db:migrate:status
Loading fixtures:
Dj> django-admin loaddata my_data.json
Ra> rake db:seed
Rails’s seeds are scripts that can load any type of data. The seed
command will run through all the scripts in db/seeds
directory and execute them.
Our trusted shell and the dev server:
Dj> django-admin shell
Ra> rails console
Dj> django-admin runserver
Ra> rails server
Adding new boilerplate code for you to work on.
Dj> django-admin startapp mynewapp Ra> rails generate controller mynewcontroller mynewaction
These are not really equivalent, Rails does not have the concept of apps, there is only one. The generate controller command will create a controller with a single action named mynewaction.
And of course testing:
Dj> python manage.py test
Ra> rake test
# Specific tests
Dj> python manage.py test myapp.tests.MyTest
Ra> rake test tests/my_test_suite.rb TestTheTruth
Now, to create your own management commands in Rails you need to define them in lib/tasks
. The tasks should have a .rake
extension and you probably want to namespace them:
namespace :my_namespace do
desc "Description of My Cool Task"
task :my_cool_task => :environment do
puts "This is cool!"
end
end
If you run rake -T
you should see your task under the namespace ready to be executed.
Forms
Vanilla Rails does not have a specific class for forms, however it does ship with a series of template helpers to make writing forms easier, but that’s probably not enough for you if you come from Django.
Luckily we can create a similar behaviour to Django’s Forms using a combination of the Virtus gem and some elements from ActiveRecord base class. What’s Virtus? Adam Hawking explains it best but essentially it allows you to create attributes that will be coerced to the correct data type in Ruby.
What about validations? Turns out we can use that ActiveRecord’s Validations framework to define them.
Remember that pattern in Django’s ModelForms where we simply call save()
on the form if it’s valid? We can do a similar thing using ActiveRecord::Conversion
which allows you to implement persistence methods.
If we wrap all into a custom BaseForm class:
class BaseForm
include Virtus.model
include ActiveModel::Conversion
include ActiveModel::Validations
def persisted?
false # to be implemented in the subclass
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
raise # to be implemented in the subclass
end
end
which we can subclass this BaseForm to create more specific forms with custom methods for persistance and validation:
class BookForm < BaseForm
attribute :book, Book
attribute :title, String
validates :title, presence: true
def initialize(book, attributes = {})
self.book = book
self.title = attributes[:title] || book.title
end
def persisted?
self.book.persisted?
end
def persist!
book.title = title
book.save!
end
end
This will allow you to create form objects and validate the fields even in the console:
Ra> BookForm.new(Book.first).persisted?
=> true
# valid forms
Ra> form = BookForm.new(Book.new, {:title => "Crime and punishment"})
=> #<BookForm...>
Ra> form.valid?
=> true
Ra> form.persist!
=> true
# invalid forms
Ra> form = BookForm.new(Book.new)
=> #<BookForm...>
Ra> form.valid?
=> false
These forms along with the form helpers should have your form needs covered.
So there you go, that’s about as quick cheat sheet I can give you to get you past the first hurdles when you’re used to Django and find yourself working in Rails.
I’ll let you get back to it now. Have fun!