If you are brand new to MongoDB and Rails 4, take a quick look at my very basic rails 4 mongodb tutorial before diving into this one.

Gems: mongoid, omniauth, figaro

Let’s get started

Make sure you have Rails 4 (rails -v). We’ll make a Rails app skipping test-unit (-T), since I prefer RSpec, and omitting ActiveRecord (-O) since we’ll be using MongoDB.

rails new parakeet -T -O
cd parakeet

Add the following to the Gemfile

gem "mongoid", git: 'git://github.com/mongoid/mongoid.git'
gem "omniauth-twitter"
gem "figaro"    # key configuration using ENV 

Now some auto-code generation for quick setup:

rails g mongoid:config
#      create  config/mongoid.yml

rails generate figaro:install
#      create  config/application.yml
#      append  .gitignore

I’ve decided to use figaro which allows me to easily configure my API keys without committing them to my source repo, which is very helpful when posting open source code. We need to set up the app for an API key in order to auth with Twitter.

Get Developer Key from Twitter

Sign in using your regular Twitter account at: https://dev.twitter.com/

Then in the upper-right, select “my applications”

Click “Create a new application” and fill in the form. I called my app blue-parakeet for uniqueness — you’ll have to make up your own name.

Make sure you put in a callback URL, even though you won’t use it for development (since omniauth tells twitter the callback URL to override this setting) — if you don’t supply one you will get a 401 unauthorized error.

Read and Accept the Terms, then click “Create Your Twitter Application”

Now you have a “key” and “secret” (called “consumer key” and “consumer secret”) which you will need to configure your rails app.

Using Figaro gem for Configuring API keys

Edit config/application.yml

# config via Figaro gem, see: https://github.com/laserlemon/figaro
# rake figaro:heroku to push these to Heroku
TWITTER_KEY: ABCLConsumerKeyCopiedFromTwitterDevPortal
TWITTER_SECRET: XYZConsumerSecretCopiedFromTwitterDevPortal

Configuring Omniauth

Edit config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

Now Omniauth is already setup to auth with twitter. Let’s run the server. Install mongo with brew install mongodb if you haven’t already. Also, if you don’t have mongo set up to run automatically at startup, then run Mongo:

mongod

Then run Rails server:

rails s

Go to http://localhost:3000/auth/twitter and you’ll be presented with twitter auth

However, when we authenticate, we get an error, since we have’t configured our routes yet:

Create a Sessions Controller, Add Routes

Next step is a sessions controller and a route for the OAuth callback. We’ll make a placeholder create action that just reports the auth info we get back from Twitter.

On the command line:

rails generate controller sessions

Edit the newly created file, app/controllers/sessions_controller.rb

require 'json'
class SessionsController  request.env["omniauth.auth"]
  end
end

add the following to config/routes.rb

get '/auth/:provider/callback' => 'sessions#create'
get '/auth/failure' => 'sessions#failure'
get '/signout' => 'sessions#destroy', :as => :signout
root :to => redirect("/auth/twitter")  # for convenience

Now go to http://localhost:3000/auth/twitter — after authenticating with Twitter, you will see the user info that Twitter sends to the app from the authentication request (see docs for explanation of each field). The general stuff which is more consistent across providers is in the ‘info’ section, and most of the interesting twitter-specific info is in the “extra” section:

User Registration

For this app, we’ll use a simple user model, just to show that there’s no magic here — we’re only using Twitter auth not storing our own passwords, so we don’t really need the full features of the lovely Devise gem.

rails generate scaffold user provider:string uid:string name:string

Add to app/models/user.rb

  def self.create_with_omniauth(auth)
    create! do |user|
      user.provider = auth['provider']
      user.uid = auth['uid']
      if auth['info']
        user.name = auth['info']['name'] || ""
      end
    end
  end

With Rails 4 the recommended pattern to lock down model attributes that we don’t want changed from form submits (or malicious attacks) is in the controller. In app/controllers/users_controller.rb change:

    def user_params
      params.require(:user).permit(:provider, :uid, :name)
    end

to:

    def user_params
      params.require(:user).permit(:name)
    end

and then remove the corresponding fields from app/views/users/_form.html.erb

Finally, the real create action for the sessions controller, plus a destroy action for the /signout url we defined earlier:

  def create
    auth = request.env["omniauth.auth"]
    user = User.where(:provider => auth['provider'],
                      :uid => auth['uid']).first || User.create_with_omniauth(auth)
    session[:user_id] = user.id
    redirect_to user_path(user), :notice => "Signed in!"
  end

  def destroy
    reset_session
    redirect_to root_url
  end

With this app, we’ve got a basic understanding to Twitter OAuth using Rails 4 and the OmniAuth gem. We didn’t actually do anything specific to MongoDB and no testing yet. It is important to understand the technology we’re working with before testing or even writing production code.

Special thanks to Daniel Kehoe of RailsApps. His Rails 3 OmniAuth Mongoid tutorial provided a helpful foundation.

This post covers getting started with Rails 4 and Mongodb, using the Mongoid gem. As part of getting up to speed, I enjoyed reading Rails + Mongo take on the object-relational mismatch by Emily Stolfo from MongoDB.

For starters, here’s a how to create a super simple toy app. I assume Ruby is installed via rvm, but you are new to Mongo.

Install Mongodb on Mac OSX

brew update
brew install mongodb

To have launchd start mongodb at login:
ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
Then to load mongodb now:
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
Or, if you don’t want/need launchctl, you can just run:
mongod

Rails 4 with Mongoid

I chose Mongoid over MongoMapper (see quora, stackoverflow)
I used MRI 1.9.3 (at this writing, Mongoid supports HEAD but not 2.0)
rvmrc:

rvm use ruby-1.9.3-p429@rails4 --create

added to Gemfile:

gem "mongoid", git: 'git://github.com/mongoid/mongoid.git'

on the command-line:

rails new mongo-people --skip-active-record
rails generate mongoid:config
rails generate scaffold person name street city state
rails s

Woo hoo! We’ve got an app — looks like a vanilla Rails app from the outside, but it is different on the inside:

class Person
  include Mongoid::Document
  field :name, type: String
  field :street, type: String
  field :city, type: String
  field :state, type: String
end

No database migrations needed. If we want a new field, we could just declare one in the model and add it to our views. I assume migrations could be used for data migration, but that would be a subject of another post.

References

Rails 4 with MongoDB Tutorial

Bryan Braun (@bryanebraun) gave a refreshingly opinionated talk Empowering Content Creators with Drupal.  Coming directly from the Ruby and Rails communities where a core value is to articulate best practices, it is great to see this kind of guidance from a member of the Drupal community.  (slides here)

Bryan referred to a blog post by Dilbert creator Scott Adams on the Botched Interface Market, where sites like Orbit and Travelocity had such poor user experiences that they inadvertently created a market opportunity for new sites like Hipmunk.  Bryan’s goal is that Drupal should not become a botched interface market.

He organized his advice into two categories:

  1. More Control: Power Tools, Reducing Risk
  2. Less Confusion: Better Interface, Streamline Workflow

Bryan highlights borrowed techniqes from other platforms (mostly WordPress), and points out lots of “low-hanging fruit” techniques.

Example: the default layout for a node has a lot of labels and whitespace which doesn’t contribute to understanding the page.  The default UI for a file attachment is not concise.  Compare Gmail’s message compose interface vs. what it would be in Drupal (~1/3 of your screen vs. a page that is two screens high!)  Having a very long page can contribute to confusion, since you have to scroll to get to some of the functionality.

Larry Garfiled’s blog post Drupal is not a CMS points out that Drupal is something you use to build a CMS.  Perhaps we should think of it as a CMF, a Content Management Framework.  In this case, we are designing the workflow and user experience of a new custom CMS that we build with Drupal.

Myth: I am not a designer
Fact: um.. you actually are. Whether you identify as a designer or not. The decision you make when you are building a site has an effect on the end user — these are design decisions!

Myth: it will take a long time and effort
Fact: it could but it doesn’t have to

More Control

We can make our content creators more productive by giving them “power tools” while at the same time, reducing risk that they will make mistakes will give them confidence to move faster.

WYSIWYG

It’s not really an option anymore to tell people to edit HTML. [My personal perspective is that even though I am perfectly capable of HTML markup, why should you make me type extra characters? why should I have to learn the markup that works with your stylesheet? Though I do like the option of editing HTML when the rich text editors fail me.]

The following modules are good for WYSIWYG and File Upload:

  • wysiwyg or ckeditor which both appear to be rich text, WYSIWYG editors
  • media or imce for uploading and managing file (also seems to be an either/or choice

Node Editing Power

Always check “Add to Menu” on the Node Edit page — for most content creators, if a menu isn’t there, the feature doesn’t exist.

With context, use a module with Context Node which can pull out just a few context options and put it on the Node Edit page.

As a themer, you can make a bunch of templates and the content creator can pick with template -picker module.

Use Short Codes

Short code are a best practice from WordPress and can accelerate content creation:

[video:url]
[[nid:123]]

Drupal Modules:

  • Caption Filter
  • Video Filter works with the wysiwyg module or with TinyMCE or CKEditor (not sure why those are in a different category).
  • Insert View lets content editors add views without editing PHP
  • Make your own

In Drupal we tend to expose this kind of functionality as a block, but that gives the power to the site builder, rather than the content creator. In Drupal, the modules that do this tend to be called filters.

Reduce Risk

Always put description text when you are creating new fields (you probably won’t come back later). If you are going to come back later, you can improve it then — at least put something in.

  • Autosave
  • Revisions: just enable it by default
  • Preview — poor experiences with the preview button, not always what you expext
  • Turn off preview button on content types page, use use view_unpublished module instead

Granting Appropriate Access is not an easy fix.  You need to understand how your organization works, know the users, watch them work, etc. Once you do know those things, you can set up good workflows for them with clear options for the different roles in the organization.

Less Confusion

Admin Themes can help

  • Rubik is less cluttered: 2-column node-edit page, description text on
  • Ember is responsive

Logical Grouping

Group things according to how your content creators think about them, not how you built them. Consider grouping admin menu items into safe vs. risky like WordPress does. Bury advanced and less-frequently used functionality.

  • Fields into fieldsets & field collections
  • Admin Menus for content creators
  • Permissions into Roles
  • WYSIWYG Icons

Default to expanded for highly recommended options, collapse for optional, and hide anything that is unnecessary.

  • Use conditional fields (never show someone an option that does nothing
  • Simplify hides fields
  • Jammer hide many other things)
  • Hide Submit Button avoids double submissions and just an importantly communicates to the creator or editor that the site is actually doing something
  • Preview Button (see above)

Streamline Workflows

  • Use Chosen instead of the default multi-select
  • Add Another is great for repetitive node creation.

Dashboard

There are lots of great dashboard modules.  Consider what your creator or editor wants to see the most — make that their default homepage.

“the best tool of them all… …is the one that works best for your users”

It looks like I’ll still have to do quite a bit of experimentation, since Bryan often points to multiple modules to solve the same challenge — still a great reference to address common concerns and highlight best practices.