Your posts match “ rails ” tag:

cancan 筆記

Cancan 筆記

簡介

cancan 是一套 Rails 的權限設定 Gem ,最常見用的地方就是我們需要針對使用者或者管理者進行權限控管的時候我們可以針對該 user 的 role 來判定它能夠有什麼樣的權限。

以往常見的做法是:

Helper:

def editable?(user)
  if user.has_role? :admin
    link_to("編輯", edit) + link_to("刪除", destroy)
  elsif user.has_role? :member
    link_to("編輯", edit)
  else
    ""
  end
end

View:

<%= editable?(user) %>

但是用了 cancan 之後我們可以用

Ability.rb

def intialize(user)
  
  if user.blank?
    basic
  elsif user.has_role? :admin
    admin_only
  elsif user.has_role? :member
    members_only
  else
    basic
  end
end

def basic
  can :read, [ Post, Comment ]
end

def members_only
  can [ :create, :update ], [ Post, Comment ]
end

def admin_only
  can :manage, :all
end

如此一來,我們就可以在 view 裡面這樣做

<% if can? :create, Post %>
  <%= link_to("新增", new_post_path) %>
<% end %>

<% if can? :manage, Post %>
  <%= link_to("文章管理", admin_posts_path) %>
<% end %>

這主要是 cancan 可以用的情境。

Before we start

我想,各位可以先去閱讀 xdite 寫的三篇 cancan 介紹文章:

  1. Cancan 實作角色權限設計的最佳實踐(1)
  2. Cancan 實作角色權限設計的最佳實踐(2)
  3. Cancan 實作角色權限設計的最佳實踐(2)

看完之後其實你就會用 cancan 了 … XD

但是我在看完之後其實還是有點一知半解,在 Custom Action 的時候不知道該怎樣設定自訂的 action ,所以我讀了一下文件後知道了一件事:讀官方文件很重要啊!!!

自訂 Action

廢話不多說,假設我們想要做一個 like 的動作,我們可以這樣做:

can :like, Post

然後在前端這樣寫:

<%= link_to_if(can?(:like, Post), "Like (Y)", like_post_path(post)) %>

其實就只是這樣而已 …

現在正在研究 nested resources 的部分。

Rails 4 + Compass + Susy + Sassy Buttons

做個筆記 ...

在 Rails 4 之後好一大段時間, compass-rails 整個是爛的,爛到深處無怨尤 ... 後來在最近 compass-rails 推出 1.1.2 版之後才好了起來,今天是 2014-01-22 ,我安裝了 1.1.3 版。

步驟

  1. Gemfile 內加上

    gem 'compass-rails', '~> 1.1.3'
    gem 'susy'
    gem 'sassy-buttons'
    
  2. bundle install

  3. touch tmp/restart.txt

  4. 好了,這樣就好了 ...

compass-rails Github README 上提到

if you are using the rails configuration files you should add:
config.compass.require "susy"
to your application.rb configuration file.

這段可以忽略,你只要到需要用到 susy 的檔案內引入 @import "susy.base"; 就可以了

然後也可以自己建立一個 _susy.base.css.scss 然後檔案內容:

@import "susy";
@import "sassy-buttons";

$total-columns: 12;
$column-width: 4em;
$gutter-width: 1em;
$grid-padding: $gutter-width;

就可以設定好了,不需要另外加上什麼奇怪的設定。

這樣應該就可以 work 了 ... 搞好久 @@

Rails 4 + SettingsLogic + Googl 製作短網址功能

Why?

啊就因為專案需要啊,不然咧 ... XD

How?

請仔細看本教學

Okay, first?

各位可以到 https://github.com/hechien/example-short-url-rails clone 這個專案下來看看 :)

Requirements

我們會需要以上三個 Gem 套件,所以請在 Gemfile 中加上

gem 'settingslogic'
gem 'googl'

Okay, let's go!

第一次

在這個 Project 中我會建立一個 Article 來存文章,然後在儲存之後把文章網址轉為短網址。

打開 app/controllers/articles_controller.rb 然後在建立文章之後寫上

url = Googl.shorten(article_url(@article)).short_url
@article.update({ short_url: url })

Refactor 1

這樣就能夠把短網址弄出來了。

不過這樣寫其實挺亂的,所以我們拆出來另外寫。

def create
  respond_to do |format|
    if save_article
      format.html { redirect_to @article, notice: 'Article was successfully created.' }
      format.json { render action: 'show', status: :created, location: @article }
    else
      format.html { render action: 'new' }
      format.json { render json: @article.errors, status: :unprocessable_entity }
    end
  end
end

private

def save_article
    @article = Article.create(article_params)
  if @article.persisted?
    @article.update({ :short_url => Googl.shorten(article_url(@article)).short_url })
    return true
  end
  false
end

這樣的確乾淨多了,不過如果想要有一個監控 URL 點擊的次數的話可能就得登入自己的 Google 帳號,那這時候就得傳入自己的帳號密碼進去了,這邊我的資料是

(當然是假的,不必想登入)

加上帳密的寫法

client = Googl.client('foobar@gmail.com', 'foofoofoobarbarbar')
@article.update({ :short_url => client.shorten(article_url(@article)).short_url })

這樣一來就能使用登入過的帳號記錄了。

Refactor 2

可是,這樣子大家會看到帳號密碼啊 ... 安全性瞬間將到最低點!所以我們可以透過 SettingsLogic 來協助處理這件事。

建立 SettingsLogic model

app/models/ 底下建立一個 settings.rb

# app/models/settings.rb

class Settings < Settingslogic
  source "#{Rails.root}/config/application.yml"
  namespace Rails.env
end

建立 application.yml

然後,到 /config/ 底下建立 application.yml 這個檔案,並且輸入以下內容 (其實你可以用貼的)

# config/application.yml

defaults: &defaults

  googl:

    username: "foobar@gmail.com"

    password: "foofoofoobarbarbar"



development:

  <<: *defaults



test:

  <<: *defaults



production:

  <<: *defaults

存檔離開

修改 Googl 的設定方式

client = Googl.client(Settings.googl.username, Settings.googl.password)

好了,搞定,然後記得要在 .gitignore 裡面加上 /config/application.yml

蝦咪?你要把 application.yml 存到 Git 內?

嗯 ... 那還有一招,不過我們先把 /config/application.yml.gitignore 裡面移除吧!

然後,打開你的 application.yml 後,改為以下內容:

defaults: &defaults

  googl:

    username: <%= ENV["GOOGL_USERNAME"] %>

    password: <%= ENV["GOOGL_PASSWORD"] %>


development:

  <<: *defaults



test:

  <<: *defaults



production:

  <<: *defaults

然後再去設定環境變數,把帳號密碼設定進去就好了。

** UPDATED **

Refactor 3 - Service Object

xdite 建議拆成 Service Object 來處理,我就練習 & 筆記一下 XD

Steps

  1. app 資料夾底下建立一個 services 資料夾
  2. 新增 services/shorten_url.rb
  3. 新增底下的程式碼
    class ShortenUrl
    def initialize @client = Googl.client(Settings.googl.username, Settings.googl.password) end

    def shorten(url)
    @client.shorten(url).short_url
    end
    end


    1. 修改 app/controllers/articles_controller.rb
      def create
      @article = Article.new(article_params)
      respond_to do |format|
      if @article.save
        @article.update({ :short_url => ShortenUrl.instance.shorten(article_url(article)) })
      end
      end
      end
      
    2. 刪除掉最底下的 save_article

    我後來的版本還有替 ShortenUrl 加上 Singleton 功能,這個部分加不加就見仁見智了。

Rails with Time zone 筆記

做一個國際型的社群網站多少都會碰到時區問題,而在 Rails 裡面這部分算是還蠻容易就可以處理的了。

取得日期與時間

在 Rails 中,通常會透過以下幾種敘述得到日期或者日期時間

Time.now
Date.today

但是這種方式取得的時間都是以「執行端」為主的時間,抓的基本上就是系統時間,所以一般在台灣的主機跑的都是 +08:00 ,假設有日本、韓國或者夏威夷等地方的使用者想要取得他們當地的時間的話用這種方式絕對會出問題。

所以就必須用另外一種方式來取得時間,就是:

Time.current
Date.current

如此一來抓到的時間點才會是根據不同時區而做過調整的。然而,有一些 API 本身就會跟著時區設定跑,像是:

1.days.ago
10.days.from_now

諸如此類這種的就會根據 Rails 的 time zone 設定而決定當前的時區,設定的方式就是在 config/application.rb 中編輯 config.time_zone = 'Taipei' 就可以設定台灣時區。

Rails 支援很多的時區設定,但是都是根據名稱為主,而想要有詳細的列表可以在命令列(終端機)執行 rake time:zones:all 檢查。

rake time:zones:all

然而有的時候可能會需要過濾一下一些時區,或者自定時差來列表,這時候可以用類似以下方式取得:

rake time:zones:us # 取得美國地區所有的可設定值 (美國跨好幾個時區)
rake time:zones:local # 取得你當前時區的所有可設定值
rake time:zones:all OFFSET=-3 # 取得時區偏移量為 -3 小時的

在 Controller 該怎麼設定

所以,當我們希望加上自訂時區設定的時候,會希望可以自動生效時區設定,這時候我們就可以用 around_action (Rails 4 之前的版本是 around_filter) 來做設定。

class ApplicationController < ActionController::Base
  around_filter :setup_timezone, :if => :current_user?
  
  def setup_timezone(&block)
    Time.use_zone(current_user.time_zone, &block)
  end
end

所以,要記得替你的 User model 加上 time_zone 這個欄位。

參考文章

Mina 初體驗

從以前到現在 capistrano 一直都是我用來 deploy Rails application 的首選,原因是社群都這麼做所以我也就這麼做了,在那個只要下 cap deploy 就能把放在 git repo 上的 source code 變成能夠運作的網站,那一份感動真的是無法言喻。

然而, capistrano 的設定真的是太難搞了 ... 常常都讓我要搞上接近一天的時間才可以正常的把東西給 deploy 上去 (而且還有爛掉的可能),所以在不久之前我發現到了 Capistrano 3 的存在,一接觸我就愛上第二版的 Capistrano 了 ...

新的 DSL 設定、更簡單的方式就可以把網站部署起來 ... 真的感覺到 Capistrano 3 真的是 Good Job 啊!

然而就在昨天晚上, Ga Dii 鼓吹我嘗試 Mina 這一套很年輕的 deploy 工具後,我發現我似乎回不去 Capistrano 的懷抱了 ...

安裝

gem i mina

初始化

mina init

指令基本上沒太多差別,只是原本在 Capistrano 用 capify 的改用 mina init 而已,會產生一個在 config 資料夾下的 deploy.rb

設定

Mina 的設定檔感覺起來更容易理解,然後記得如果你有用 rvm 或 rbenv 的話要把 require 'mina/rvm'require 'mina/rbenv' 給取消註解,然後如果是 rvm 的話還要在

task :environment do
  # If you're using rbenv, use this to load the rbenv environment.

  # Be sure to commit your .rbenv-version to your repository.

  # invoke :'rbenv:load'


  # For those using RVM, use this to load an RVM version@gemset.

  invoke :'rvm:use[ruby-2.1.0@default]'
end

指定你的 gemset ,不然會噴錯誤。

自己的 task

不明原因讓我沒辦法在讓 mina 部署的過程中讀取到 env variables ,所以參考了一些網路資料得到這樣的做法:

task :env do                                                                                                                                                                                                
  queue %{
    echo "-----> Loading environment"
    #{echo_cmd %[source ~/.bash_profile]}
  }
end

task :deploy => :environment do
  invoke :env
  
  deploy do
    # 預設的 code

  end
end

如此一來就能夠在 deploy 之前先讀取環境設定檔

deploy

mina deploy

好處?

說真的,速度我覺得應該快上兩倍有 ... 傳說是因為 mina 只用了一條 ssh 連線的關係,但是實際上是怎樣我就不太清楚了。

缺點?

現在比較麻煩的是還沒有像是 unicorn 的套件可以使用,所以啟動伺服器我是手動連進去執行 bundle exec unicorn_rails -E production -c config/unicorn/production.rb -D 的方式執行,之後可能自己寫一個 mina/unicorn 來用。

參考資料

nginx 筆記

部署好後我忘記在 nginx 設定檔內關於 assets 路徑中設定 root ,所以變成會找不到 css / javascript files ,只要有設定好 root 是在 /home/deploy/xxx/current/public 的話就可以解決此問題

Rails Asset Gem

今天心血來潮想說來練習怎樣替自己寫的 CSS / JavaScript 包成 Gem ,上網後找到了 xdite 寫的這兩篇文與範例:

  1. 如何打包一個 Asset Gem
  2. 如何打包一個 Gem
  3. ggs-rails 範例

這三個看完其實就會用了,但是我來做一個步驟敘述好了

  1. 先執行 bundle gem PROJECT_NAME ,這個 PROJECT_NAME 請自己取,我在範例中使用的是 HeChienCss
  2. 進入到產生出來的 HeChienCss 資料夾內,然後新增兩個資料夾: vendor/assets/javascriptsvendor/assets/stylesheets。不過如果你只需要 JavaScript 就不需要新增 stylesheets 資料夾,反之亦然。
  3. lib/PROJECT_NAME 資料夾底下新增 engine.rbrailtie.rb 兩個檔案,內容如下述
  4. 編輯 lib/PROJECT_NAME.rb ,內容如下述
  5. 把你要包起來的 CSS / JavaScript 扔到 vendor/assets 資料夾下相對應的目錄,就像你在寫 Rails project 時那樣。
  6. 編輯 PROJECT_NAME.gemspec ,把一些資料改一改,拿掉 TODO, FIXME 字樣
  7. 如果是需要安裝其他 Gem 的話,請編輯 Gemfile 並且寫進去,譬如如果需要 compass 的話就在 Gemfile 內寫 gem 'compass'
# engine.rb

module PROJECT_NAME
  module Rails
    class Engine < ::Rails::Engine
    end
  end
end
# railtie.rb

module PROJECT_NAME
  module Rails
    class Railtie < ::Rails::Railtie; end
  end
end
# PROJECT_NAME.rb


require "PROJECT_NAME/version"

module PROJECT_NAME
  module Rails
    if ::Rails.version < "3.1"
      require "PROJECT_NAME/railtie"
    else
      require "PROJECT_NAME/engine"
    end
  end
end

這樣一來就可以執行 rake build ,然後開一個新的 Rails Project (或用原有的也可以) ,然後在該 Project 內編輯 Gemfile ,寫上 gem 'xxxx', :path => "/path/to/your/asset/project" 後執行 bundle 應該就可以正常了。

如果執行 rake build 會噴 Error 是跟 FIXMETODO 有關的話可以看看這篇底下的解法(其實很簡單就是了)