論盡蛋
心で書く

Blog


Some useful Git commands

There are some useful git commands not so obvious :)

I assume the commits are not pushed to any remotes yet. IMHO, it is almost always a bad idea to edit published commits.

To amend the last commit's message

provided that the commit was not pushed to any remotes.

#!bash
git commit --amend -m "new message"

Restore a file's content back to a certain commit's version

#!bash
git checkout commit-sha-value file
# e.g.
git checkout f9d356a index.html

To track a remote branch

#!bash
git branch --set-upstream foo origin/foo

Don't forget to push tags

git push by default do not push tags! So remember to do this separately:

#!bash
git push --tags # you might need to specify the remote and branch where you want to push to

Whether you use --tags on git pull or git fetch depends. There are some subtle differences. You could read this stackoverflow post for more detail.


Setup the test environment

There are some gems for specific parts of testing.

factory_girl - for generating fixtures

rspec2 - for doing unit test, integration test, etc

capabara - for mimicking user's behaviour in order to do user acceptance test and improviding the integration test

spork - to fork a test environment before each run of the tests, for firing test cases much faster than the traditional way which loads the entire rails test environment before you could run the test cases

guard - for handling events on file modifications. It is configured that whenever a source code or a test case is updated, the corresponding test cases would be run automatically. Together with spork, test cases would be automatically run in a fast pace whenever a source code or a test case is updated so that we could get the test result in real time

simplecov - for code coverage report generation

Code

Add the following code to Gemfile:

#!ruby
group :test, :development do
  gem 'rspec-rails', '~> 2.6'
  gem 'factory_girl_rails'
end

group :test do
  gem 'spork', '~> 0.9.0.rc'

  gem 'guard-rspec'
  gem 'guard-spork'

  gem 'capybara'

  gem 'simplecov', :require => false
end

then run bundle or bundle install.

To init rspec:

#!bash
rails generate rspec:install
> create  .rspec
> create  spec
> create  spec/spec_helper.rb

To setup spork:

#!bash
spork --bootstrap

After that, some instructions are inserted into spec/spec_helper.rb automatically. Update the spec/spec_helper.rb file according to the instructions.

Now, it's the time to guard rspec.

#!bash
guard init rspec
> Writing new Guardfile to /Users/PeterWong/Projects/sobiwi/Guardfile
> rspec guard added to Guardfile, feel free to edit it

Then to guard spork:

#!bash
guard init spork
> spork guard added to Guardfile, feel free to edit it

Now we need to update the Guardfile to move the newly appended guard 'spork' block to the top of the guard 'rspec' block.

Also we need to update the Guardfile:

#!ruby
# change the following line
guard 'rspec', :version => 2 do
# to
guard 'rspec', :version => 2, :cli => '--drb' do

Now guard is working and to run test cases in real time, run the command guard.

To setup capybara, update spec/spec_helper.rb:

#!ruby
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rspec' # add this new line

finally to automatically reload factory_girl fixtures, add the following line inside the Spork.each_run block in the spec/spec_helper.rb:

#!ruby
FactoryGirl.reload

Now things are all done. We could do TDD too :)

Oh, forgot to setup the simeplecov. To set it up, insert the following line in the very beginning of the spec/spec_helper.rb file:

#!ruby
require 'simplecov'
... # rest of the original file content

and then create a .simplecov file in the root of the project having the following content:

#!ruby
SimpleCov.start 'rails' do
  add_filter 'spec'

  add_group 'Mailers', 'app/mailers'
end

It is to tell simplecov to ignore the code coverage of the spec directory (we do not test out test cases) and add a group named Mailers for the mailers in case we have mailers in use.

SimpleCov.start 'rails' will automatically group controllers, helpers, models, lib and plugins code and so those groups do not need to be added by ourself.

One more note, to obtain the coverage report, guard cannot be used. Instead we should run rspec directly:

#!bash
rspec .

to run all the spec to obtain the full coverage report (as the hits per line etc need to be calculated, we must ensure every test case is run once).


User authentication

There are a bunch of well developed user authentication gems available for rails. One of them is OmniAuth.

Starting from version 1.x, OmniAuth separated every strategies into separated gems. OmniAuth integrated many different authentication providers, such as Facebook, Twitter, OpenId, in order to provide a standardized interface.

Each provider is a so-called strategy. Recently there is a omniauth-identity gem to due with the traditional username password authentication instead of using external providers.

In short, making use of OmniAuth, we could provide username password authentication with the omniauth-identity, while having an advantage of integrate with other external providers relatively easier.

Code

I am going to follow the episode #304 OmniAuth Identity with little modifications to suit our need.

1) add the following to the Gemfile and run bundle

#!ruby
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'omniauth-identity'

2) create a config/initializers/omniauth.rb file with the following content:

#!ruby
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :identity
end

3) create the sessions_controller for handling sign in / out for omniauth

Run the following command: rails generate controller sessions.

Edit the generated app/controllers/sessions_controller.rb file as:

#!ruby
class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.from_omniauth(env["omniauth.auth"])
    session[:user_id] = user.id
    redirect_to root_url, notice: "Signed in!";
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url, notice: "Signed out!";
  end

  def failure
    redirect_to root_url, alert: "Authentication failed, please try again.";
  end
end

Now you could see there are something we do not have right now: User model and root_url

4) create the User model

By using the following command:

#!bash
rails generate model user provider:string uid:string display_name:string # any other fields you might want

followed by rake db:migrate

and then update the generated app/models/user.rb as:

#!ruby
class User < ActiveRecord::Base
  def self.from_omniauth(auth)
    find_by_provider_and_uid(auth["provider"], auth["uid"]) || create_with_omniauth(auth)
  end

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

Now the User model part is ready. It's time to the identity part. In omniauth-identity, there is another model for handling the authentication instead of the user model (of course you may map the model in the config...).

5) create the Identity model

By using the following command:

#!bash
rails generate model identity name:string email:string password_digest:string

followed by rake db:migrate

Now you need to update the generated app/models/identity.rb as:

#!ruby
class Identity < OmniAuth::Identity::Models::ActiveRecord
  # anything else you want
end

6) add the following two paths to suitable position in your view file:

6.1) create identity path: /auth/identity/register
6.2) login path: /auth/identity

The above 2 links are linked to the default registration and login pages. However, these 2 default pages does not match our design as well as there are no error handling. So we need to provide validations to Identity model and also provide the registration and login pages.

7) insert validation rules to the identity model (app/models/identity.rb):

#!ruby
validates :name, presence: true
validates :email, uniqueness: true, format: /^[^@ ]+@([-a-z0-9]+.)+[a-z0-9]{2

We do not need to add presence: true to the email validation as the format do not allow blank input.

8 ) points for the login form

I am not going to paste my full code in both the login form as well as the registration form as it would be too long.

Instead, I write down the things we need to notice here.

8.1) you should be using form_tag and must post to /auth/identity/callback
8.2) these two fields should present: auth_key (for email) and password
8.3) the above 2 keys should be in top level in params.

For example <%= text_field_tag :auth_key %>
Instead of <%= text_field_tag :login[auth_key] %>

If you actually using form_for, You could do: <%= f.text_field :auth_key, name: "auth_key" %>

8.4) I would recommend to put the form inside the sessions#new view. This view file in the future will also provide other external providers' login.

That's all for login form :)

9) points for the registration form

9.1) you should create a identities_controller and using at least the new action. (no need to use create action as the registration form is actually passed to omniauth for the standardized approach).
9.2) put the needed route into the routes.rb file. eg. resources :identities, only: [:new]
9.3) you should again be using form_tag and must post to /auth/identity/register
9.4) by default, only the following fields will be handled: name, email, password, password_confirmation.
password and password_confirmation must be handle, while name and email could be set in the configuration. To change name, email or to add more other fields, you should update the following line in the config/initializers/omniauth.rb as the next code block.
9.5) you need also to make omniauth-identity to redirect back to the identities#new, see the next code block too (config/initializers/omniauth.rb).

#!ruby
provider :identity, :fields => [:name, :email],
  on_failed_registration: lambda { |env|
  IdentitiesController.action(:new).call(env)
}

9.6) define the identities#new action as follow (app/controllers/identities_controller.rb):

#!ruby
def new
  @identity = env['omniauth.identity']
end

10) add the required routes to config/routes.rb

#!ruby
get '/login' => 'sessions#new', as: :login
match '/auth/:provider/callback', to: 'sessions#create'
match '/auth/failure', to: 'sessions#failure'
match '/logout', to: 'sessions#destroy', :as => :logout

resources :identities, only: [:new] # as well as the route for the registration form

All things should be done :)


久違了的後台工作~

好鄰舍家家顯關懷嘉禮暨全區關懷大行動

Audio Panel


清除行末空格

前言

最近有一份 project 是要寫一個compiler,將自己的語言編譯成 professor 提供的一種類 assembly language。

不知是否處女座的關係,自己有個習慣是不可以讓行末出現空格的!每次看到就會忍不住要刪除。

但因為這是 group project (共二人 :)),而一些基本的 source code 是 course 提供的,所以有不少地方都有行末空格出現...

所以特意寫一寫如何簡單地用 Textmate 來清除行末空格。

準備

  • Source Code File
  • Textmate (或其他支援 search by regular expression 的 text editor)

做法

在 Textmate 中,可按 cmd + shift + F 來作 global search。

在 Find 中輸入 [ ]+$,保留 Replace 空白,tick Regular expression

然後便可以 search & replace 了。

說明

現在來說說 [ ]+$

首先是 [ ]:要留意 [ 和 之間要有一個空格。他的意思是找一個空格或一個 tab。

然後是 [ ]+:前面是找一個,加上 + 後就變成找一個或以上了。

最後是 [ ]+$:$在最後出現,代表行末。所以要找的,就是緊接在行末之前的一個或以上的空格或 tab。