What have you found for these years?

2008-09-16

modeling photos/albums

本來想仔細看一下 A Lambda Evaluator in Agda 的,不過沒什麼精神...
今天狀況一直很差,中午的時候更是如此。儘管如此,程式上倒是還算有進展。
趕快硬著頭皮把筆記寫完,就要去睡了...

(btw, 我句子裡的贅字真的很多,真是壞習慣)

*
原本的 model 就很複雜了,photos 和 albums 本身是多對多關係,
中間又有不少東西需要做 abstraction. 因此我拉了一個很蠢的東西:

ActsAsPhotosAlbums

這應該只是實作上的 abstraction, 沒什麼道理可言。
只是把一些 photos 和 albums 共有的東西拉出來一併處理。
像是 acts_as_commentable, 不過這個被我拆掉了,改成:


has_many :comments, :as => :commentable,
:dependent => :destroy, :order => 'created_at ASC'
attr_readonly :comments_count


理由是 acts_as_commentable 本身根本不夠複雜到需要額外獨立出來。
再加上我需要針對 class Comment 做一些修改,所以就把他吃掉了。

其他像是以下就不多提了:


belongs_to :user, :counter_cache => true
belongs_to :permission


比較特殊的大概是對 description 額外做了一層包裝吧:


module_eval <<-END_EVAL
def description
resource_description.ergo.text || ''
end

def description= text
if resource_description
resource_description.text = text
resource_description.save
else
desc = #{model}Description.new
desc.text = text
self.resource_description = desc # this cause save
end
description
end
END_EVAL


因為實際上 photo description 和 album description 還有一點小差異在。
這邊我有碰到一個比較大的麻煩是,我搞不清楚什麼時候會 "save".
有些情況下會自動儲存,有些又不會,有些叫他儲存實際上又沒儲存 =_=b

我也懶得去搞清楚儲存時機,所以後來常常都不寫 association 的 assignment,
直接用 ooo_id 改寫資料庫,省得還要判斷到底誰會存誰不會存。
這邊我想最好還是要搞清楚,不過有點懶,就暫時先放著...
反正有 bug 的話一試就知道了?

*

接下來要加上去的東西是 permission 機制,因為上面已經有點複雜了,
所以這部份花了不少時間在打草稿,才開始動手寫。大概用掉兩面白紙左右...

理想上大概是做到像這樣:


@photos = @album.photos.for_visitor(@visitor).paginate :per_page => 9


也就是說要透過 visitor 的權限來篩選 photos, 不能讓 visitor 越權看到不該看到的。
不過實際上可能做不到上面那個樣子,as a workaround, use:


@photos = @album.photos_for_visitor(@visitor).paginate :per_page => 9


差別只在把一個 . 換成 _

字元上差異不大,不過實際上意思倒是差不少。實際上背後還是用 named_scope 做的。


def photos_for_visitor visitor
photos.for_visitor(visitor, user)
end


也就是說,其實也可以用:


@album.photos.for_visitor(@visitor, @album.user).paginate :per_page => 9


因為 named_scope 本身是 class level 的 scope, 他沒有 id 的資訊。
而在這邊我們一定要把 user id 丟進去,才能拿來跟 visitor 比對兩者關係。
例如兩人是不是朋友,兩人是不是其實根本就是同一個人?

而這邊的 user 很顯然就是 album 的 owner, 問題是在 @album.photos 中的
association proxy 要怎麼取得 owner 資訊?我想 rails 內部應該有這種東西,
不過此時還會碰到另外一個問題,就是 named_scope 是 class level 的東西,
我要的資訊卻是 instance level. 這邊或許可以靠 association extensions
解決,不過目前不是很清楚,直接 wrap 一層比較快,就先這樣吧。

*

named_scope 就寫成:


named_scope :for_visitor, lambda{ |visitor, user|
{:conditions => "permissions.#{user.relationship(visitor)} != 'none'",
:include => :permission}
}


這邊或許應該用 Permission.table_name... 不過沒差啦。
所以就是在這邊取得兩個 user 資訊,再由 user 自己去判斷 relationship.
取出 relationship 後,直接在 permission 中搜尋該 relationship 的權限。

權限用 != 'none' 來判斷,因為不管是 read/write 都有 read 權限。
或許還會改,不過暫定是這樣。覺得比較討厭的是,我不熟 sql 的弱點就暴露出來了。
還是搞不清楚 join table 是什麼東西?:joins 和 :include 又有什麼不同?

原本打草稿時在這邊碰上很大的麻煩,就是我不知道 join table 是什麼?
想半天就覺得要達成這種 query, 很可能需要把所有 photos 都撈出來再一一判斷。
這樣 complexity 就會變成 n*n 之類的...

之前在撈出所有朋友的照片時就碰上這個麻煩。我還在想有沒有更好的作法?
除了 cache 以外...

但不知道為何,腦中忽然就浮現好像有 :include 和 :joins 可以用。
稍微試了一下,發現確實符合需求。應該啦,我只測了一筆,暫時還沒便繼續測。
總之這樣生出的 sql 雖然很複雜,但應該還是比 complexity n*n 要來得好?

*



最後再講解一下這個 permission 機制,想想也是滿複雜的。
首先 photo 和 album 都有一個 permission, 就像上面
ActsAsPhotosAlbums 裡面有列出來的:

belongs_to :permission

但是實際上這兩個 permission 所處的地位是完全不一樣的。
photo 的 permission 只是 cache, 但 album 的 permission 則不會檢查到。
請看 named_scope 的部份,那是寫在 photo 裡面的。所以 permission 是檢查
photo 的部份,不是 album, album 從頭到尾都不會檢查。

意思就是你看一個 public 相簿,裡面可能參雜 public, protected, private
等等相片... 權限檢查則是對每一張相片做檢查,不符合使用者權限的就不會顯示。

這意謂著,你很可能會以 friend 的身份,看到 public 相簿裡面有 protected 相片。
但是非 friend 者,看這 public 相簿則看不到該 protected 相片。

造成這個結果的原因是,你先開一個 public 相簿,然後裡面塞相片,全部是 public.
接下來,你再開一個 protected 相簿,然後把其中一張相片放進去。
此時,該相片的權限立刻會變成 protected! 這造成原本 public 相簿裡出現
protected 相片,而 guest 是看不到該相片的;friend 則仍然可以。

*

也就是說,photo 的權限實際上是所有所屬的 albums 裡中取最嚴格的權限。
而 photo 本身的權限,就是上面計算出來最嚴格的權限的 cache.
重新計算 cache 的時機,則是任何相片的任何移動,包括移出相簿,移除相簿。

所以上面再把該相片從 protected 相簿中移出時,他又會變回 public.

*

不要問我為什麼會變這麼複雜..........
原先的設計就是多對多關係,照著做下去很自然就變這樣了...

然後加密相簿又是一個特例,還要額外處理。

*

這邊做完之後,還有使用者的權限。像是 free account, paid account,
admin, etc... 除此之外還要有特殊相簿,拿來放暫存的圖片。
這應該算是我寫過最複雜的 web application 了...

0 retries:

Post a Comment

Note: Only a member of this blog may post a comment.



All texts are licensed under CC Attribution 3.0