Facebook Authentication with Parse

My latest iOS application, Heart Quest, uses Facebook authentication and Parse for the back end. I ran into some challenges getting the two to work together but I am pleased with how my current implementation functions. I’d like to share that implementation here to help anyone who may be developing an app using Facebook authentication with their Parse apps.

NOTE: This will most likely be my only blog post related to Parse due to the service shutting down later this year. I have begun using Firebase as an alternative so look forward to some Firebase tutorials in the near future! 

If you still need to setup the Facebook SDK with your Parse app you can use the excellent guide over at Ray Wenderlich’s website. If you already have the Facebook SDK installed please continue with this tutorial.

To begin, initialize the FBSDK Login Manager and handle any errors:


let manager = FBSDKLoginManager()
manager.logInWithReadPermissions(["public_profile"], fromViewController: self, handler: { (result, error) -> Void in
if let error = error {
print(error.localizedDescription)
}

if result.isCancelled {
print("Cancelled")
} else {

logInWithReadPermissions is where you set the type of permissions you would like to request from your users. In the above example we are only asking the user for their basic profile information (name and profile picture). If you wanted to request additional permissions, such as the user’s Facebook likes, you would add that to the permissions array:

logInWithReadPermissions(["public_profile","user_likes"],

If there are no errors with logging in you’ll then want to check for any valid Facebook access tokens:


 if let accessToken: FBSDKAccessToken = FBSDKAccessToken.currentAccessToken() {
                PFFacebookUtils.logInInBackgroundWithAccessToken(result.token, block: {
                    (user: PFUser?, error: NSError?) -> Void in
                    if user!.isNew {
                        //segue to Sign Up form for a new user
                    } else {
                        //segue to first view for logged in users
                    }
                    if error != nil {
                        print("Uh oh. There was an error logging in.")

Use the .isNew method to check if that user has ever signed in to your app. This allows you to send new users to a separate Sign Up form so that you can save their user information in your database. If .isNew is false, use the else statement to send the logged-in user to your app.

If there is an error with the access token (error != nil), the best way to handle it is to create a new token and retry the login process:


                        let manager = FBSDKLoginManager()
                        manager.logOut()
                        manager.logInWithReadPermissions(["public_profile"], fromViewController: self, handler: { (result, error) -> Void in
                            if let error = error {
                                print(error.localizedDescription)
                            }

                            if result.isCancelled {
                                print("Cancelled")
                            } else {
                                //throw the new token into logInInBackgroundWithAccessToken(:)
                                if let accessToken: FBSDKAccessToken = FBSDKAccessToken.currentAccessToken() {
                                    PFFacebookUtils.logInInBackgroundWithAccessToken(result.token, block: {
                                        (user: PFUser?, error: NSError?) -> Void in
                                        if user!.isNew {
                                            //sign up new user
                                        } else {
                                          //segue to app
                                            }
                                        })
                                    }
                                }
                            })

The final code for the Facebook Login button will then look something like this:

    @IBAction func signin(sender: AnyObject) {

        let manager = FBSDKLoginManager()
        manager.logInWithReadPermissions(["public_profile"], fromViewController: self, handler: { (result, error) -> Void in
            if let error = error {
                print(error.localizedDescription)
            }

            if result.isCancelled {
                print("Cancelled")
            } else {

            if let accessToken: FBSDKAccessToken = FBSDKAccessToken.currentAccessToken() {
                PFFacebookUtils.logInInBackgroundWithAccessToken(result.token, block: {
                    (user: PFUser?, error: NSError?) -> Void in
                    if user!.isNew {
                        //segue to new user sign up form
                    } else {
                      //segue to app
                    }

                    if error != nil {
                        print("Uh oh. There was an error logging in.")

                        let manager = FBSDKLoginManager()
                        manager.logOut()
                        manager.logInWithReadPermissions(["public_profile"], fromViewController: self, handler: { (result, error) -> Void in
                            if let error = error {
                                print(error.localizedDescription)
                            }

                            if result.isCancelled {
                                print("Cancelled")
                            } else {
                                //new token is here and throw the token into logInInBackgroundWithAccessToken(:) again
                                if let accessToken: FBSDKAccessToken = FBSDKAccessToken.currentAccessToken() {
                                    PFFacebookUtils.logInInBackgroundWithAccessToken(result.token, block: {
                                        (user: PFUser?, error: NSError?) -> Void in
                                        if user!.isNew {
                                           //segue to new user sign up form
                                        } else {
                                            //segue to app
                                            }
                                        })
                                    }
                                }
                            })
                        }
                    })
                }
            }
        })
    }

 

Mailboxer Demo App Now Available

I wrote a tutorial last year that explained how to customize the Mailboxer Ruby gem and it was well-received. Many people requested source code so that they could debug issues. I unfortunately did not have the availability to provide the source code at the time. However, I am very happy to announce that an open source application is currently available on Github and it is completely free to use. A live demo of the app is also available on Heroku. I hope this helps anyone that is struggling to get mailboxer up and running in their Rails app! As always, feel free to ask questions if you run into issues.

Workula

Updated 3/1/2015: I was unable to gain traction for this idea so I am shelving this project. I may revisit the idea at a later time.

My project, Workula, has quietly launched today. As much as I wanted to continue to polish the code and add new features it needed to be launched. I am in need of feedback and this is the best way to do it.

So what is Workula? Workula is a website that allows users to share work experience. We’re changing the definition of work experience because it puts college students and the unemployed at a severe disadvantage. Work experience is currently defined as being employed by a company for a certain amount of time. We define work experience as “work”. With Workula, work experience is broken down into smaller updates we call WorkBlocks. A WorkBlock can represent a status update, a comment on other work, new work submissions, and contributions to other work. We feel that breaking down work experience like this will encourage people that don’t have formal work experience to enhance their set of skills and document each step of the way.

I invite you all to sign up for a free account today to begin sharing your work experience:

https://www.workula.com

Any feedback would be greatly appreciated as well. Thanks in advance!

Customizing the Mailboxer Ruby Gem

Update 3/1/2015: This tutorial has been updated for Rails 4.2 and Mailboxer 0.12. I also created a demo application that can be found on Github. A live demo of the app is also available on Heroku.

The Mailboxer Ruby gem is a robust messaging system that is extremely easy to implement in any Rails app. There are some good tutorials out there that explain how to quickly get it up and running but I saw many questions that asked how to customize the inbox. In this blog post I will explain how to set up the Mailboxer gem and customize the views to contain the following:

  • Inbox with Sender, Subject, Date, and link to move to Trash
  • Trash with the same information as Inbox as well as a button to delete all conversations in Trash and a button to move a conversation back to the Inbox
  • Conversation view page showing all associated messages and a reply form

You’ll see the words “conversation” and “message” used throughout this blog post. In Mailboxer a conversation is simply a group of messages. Our Inbox and Trash pages will contain a table of conversations. Clicking on a conversation will open all of the messages associated with that conversation.

Note: I will be using the current_user method to refer to the currently logged in user. This method is supplied by the Devise gem. If you are not using Devise to handle user authentication be sure to replace this method with your own.

Much of this code for the Conversations controller was provided by RKushnir’s Mailboxer sample app on Github. You can see the full controller code here (since this is a private messaging system between two users, the create action in the linked controller is not needed).

Installation

Add the following line to your Gemfile:

gem "mailboxer"

Then run:

bundle update

Next, run the install script. This script will automatically create the migrations that are required for Mailboxer to run:

rails g mailboxer:install

Migrate your database once the installation is complete:

rake db:migrate

Prepare the models

Mailboxer can be used on any model but in this example we’ll prepare our User model to use Mailboxer. Add the following line to your User model:

User > ActiveRecord::Base
  acts_as_messageable
end

Basic Mailboxer view

To create some test messages use the following method in the Rails console or set up a form in a view (replace “recipient_user_object” with a real User object from your app):

current_user.send_message(recipient_user_object, "body", "subject")

The quickest way to show the contents of your Inbox is to add the following line of code in your view:

<%= render current_user.mailbox.inbox %>
If you aren’t using Devise you can substitute current_user with any User object.
As you can see the output is pretty basic if we simply render the inbox using the above code. A bullet list is generated with the conversation subject and a “Move to Trash” link for each conversation.

Edit the Conversations controller

Before we edit our views we’ll need to add some code  to our Conversations controller. Add a conversations instance variable to our index action which will contain all of the user’s inbox messages:
class ConversationsController > ApplicationController
  before_filter :authenticate_user!
  helper_method :mailbox, :conversation
  def index
    @conversations = @mailbox.inbox.all
  end
end

Create a reply action which will be called when replying to a message within a conversation:

def reply
 current_user.reply_to_conversation(conversation, params[:body])
 redirect_to conversation_path(@conversation)
end

Create a trash_folder action in your Conversations controller and add an instance variable which will contain all of the user’s messages in the Trash folder:

 
 def trashbin     
  @trash ||= current_user.mailbox.trash.all   
 end 
end 

The above actions will handle our Inbox and Trash pages. However, we’ll also need to add actions for the Trash and Untrash actions. We will be adding a link on the Inbox page to allow the user to send a conversation to the Trash folder. We’ll also add an “Untrash” link on the Trash page to send a conversation back to the Inbox page. To do this, create the trash and untrash actions in the Conversations controller:

 
def trash
 conversation.move_to_trash(current_user)
 redirect_to :conversations
end
def untrash
 conversation.untrash(current_user)
 redirect_to :back
end

Add an emtpy_trash action to the Conversations controller:

 
def empty_trash
  current_user.mailbox.trash.each do |conversation|
   conversation.receipts_for(current_user).update_all(:deleted => true)
  end
 redirect_to :conversations
end

You’ll also need to add the following private methods called by the other methods mentioned above. They define @mailbox and @conversation:

private

def mailbox
 @mailbox ||= current_user.mailbox
end

def conversation
 @conversation ||= mailbox.conversations.find(params[:id])
end

Edit the Messages controller

We’ll need to add two actions to our Messages controller: new and create. These actions will be used to send a new message to a user, thus starting a new conversation:

class MessagesController > ApplicationController

  # GET /message/new
  def new
    @user = User.find(params[:user])
    @message = current_user.messages.new
  end

   # POST /message/create
  def create
    @recipient = User.find(params[:user])
    current_user.send_message(@recipient, params[:body], params[:subject])
    flash[:notice] = "Message has been sent!"
    redirect_to :conversations
  end
end

Customize the views

Mailboxer keeps track of the participants in a conversation. In our case, the only participants are the sender and the recipient. To show the sender of a message, loop through all of the participants and only show participants except for current_user:

<% @conversations.each do |conversation| %>
  <% if participant != current_user %>
    <%= participant.name %>
  <% end %>
 <% end %>

Conversation.subject can be used to display the conversation’s subject. You can have it link to the full conversation with this:

<%= link_to conversation.subject, conversation_path(conversation) %>

To show the date it was sent, you can use conversation.created_at. In the code below I’ll use update_at since conversations may last longer than a day. I’ll also use strftime to format the date into the “Sun, 01/01/2014 12:00 PM” format:

<%= conversation.updated_at.strftime("%a, %m/%e/%Y %I:%M %p") %>

Now for the Trash link. This link will call the trash action we setup in the controller above:

<%= link_to "Move to Trash", {:controller => "conversations", :action => "trash", :id => conversation.id}, :title=> "Move to Trash", :method=> 'post' %>

In the next section we’ll need to setup the routes so that the Trash links will work correctly. Before we move on to the routes we’ll need to customize the Trash page. The Trash page is going to be the same as above except for two things:

1. When looping through the participants to display the sender, change the first line to this:

<% @trash.each do |conversation| %>

2. Change the Trash link to an “untrash” link. Clicking on this link will send the conversation back to the Inbox:

<%= link_to "Move to Inbox", {:controller => "conversations", :action => "untrash", :id => conversation.id}, :title => "Move to Inbox", :method=>'post' %> 

Create a show.html.erb view page for a single Conversation. You’ll also want to include a messages form that allows the user to reply to a message within a conversation (we’ll create the form next):


<%= conversation.subject %>

A conversation with
<% @conversation.participants.each do |participant| %>
 <% if participant != current_user %>
  <%= participant.name %>
 <% end %>
<% end %>
<%= content_tag_for(:li, @conversation.receipts_for(current_user)) do |receipt| %>
 <% message = receipt.message %>
 <%= message.sender.name %>
 <%= simple_format h message.body %>
 Sent <%= time_ago_in_words(message.updated_at) %> ago

<% end %>

<%= render 'messages/form', conversation: @conversation %>

The messages form, named _form.html.erb in the Messages views folder, is a simple form that looks like this:

Reply:
  <%= form_tag reply_conversation_path(conversation), method: :post do %>
 <%= text_area_tag 'body', nil, cols: 2, class: 'form-control', placeholder: 'Type your message here!', required: true %>
 <%= submit_tag "Send Message", class: 'btn btn-primary' %>
 <% end %>

Lastly, create a new message form that will be used to send a new message to a user and start a new conversation. Name this page new.html.erb within the messages view folder:


Send a message to <%= @user.name %>

<%= form_tag({controller: "messages", action: "create"}, method: :post) do %>
 <%= label_tag :subject %>
 <%= text_field_tag :subject %>
 <%= label :body, "Message text" %>
 <%= text_area_tag :body %>
 <%= hidden_field_tag(:user, "#{@user.id}") %>
 <%= submit_tag 'Send message', class: "btn btn-primary" %>
<% end %>

We’re almost there! The last thing we need to do is update the routes file.

Updating the routes

Add this to your routes:

resources :messages do
  member do
    post :new
  end
end
resources :conversations do
  member do
    post :reply
    post :trash
    post :untrash
  end
 collection do
    get :trashbin
    post :empty_trash
 end
end

And that’s it! Please let me know if you have any questions.

Troubleshooting the Rails Asset Pipeline for Cloud Application Platforms

I recently deployed a Rails application to a cloud application platform in order to gather some feedback. The application ran just fine on my development machine and I couldn’t wait to push it to a server so other people can test it. I had this preconceived notion that my app would magically appear on the public internet with the push of a button (you would think I knew better by now). The process took a few sleepless nights to complete. This blog post will explain the issues I encountered with the deployment and how I resolved them.

My first thought was to deploy my app to Heroku since deploying an app to Heroku was one of the first things I had learned when reading through the popular Rails Tutorial. Once I had pushed my app to Heroku I immediately noticed that something went terribly wrong. My app was not loading CSS! The app’s logo loaded just fine but there was absolutely no formatting. Just white background with black text in a single column. Thinking that there must have been an incompatibility issue with my app and Heroku I decided to try deploying to a Digital Ocean server using the Cloud66 platform. Same result. I then knew something was wrong with my configuration.

After doing some basic research I discovered that many other people run into this issue as well. I saw many different solutions that, at the time, I knew very little about. Before doing any research at all I highly recommend reading the Ruby on Rails documentation on the asset pipeline. Had I read that sooner I think I would have came to a solution in a shorter amount of time and with less stress.

The issue is with the /config/environments/production.rb and /config/application.rb configuration files. I will post my copies of these files further below. For now I want to explain the important configurations settings.

The first thing to check is that the asset pipeline is enabled. Make sure config.assets.enabled is set to true in your application.rb file:

 config.assets.enabled = true

In that same file you also want to ensure that the following is set to false:

config.assets.initialize_on_precompile = false

This setting is required in order to get your rails app properly deployed to Heroku or Cloud66+Digital Ocean. I use the word properly because setting this to true will enable assets to be compiled live as the app is running. This uses much more memory and leads to poor performance. Make sure you set this to false to have assets precompiled before running (this is explained later in this post).

You will most likely want to compress your Javascript files and have the default Ruby gem uglifier handle the compression for you (you can read more about this in the Ruby on Rails documentation). In your production.rb file make sure the following setting is set to true:

config.assets.compress = true

You will then want to ensure that all of your CSS and Javascript files will be included. In your production.rb file make sure config.assets.precompile is set to the following:

 config.assets.precompile = [ /\A[^\/\\]+\.(ccs|js)$/i ]

The above two settings were the causes of my issues. After fixing the settings above I noticed Javascript errors in my console when I tried to precompile my assets. This eventually led me to discovering a missing “}” in one of my CSS files. Make sure that these two asset precompile settings are set properly so that you can solve any potential syntax issues locally in development mode.

Now that those configurations have been set you are ready to precompile your assets in your local development environment. To precompile your assets, type the following in your console:

bundle exec rake assets:precompile

Once this operation completes you may see errors like I did. Carefully read through the errors to pick out the error message, the file that caused the error, and the line number where it may be occurring.

After your assets are precompiled you will need to commit your changes and push them to your git respository:

git add .

git commit -a -m “precompiled assets”

git push

Lastly you will need to push the newly precompiled assets to your Production deployment. For Heroku, simply push your code to your server with the following:

git push Heroku master

For Cloud66, click on the Deploy icon on your dashboard to deploy with your new code changes.

Here are my production.rb and application.rb files for your reference:

production.rb

AppName:Application.configure do
# Settings specified here will take precedence over those in config/application.rb

# Code is not reloaded between requests
config.cache_classes = true

# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true

# Disable Rails’s static asset server (Apache or nginx will already do this)
config.serve_static_assets = false

# Compress JavaScripts and CSS
config.assets.compress = true

# Don’t fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = true

# Generate digests for assets URLs
config.assets.digest = true

# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH

# Specifies the header that your server uses for sending files
# config.action_dispatch.x_sendfile_header = “X-Sendfile” # for apache
# config.action_dispatch.x_sendfile_header = ‘X-Accel-Redirect’ # for nginx

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true

# See everything in the log (default is :info)
# config.log_level = :debug

# Prepend all log lines with the following tags
# config.log_tags = [ :subdomain, :uuid ]

# Use a different logger for distributed setups
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)

# Use a different cache store in production
# config.cache_store = :mem_cache_store

# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = “http://assets.example.com”

# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
config.assets.precompile = [ /\A[^\/\\]+\.(ccs|js)$/i ]

# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false

# Enable threaded mode
# config.threadsafe!

# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true

# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify

# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
# config.active_record.auto_explain_threshold_in_seconds = 0.5

#TODO: Edit mailer config
config.action_mailer.default_url_options = { :host => ‘localhost:3000’ }

# config/environments/production.rb
config.paperclip_defaults = {
:storage => :s3,
:s3_credentials => {
:bucket => ENV[‘AWS_BUCKET’],
:access_key_id => ENV[‘AWS_ACCESS_KEY_ID’],
:secret_access_key => ENV[‘AWS_SECRET_ACCESS_KEY’]
}
}
end

application.rb

require File.expand_path(‘../boot’, __FILE__)

# Pick the frameworks you want:
require “active_record/railtie”
require “action_controller/railtie”
require “action_mailer/railtie”
require “active_resource/railtie”
require “sprockets/railtie”
# require “rails/test_unit/railtie”

if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
#Bundler.require(:default, :assets, Rails.env)
end

module AppName
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# — all .rb files in that directory are automatically loaded.

# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)

# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]

# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run “rake -D time” for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = ‘Central Time (US & Canada)’

# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join(‘my’, ‘locales’, ‘*.{rb,yml}’).to_s]
# config.i18n.default_locale = :de

# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = “utf-8”

# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]

# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true

# Use SQL instead of Active Record’s schema dumper when creating the database.
# This is necessary if your schema can’t be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql

# Enforce whitelist mode for mass assignment.
# This will create an empty whitelist of attributes available for mass-assignment for all models
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
# parameters by using an attr_accessible or attr_protected declaration.
config.active_record.whitelist_attributes = true

# Enable the asset pipeline
config.assets.enabled = true

# Version of your assets, change this if you want to expire all your assets
config.assets.version = ‘1.0’

#Required for Heroku or Cloud 66+DO deployment
config.assets.initialize_on_precompile = false

#config.serve_static_assets = false

#Prevent passwords from being written to logs
config.filter_parameters += [:password, :password_confirmation]
end
end

Please feel free to contact me with any questions about this process.

SXSW Interactive Badge For Sale

I purchased my SXSW Interactive badge in July last year but I will be unable to attend. I am willing to sell my SXSW Interactive 2013 badge for the price I paid plus the transfer fee. I paid $695 for the badge and the transfer fee is $125. I’ll sell the badge for a total of $820.

Feel free to ask me any questions regarding the badge in the comments below or on Twitter.

UPDATE: This badge has been sold!