Coffeescript: Fat arrow vs thin arrow

In any JavaScript function, the value of this is the object that the function is attached to. However, when you pass functions to other functions or reattach a function to another object, the value of this will change. Sometimes this is what you want, but often you would like to keep the original value of this.

For this purpose, CoffeeScript provides the =>, or fat arrow, which will define a function but at the same time capture the value of this, so that the function can be safely called in any context. This is especially useful when using callbacks, for instance in a jQuery event handler.

class Birthday
  prepare: (action) ->
    @action = action

  celebrate: ->
    @action()

class Person
  constructor: (name) ->
    @name = name
    @birthday = new Birthday
    @birthday.prepare () => "It's #{@name}'s birthday"

m = new Person "Giang"
console.log m.birthday.celebrate()

Notice that the prepare function on the birthday class takes an action function as an argument, to be called when the birthday occurs. Because we’re passing this function using the fat arrow, it will have its scope fixed to the Person object. This means we can still refer to the @name instance variable even though it doesn’t exist on the Birthday object that runs the function.

omniauth-facebook + rails 3 + authlogic debug

Personally I prefer Devise to Authlogic but recently I have to work with Authlogic on a legacy codebase. But I still like Devise because it’s up-to-date, clean, easier to use…

omniauth-facebook provides a cool way to login with FB – display a popup instead of redirecting users to FB login page then back to our site. Obviously this is the way we want :)

We won’t re-invent the wheel here, let’s follow Railcasts #360 to have omniauth-facebook integrated to your app. These are some issues you may encounter:

1.  Can’t log users in manually with Authlogic:

In some cases the user can’t be logged in. For me, the reasons were:

  • validations failed (lack/duplicated of email/username…): some FB users set their emails to private so ommiauth-facebook can’t get their emails, you have to set a fake email in order to pass the email validation, therefore the new user can be created in database and gets logged in.NOTES: the first time users login with their FB accounts, their accounts will be saved to database and they will be logged in automatically, we just have to manually log FB users in only when the FB accounts are already created in our database.
    user.email = auth.info.email || "fb_user_#{Time.now.hash.abs}@email.com"
  • password not present: Devise provides a method Devise.friendly_token ( detailed guide here ) to manually create a password  but Authlogic doesn’t. You have to provide password and password_confirmation manually, make sure they’re the same.
  • create a new UserSession in controller: to log user in manually, we do
    UserSession.create(my_user_object, true)

    I’m not sure why but user won’t get logged in if the above is called in controller, moved it to model works.

2.  Invalid credentials

you see something like this in the log console:

(facebook) Authentication failure! invalid_credentials: OmniAuth::Strategies::OAuth2::CallbackError, OmniAuth::Strategies::OAuth2::CallbackError

If you remove the JS code you won’t get this anymore, but the popup will be gone as well. It seems this is a bug of omniauth-facebook 1.4.1 (the newest version at the time of this writing), let’s use omniauth-facebook 1.4.0 instead, everything works just fine then.

3.  FB errors

You’ll may get some error messages while trying to login with FB, make sure in your FB app configuration, Sandbox Mode is set to Disabled.

Rails + oauth-plugin + mongodb part 2: Consumer

In previous post, we went through the process to create a Provider with Oauth-plugin that works with Mongodb using mongid as the driver. Here we will build the Consumer using mongoid too.

This Consumer app will run on port 4000 (rails s -p 4000) and connect with the Provider app which will be running on port 3000.

Step 1 – step 3

Exactly the same as in previous post for Provider

4. Generates things

rails g devise:install
rails g devise User
rails g controller Welcome index
rails g oath_consumer User
rm public/index.html

Add this to your User model:

references_many :consumer_tokens
index "consumer_tokens.token"

In app/model/consumer_token.rb, find the line reads

embedded_in :user, :inverse_of => :consumer_tokens

change it to

referenced_in :user, :inverse_of => :consumer_tokens

In oauth_consumers_controller.rb, comment out the line reads

before_filter :login_required, :only=>:index

Uncomment/add new line that reads

before_filter :authenticate_user!, :only=>:index

In oauth_consumers_controller.rb, make sure these methods are NOT commented out:

go_back, logged_in?, current_user=, deny_access!

Added this to User model:

references_one :test, :class_name => "TestToken", :dependent => :destroy

(TestToken is the model for the provider, we would have TwitterToken, FacebookToken…etc)

Create a model file named test_token.rb in app/models/ with the content:

class TestToken < ConsumerToken
  TEST_SETTINGS = {
    :site => 'http://localhost:3000', # this is the URL to provider app
    :request_token_path => '/oauth/request_token',
    :access_token_path => '/oauth/access_token',
    :authorize_path => '/oauth/authorize'
  }

  def self.consumer(options={})
    @consumer ||= OAuth::Consumer.new(credentials[:key], credentials[:secret], TEST_SETTINGS.merge(options))
  end
end

In config/routes.rb, add your root path root :to => "welcome#index"

5. Communicate with the Provider

Let’s start provider app on port 3000 and consumer app running on port 4000 (rails s -p 4000)

Navigate to http://localhost:3000/users/sign_up to register an account.

Navigate to http://localhost:3000/oauth_clients/ to register your app with these info:

Name: Test consumer
Main Application URL: http://localhost:4000/
Callback URL: http://localhost:4000/oauth_consumers/test/callback

You will be redirected to oauth_client show page with credentials (yours will be different)

Consumer Key: d8KBiaD98Mnp2vyB9A8ZSAT0vpKu5kdFtAXUsZup
Consumer Secret: UDdD5HAefrRZ1afguDy0WrTALYwZ8KXWKgLiSJCE
Request Token URL http://localhost:3000/oauth/request_token
Access Token URL http://localhost:3000/oauth/access_token
Authorize URL http://localhost:3000/oauth/authorize

In config/initializers/oauth_consumers.rb, add the credentials above. The content will look like:

OAUTH_CREDENTIALS = {
  :test => {
    :key => 'd8KBiaD98Mnp2vyB9A8ZSAT0vpKu5kdFtAXUsZup',
    :secret => 'UDdD5HAefrRZ1afguDy0WrTALYwZ8KXWKgLiSJCE',
    :expose => true
  }
}

Restart your Consumer app if it was running when you changed the content of this initializer.

Modify the content of Welcome#index to get the provider data:

class WelcomeController < ApplicationController
  def index
    @consumer_tokens = TestToken.where(:user_id => current_user.id)
    @token = @consumer_tokens.first.client
    logger.info 'private data' + @token.get('/data/index').body
  end
end

Go to http://localhost:4000/oauth_consumers to see all the services. Actually we have only 1 here, it’s the ‘test’ service which owned by ‘TestToken’ model.

Click on the service (here is test) then give it access

Go to http://localhost:4000 and you will see the data from provider printed out in log console

You can find the source code for Consumer here

Rails + oauth-plugin + mongodb part 1: Provider

Recently I had a chance to work with Oauth and Rails using oauth-plugin and mongodb. Although oauth-plugin itself has a documentation but actually doesn’t cover the mongoid part detailed enough. Also I found this very good tutorial but it’s for SQLite3 only: Oauth Provider, Oauth Consumer

Base on the above posts, to get Rails and oauth-plugin working with mongodb, you need to do some changes.

Firstly, create a oauth-sample directory, you’ll put provider and consumer apps in there.

1. Create new project

    • mkdir oauth
    • cd oauth
    • rails new provider
    • cd provider
    • add these gems to your Gemfile
gem 'rails', '3.0.12'
gem 'devise'
gem 'oauth-plugin', '~> 0.4.0'
gem 'mongoid' gem 'bson_ext'
  • bundle install`

Now you have:

  • an authentication system installed (Devise)
  • oauth-plugin which helps you a lot in generating files for Oauth
  • mongoid a driver for mongodb
  • bson_ext which mainly for mongo peformance improvement

2. Config the Rails app to use mongodb

  • delete config/database.yml
  • rails g mongoid:config
  • replace require 'rails/all' in config/application.rb with
require "actioncontroller/railtie"
require "actionmailer/railtie"
require "activeresource/railtie"
require "rails/testunit/railtie"
#require "sprockets/railtie" # Uncomment this line for Rails 3.1+

3. Config the generators

This step is important, you need to do this before generating/installing Devise model, Oauth things…

Add this code into your application.rb, uncomment haml/rspec if you use them. Keep the mongoid option as that is what we want.

config.generators do |g|
  g.orm :mongoid
  #g.template_engine :haml
  #g.test_framework :rspec
end

From now on, the generators know they should invoke mongoid instead of activerecord when generating things…

4. Generates things

rails g devise:install
rails g devise User
rails g controller Data index
rails g oath_provider
rm public/index.html

Add this into your User model:

references_many :client_applications
references_many :tokens, :class_name => "OauthToken", :order => "authorized_at desc"

Add this into your oauth_clients_controller.rb:

alias :login_required :authenticate_user!

In oauth_clients_controller.rb, find and replace the line reads

@tokens = current_user.tokens.find(:all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null')

with

@tokens = current_user.tokens.includes(:client_application).where('oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null')

In config/application.rb, add this code:

require 'oauth/rack/oauth_filter'
config.middleware.use OAuth::Rack::OAuthFilter

In app/controllers/oauth_controller.rb, add this code:

alias :logged_in? :user_signed_in?
alias :login_required :authenticate_user!

Modify the Data#index to add some data:

class DataController < ApplicationController
  before_filter :oauth_required

  def index
    @data = { "coincoin" => "o< o<" }
    respond_to do |format| 
      format.json { render :json => @data } 
    end
  end
end

In config/routes.rb, add your root path root :to => "data#index"

5. Explain :include issue

As mentioned in oauth-plugin guide we should use

has_many :tokens, :class_name => "OauthToken", :order => "authorized_at desc", :include => [:client_application] with the :include

Actually :include is for ActiveRecord eager-loading feature and it doesn’t work with Mongoid, so I thought I should find an equivalent of it in Mongoid.

After doing a research I found that Mongoid originally didn’t support eager-loading, then started supporting eager-loading from version 2.2.0

We can use this feature by using Model#includes method as guided here

So I think we should remove the :include => [:client_application] option in User model, then add .includes(:client_application) to the controller as above.

6. Issues with oauth-plugin + mongoid

You can make these below changes yourself or simply get from here. I folked from original repo and modified.

You will encounter these one when you trying to connect consumer with provider on step 5 in consumer guide.
- Dynamic finders don’t seem to work. You’ll get the exception with ‘findbykey’, we need to modify the oauth-plugin gem directly (as the author hasn’t provide a fix yet).
Do this commands:

bundle show oauth-plugin # to know where it was installed.
cd /path/to/oauth-plugin/installed
cd lib
grep -rn 'find_by_' .

Let’s change all the .find_by_<a_key>(<a_value>) with .where(:<a_key> => <a_value>).first

E.g:

#this one
@token = ::RequestToken.findbytoken! params[:oauth_token]
#should be changed to
@token = ::RequestToken.where(:token => params[:oauth_token]).first

- undefined method 'expand_complex_criteria' for #<Array:0x00000102934308>

In lib/oauth/rack/oauth_filter.rb of oauth-plugin gem, find the line (around line #27) which reads:

if token = Oauth2Token.first(:conditions => ['invalidated_at IS NULL AND authorized_at IS NOT NULL and token = ?', token_string])

change it to

if token = Oauth2Token.where(:invalidated_at => nil, :authorized_at.ne => nil, :token => token_string).first

Then find the line (around #45) which reads:

oauth_token = client_application.tokens.first(:conditions => { :token => request_proxy.token })

change it to

oauth_token = client_application.tokens.where(:token => request_proxy.token).first

And we’re done for Provider

You can get the source code for Provider here

BrowserCMS from development to production

There’re some Rails CMS such as Radiant, Refinery, BrowserCMS… and I find BrowserCMS the best one to build simple websites for my customers, we can easily create dynamic content for you websites with Portlet, create new object to manage by with Content type, and easily create template for each page (homepage, subpages…).

In this post we won’t talk about how to use BrowserCMS, please go to the BrowserCMS Wiki if you’re finding that information. This post just concentrates on things you need to do to get BrowserCMS work properly on server in production mode.

For me, I use a VPS with Rails 3, Ruby 1.9.2-p180 (with RVM installed), Rubygem 1.6.2, Nginx + Passenger.

I have it work well on my development machine, but on VPS under production mode, there are 2 problems:

  1. all stylesheets are NOT applied
  2. images can be uploaded but won’t show up both in back-end and front-end

the first one will be resolved if you read the Deployment Guide of BrowserCMS, you’ll some basic settings need for production mode and set the config.serve_static_assets to true:

# To send CMS files, the new Rails 3 default (using X-Sendfile) must be 
#commented out, or Apache mod_sendfile should be installed. 
# See http://codequest.eu/articles/rails3-apache-passenger-and-empty-file-using-send-file for details 
#
# config.action_dispatch.x_sendfile_header = "X-Sendfile"

# This has to be enabled so that core CMS assets (like internal buttons, css..)
# can be served from the bcms gem.
config.serve_static_assets = true

# This should match the URL for your public CMS site.
SITE_DOMAIN="www.sitedomain.com"

# You may need to edit your mail server address as well in the next line.
config.action_mailer.smtp_settings = {:address => 'mail.yourmailserver.com', 
                                      :domain => "#{SITE_DOMAIN}"}

The second one it’s because we’re using Nginx, we need to uncomment this line in config/initializers/browsercms.rb

# For nginx:
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

then images issue gone.

Really easy but it took me 2 hours to figure out, I had tried to check the chmod of uploaded files, check the location of uploaded files, check the database....

Hope this helps