__Các web developer thường xuyên gặp phải những tình huống mà họ cần lưu trữ một thứ gì đó, đặc biệt là lưu thứ gì đó dạng collection để sử dụng tạm thời, ví dụ một vài trang user đã vào xem trước đó. Tôi đã từng thấy nhiều người tạo hẳn một bảng trong CSDL chỉ để lưu những trang user đã ghé qua trong trường hợp không cần thiết, đó thật sự là một cách làm tệ hại khi mỗi lần truy cập vào 1 trang lại phải insert 1 record vào DB. Thậm chí có người sử dụng session để lưu, thật là một sự lãng phí.__ Vấn đề có thể được giải quyết với cookies. Trong bài viết này mình sẽ chỉ ra một cách để "che đậy" cookies và làm sao để tương tác với cookies như một collection một cách đơn giản nhất ## Ý tưởng Trước tiên, một đối tượng cookie collection sẽ phải làm được 2 công việc cơ bản là add new items và retrieve one/all item(s). Mình sẽ lấy ví dụ là một web bán hàng trực tuyến, cần lưu lại các 10 sản phẩm người dùng vừa ghé xem. Trong ví dụ này, ý tưởng của mình sẽ có 1 hàm lấy danh sách các sản phẩm vừa xem, và thêm sản phẩm mới mỗi khi gọi hàm `show` trong `products_controller`. ```ruby recent_products #=> Retrive all items. recent_products.push @product #=> Add new item ``` ## Sử dụng cookies để lưu và lấy collection Mình sẽ tránh việc gọi trực tiếp đến cookies hash, thay vào đó mình sẽ tương tác với 1 collection. Với lớp `CookieCollection`, chúng ta sẽ định nghĩa các phương thức để thêm/xóa item. Dĩ nhiên để làm việc này một cách dễ dàng thứ đầu tiên ta nên nghĩ đến đó là lớp `Array`, có nghĩa là chúng ta sẽ kế thừa `class Array` Ở đây để tối ưu về mặt hiệu suất, chúng ta chỉ lưu các id sản phẩm chứ không lưu cả object. Việc lưu và lấy các object sẽ do một lớp khác đảm nhiệm. ```ruby # lib/cookie_products/cookie_collection.rb class CookieCollection < Array attr_accessor :ids LIFE_TIME = 10 def initialize cookies @cookies = cookies if @cookies[cookie_name].present? self.ids = @cookies[cookie_name].split(',') else self.ids = Array.new end end def push object super object update_cookie end private def update_cookie ids = map(&:id) @cookies[cookie_name] = { value: ids.join(','), expires: LIFE_TIME.years.from_now } end def cookie_name self.class.name.parameterize end end ``` ## Xây dựng lớp lưu id sản phẩm các trang đã xem Lớp `RecentProducts` sẽ trực tiếp đẩy các sản phẩm vào cookie collection. Ở đây mình sẽ chỉ lưu 10 sản phẩm gần đây nhất. ```ruby # lib/cookie_products/recent_products.rb class RecentProducts < CookieCollection RECENT_PRODUCT_SIZE = 10 def initialize cookies super cookies self.ids = ids.last RECENT_PRODUCT_SIZE ids.each {|product_id| push Product.find(product_id)} end def push product delete product while length > RECENT_PRODUCT_SIZE - 1 delete_at 0 end super product end end ``` ## Thêm các hàm helpers Trong `application controller` mình sẽ tạo ra các helper methods để truy cập đến object collection, nhờ đó trên controllers và views có thể dễ dàng gọi các phương thức này. ```ruby # app/controllers/application_controller.rb helper_method [:recent_products, :last_viewed_product] def recent_products @recent_products ||= RecentProducts.new cookies end def last_viewed_product recent_products.reverse.second end ``` ## Sử dụng trên views và controllers Ví dụ về sử dụng trên `products controller` ```ruby # app/controllers/products_controller.rb def show @product = Product.find_by_id params[:id] recent_products.push @product #=> Đẩy 1 product vừa xem vào collection # Lấy toàn bộ danh sách recent_products end ``` ## Chú ý Đừng quên khai báo đường dẫn `eager load` đến file thư viện để rails load các lớp khi chạy ứng dụng, sau đó khởi động lại server. ```ruby # config/application.rb config.eager_load_paths << Rails.root.join("lib/cookie_products") ``` ## Kết luận Đây là cách làm khá đơn giản và hợp lý nhất trong trường hợp này. Cách làm có thể áp dụng tương tự với `session`, mình sẽ có một bài viết tương tự hướng dẫn trên làm việc trên session để quản lý giỏ hàng bằng session object collection trong thời gian tới.