Skip to content
Browse files

first iteration

  • Loading branch information...
1 parent 04f2a39 commit 40c28c3303b9521c463450972c7095b8e1452e04 @tomcz tomcz committed Mar 10, 2010
Showing with 10,194 additions and 0 deletions.
  1. +6 −0 .gitignore
  2. +9 −0 README
  3. +10 −0 Rakefile
  4. +29 −0 app/controllers/admin_controller.rb
  5. +22 −0 app/controllers/application_controller.rb
  6. +27 −0 app/controllers/orders_controller.rb
  7. +131 −0 app/controllers/products_controller.rb
  8. +85 −0 app/controllers/store_controller.rb
  9. +137 −0 app/controllers/users_controller.rb
  10. +2 −0 app/helpers/admin_helper.rb
  11. +3 −0 app/helpers/application_helper.rb
  12. +7 −0 app/helpers/orders_helper.rb
  13. +2 −0 app/helpers/products_helper.rb
  14. +10 −0 app/helpers/store_helper.rb
  15. +2 −0 app/helpers/users_helper.rb
  16. +28 −0 app/models/cart.rb
  17. +21 −0 app/models/cart_item.rb
  18. +28 −0 app/models/conversation.rb
  19. +17 −0 app/models/line_item.rb
  20. +32 −0 app/models/order.rb
  21. +26 −0 app/models/product.rb
  22. +59 −0 app/models/user.rb
  23. +5 −0 app/views/admin/index.html.haml
  24. +12 −0 app/views/admin/login.html.haml
  25. +27 −0 app/views/layouts/admin.html.haml
  26. +27 −0 app/views/layouts/store.html.haml
  27. +21 −0 app/views/orders/index.html.haml
  28. +36 −0 app/views/orders/show.html.haml
  29. +28 −0 app/views/products/edit.html.haml
  30. +21 −0 app/views/products/index.html.haml
  31. +26 −0 app/views/products/new.html.haml
  32. +20 −0 app/views/products/show.html.haml
  33. +10 −0 app/views/store/_cart.html.haml
  34. +4 −0 app/views/store/_cart_item.html.haml
  35. +4 −0 app/views/store/add_to_cart.js.rjs
  36. +18 −0 app/views/store/checkout.html.haml
  37. +11 −0 app/views/store/index.html.haml
  38. +19 −0 app/views/users/edit.html.haml
  39. +15 −0 app/views/users/index.html.haml
  40. +20 −0 app/views/users/new.html.haml
  41. +8 −0 app/views/users/show.html.haml
  42. +110 −0 config/boot.rb
  43. +22 −0 config/database.yml
  44. +41 −0 config/environment.rb
  45. +17 −0 config/environments/development.rb
  46. +28 −0 config/environments/production.rb
  47. +28 −0 config/environments/test.rb
  48. +7 −0 config/initializers/backtrace_silencers.rb
  49. +10 −0 config/initializers/inflections.rb
  50. +5 −0 config/initializers/mime_types.rb
  51. +21 −0 config/initializers/new_rails_defaults.rb
  52. +15 −0 config/initializers/session_store.rb
  53. +5 −0 config/locales/en.yml
  54. +50 −0 config/routes.rb
  55. +1 −0 db/.gitignore
  56. +15 −0 db/migrate/20100308002138_create_products.rb
  57. +9 −0 db/migrate/20100308005313_add_price_to_product.rb
  58. +61 −0 db/migrate/20100308051101_add_test_data.rb
  59. +16 −0 db/migrate/20100308105046_create_sessions.rb
  60. +18 −0 db/migrate/20100308122259_create_orders.rb
  61. +18 −0 db/migrate/20100308122348_create_line_items.rb
  62. +18 −0 db/migrate/20100308220639_create_conversations.rb
  63. +17 −0 db/migrate/20100309042714_create_users.rb
  64. +74 −0 db/schema.rb
  65. +7 −0 db/seeds.rb
  66. +2 −0 doc/README_FOR_APP
  67. +7 −0 lib/uuid_helper.rb
  68. 0 log/.gitignore
  69. +30 −0 public/404.html
  70. +30 −0 public/422.html
  71. +30 −0 public/500.html
  72. 0 public/favicon.ico
  73. BIN public/images/auto.jpg
  74. BIN public/images/logo.png
  75. BIN public/images/rails.png
  76. BIN public/images/svn.jpg
  77. BIN public/images/utc.jpg
  78. +275 −0 public/index.html
  79. +2 −0 public/javascripts/application.js
  80. +963 −0 public/javascripts/controls.js
  81. +973 −0 public/javascripts/dragdrop.js
  82. +1,128 −0 public/javascripts/effects.js
  83. +4,320 −0 public/javascripts/prototype.js
  84. +5 −0 public/robots.txt
  85. +180 −0 public/stylesheets/depot.css
  86. +230 −0 public/stylesheets/sass/depot.sass
  87. +56 −0 public/stylesheets/scaffold.css
  88. +4 −0 script/about
  89. +3 −0 script/console
  90. +3 −0 script/dbconsole
  91. +3 −0 script/destroy
  92. +3 −0 script/generate
  93. +3 −0 script/performance/benchmarker
  94. +3 −0 script/performance/profiler
  95. +3 −0 script/plugin
  96. +3 −0 script/runner
  97. +3 −0 script/server
  98. +9 −0 test/fixtures/conversations.yml
  99. +13 −0 test/fixtures/line_items.yml
  100. +13 −0 test/fixtures/orders.yml
  101. +11 −0 test/fixtures/products.yml
  102. +11 −0 test/fixtures/users.yml
  103. +8 −0 test/functional/admin_controller_test.rb
  104. +8 −0 test/functional/orders_controller_test.rb
  105. +45 −0 test/functional/products_controller_test.rb
  106. +8 −0 test/functional/store_controller_test.rb
  107. +45 −0 test/functional/users_controller_test.rb
  108. +9 −0 test/performance/browsing_test.rb
  109. +38 −0 test/test_helper.rb
  110. +8 −0 test/unit/conversation_test.rb
  111. +4 −0 test/unit/helpers/admin_helper_test.rb
  112. +4 −0 test/unit/helpers/orders_helper_test.rb
  113. +4 −0 test/unit/helpers/products_helper_test.rb
  114. +4 −0 test/unit/helpers/store_helper_test.rb
  115. +4 −0 test/unit/helpers/users_helper_test.rb
  116. +8 −0 test/unit/line_item_test.rb
  117. +8 −0 test/unit/order_test.rb
  118. +8 −0 test/unit/product_test.rb
  119. +8 −0 test/unit/user_test.rb
  120. +16 −0 vendor/plugins/haml/init.rb
View
6 .gitignore
@@ -0,0 +1,6 @@
+log/*.log
+tmp/**/*
+doc/api
+doc/app
+.idea
+.DS_Store
View
9 README
@@ -0,0 +1,9 @@
+This project contains the depot application as described in the Agile Development with Rails book from the
+Pragmatic Programmers (http://pragprog.com/titles/rails3/agile-web-development-with-rails-third-edition).
+
+It has been modified to use HAML (http://haml-lang.com/) & SASS (http://sass-lang.com/) for HTML & CSS generation.
+
+In addition the user, product and store controllers have been modified to use the POST-REDIRECT-GET web application
+design pattern (http://en.wikipedia.org/wiki/Post/Redirect/Get) as an exercise to see what it takes to make
+active-resource controllers behave nicely with browsers.
+
View
10 Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
View
29 app/controllers/admin_controller.rb
@@ -0,0 +1,29 @@
+class AdminController < ApplicationController
+
+ layout 'admin'
+
+ def login
+ if request.post?
+ user = User.authenticate(params[:name], params[:password])
+ if user
+ session[:user_id] = user.id
+ uri = session[:original_uri]
+ session[:original_uri] = nil
+ redirect_to(uri || {:action => "index"})
+ else
+ flash.now[:notice] = "Invalid user/password combination"
+ end
+ end
+ end
+
+ def logout
+ session[:user_id] = nil
+ flash[:notice] = "Logged out"
+ redirect_to(:action => "login")
+ end
+
+ def index
+ @total_orders = Order.count
+ end
+
+end
View
22 app/controllers/application_controller.rb
@@ -0,0 +1,22 @@
+# Filters added to this controller apply to all controllers in the application.
+# Likewise, all the methods added will be available for all controllers.
+
+class ApplicationController < ActionController::Base
+
+ before_filter :authorize, :except => :login
+ helper :all # include all helpers, all the time
+ protect_from_forgery # See ActionController::RequestForgeryProtection for details
+
+ # Scrub sensitive parameters from your log
+ # filter_parameter_logging :password
+
+ protected
+
+ def authorize
+ unless User.find_by_uuid(session[:user_id])
+ session[:original_uri] = request.request_uri
+ redirect_to :controller => 'admin', :action => 'login'
+ end
+ end
+
+end
View
27 app/controllers/orders_controller.rb
@@ -0,0 +1,27 @@
+class OrdersController < ApplicationController
+
+ layout 'admin'
+
+ # GET /orders
+ # GET /orders.xml
+ def index
+ @orders = Order.find(:all, :order => 'created_at DESC')
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @orders }
+ end
+ end
+
+ # GET /orders/1
+ # GET /orders/1.xml
+ def show
+ @order = Order.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @order }
+ end
+ end
+
+end
View
131 app/controllers/products_controller.rb
@@ -0,0 +1,131 @@
+class ProductsController < ApplicationController
+
+ layout 'admin'
+
+ # GET /products
+ # GET /products.xml
+ def index
+ @products = Product.all
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @products }
+ end
+ end
+
+ # GET /products/1
+ # GET /products/1.xml
+ def show
+ @product = Product.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @product }
+ end
+ end
+
+ # GET /products/new
+ # GET /products/new.xml
+ def new
+ respond_to do |format|
+ format.html { redirect_to :action => 'form', :id => Conversation::NEW_ID }
+ format.xml { render :xml => Product.new }
+ end
+ end
+
+ # GET /products/1/edit
+ def edit
+ product = Product.find(params[:id])
+
+ conversation = Conversation.new
+ conversation.ref_id = product.id
+ conversation.context = 'product'
+ conversation.save!
+
+ redirect_to :action => 'form', :id => conversation
+ end
+
+ def form
+ @conversation_id = params[:id]
+ conversation = Conversation.get_or_create(@conversation_id)
+ if conversation.ref_id
+ @product = Product.find(conversation.ref_id)
+ else
+ @product = Product.new
+ end
+ if conversation.parameters
+ @product.attributes = conversation.parameters
+ @product.valid?
+ end
+ if conversation.ref_id
+ render :action => "edit"
+ else
+ render :action => "new"
+ end
+ end
+
+ # POST /products
+ # POST /products.xml
+ def create
+ product = Product.new(params[:product])
+
+ respond_to do |format|
+ if product.save
+ flash[:notice] = 'Product was successfully created.'
+ format.html {
+ Conversation.destroy_if_exists(params[:conversation_id])
+ redirect_to(product)
+ }
+ format.xml { render :xml => product, :status => :created, :location => product }
+ else
+ format.html {
+ conversation = Conversation.get_or_create(params[:conversation_id])
+ conversation.parameters = params[:product]
+ conversation.context = 'product'
+ conversation.save!
+ redirect_to :action => 'form', :id => conversation
+ }
+ format.xml { render :xml => product.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /products/1
+ # PUT /products/1.xml
+ def update
+ product = Product.find(params[:id])
+
+ respond_to do |format|
+ if product.update_attributes(params[:product])
+ flash[:notice] = 'Product was successfully updated.'
+ format.html {
+ Conversation.destroy_if_exists(params[:conversation_id])
+ redirect_to(product)
+ }
+ format.xml { head :ok }
+ else
+ format.html {
+ conversation = Conversation.get_or_create(params[:conversation_id])
+ conversation.parameters = params[:product]
+ conversation.ref_id = product.id
+ conversation.context = 'product'
+ conversation.save!
+ redirect_to :action => 'form', :id => conversation
+ }
+ format.xml { render :xml => product.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /products/1
+ # DELETE /products/1.xml
+ def destroy
+ Product.destroy(params[:id])
+
+ respond_to do |format|
+ format.html { redirect_to(products_url) }
+ format.xml { head :ok }
+ end
+ end
+
+end
View
85 app/controllers/store_controller.rb
@@ -0,0 +1,85 @@
+class StoreController < ApplicationController
+
+ before_filter :cart_is_not_empty, :only => [:new_order, :checkout, :save_order]
+
+ def index
+ @products = Product.find_products_for_sale
+ @cart = find_cart
+ end
+
+ def add_to_cart
+ product = Product.find(params[:id])
+ @cart = find_cart
+ @current_item = @cart.add_product(product)
+ respond_to do |format|
+ format.js if request.xhr?
+ format.html {redirect_to_index}
+ end
+ rescue ActiveRecord::RecordNotFound
+ logger.error("Attempt to access invalid product #{params[:id]}")
+ redirect_to_index "Invalid product"
+ end
+
+ def empty_cart
+ session[:cart] = nil
+ redirect_to_index
+ end
+
+ def new_order
+ redirect_to :action => 'checkout', :id => Conversation::NEW_ID
+ end
+
+ def checkout
+ @cart = find_cart
+ conversation = Conversation.get_or_create(params[:id])
+ if conversation.new_record?
+ @conversation_id = Conversation::NEW_ID
+ @order = Order.new
+ else
+ @conversation_id = conversation.id
+ @order = Order.new(conversation.parameters)
+ @order.valid?
+ end
+ end
+
+ def save_order
+ @cart = find_cart
+ @order = Order.new(params[:order])
+ @order.add_line_items_from_cart(@cart)
+ if @order.save
+ session[:cart] = nil
+ Conversation.destroy_if_exists(params[:id])
+ redirect_to_index("Thank you for your order")
+ else
+ conversation = Conversation.get_or_create(params[:id])
+ conversation.parameters = params[:order]
+ conversation.context = 'order'
+ conversation.save!
+ redirect_to :action => 'checkout', :id => conversation
+ end
+ end
+
+ protected
+
+ def cart_is_not_empty
+ if find_cart.items.empty?
+ redirect_to_index("Your cart is empty")
+ end
+ end
+
+ def authorize
+ # don't protect the store with a login
+ end
+
+ private
+
+ def find_cart
+ session[:cart] ||= Cart.new
+ end
+
+ def redirect_to_index(msg = nil)
+ flash[:notice] = msg if msg
+ redirect_to :action => 'index'
+ end
+
+end
View
137 app/controllers/users_controller.rb
@@ -0,0 +1,137 @@
+class UsersController < ApplicationController
+
+ layout 'admin'
+
+ # GET /users
+ # GET /users.xml
+ def index
+ @users = User.find(:all, :order => :name)
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @users }
+ end
+ end
+
+ # GET /users/1
+ # GET /users/1.xml
+ def show
+ @user = User.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @user }
+ end
+ end
+
+ # GET /users/new
+ # GET /users/new.xml
+ def new
+ respond_to do |format|
+ format.html { redirect_to :action => 'form', :id => Conversation::NEW_ID }
+ format.xml { render :xml => User.new }
+ end
+ end
+
+ # GET /users/1/edit
+ def edit
+ user = User.find(params[:id])
+
+ conversation = Conversation.new
+ conversation.ref_id = user.id
+ conversation.context = 'user'
+ conversation.save!
+
+ redirect_to :action => 'form', :id => conversation
+ end
+
+ def form
+ @conversation_id = params[:id]
+ conversation = Conversation.get_or_create(@conversation_id)
+ if conversation.ref_id
+ @user = User.find(conversation.ref_id)
+ else
+ @user = User.new
+ end
+ if conversation.parameters
+ @user.attributes = conversation.parameters
+ @user.valid?
+ end
+ if conversation.ref_id
+ render :action => "edit"
+ else
+ render :action => "new"
+ end
+ end
+
+ # POST /users
+ # POST /users.xml
+ def create
+ @user = User.new(params[:user])
+
+ respond_to do |format|
+ if @user.save
+ flash[:notice] = "User #{@user.name} was successfully created."
+ format.html {
+ Conversation.destroy_if_exists(params[:conversation_id])
+ redirect_to :action=>'index'
+ }
+ format.xml { render :xml => @user, :status => :created, :location => @user }
+ else
+ format.html {
+ conversation = Conversation.get_or_create(params[:conversation_id])
+ conversation.parameters = params[:user]
+ conversation.context = 'user'
+ conversation.save!
+ redirect_to :action => 'form', :id => conversation
+ }
+ format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /users/1
+ # PUT /users/1.xml
+ def update
+ @user = User.find(params[:id])
+
+ respond_to do |format|
+ if @user.update_attributes(params[:user])
+ flash[:notice] = "User #{@user.name} was successfully updated."
+ format.html {
+ Conversation.destroy_if_exists(params[:conversation_id])
+ redirect_to :action=>'index'
+ }
+ format.xml { head :ok }
+ else
+ format.html {
+ conversation = Conversation.get_or_create(params[:conversation_id])
+ conversation.parameters = params[:user]
+ conversation.ref_id = @user.id
+ conversation.context = 'user'
+ conversation.save!
+ redirect_to :action => 'form', :id => conversation
+ }
+ format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /users/1
+ # DELETE /users/1.xml
+ def destroy
+ @user = User.find(params[:id])
+ begin
+ @user.destroy
+ flash[:notice] = "User #{@user.name} deleted"
+ rescue Exception => e
+ flash[:notice] = e.message
+ end
+
+ respond_to do |format|
+ format.html { redirect_to(users_url) }
+ format.xml { head :ok }
+ end
+ end
+
+end
View
2 app/helpers/admin_helper.rb
@@ -0,0 +1,2 @@
+module AdminHelper
+end
View
3 app/helpers/application_helper.rb
@@ -0,0 +1,3 @@
+# Methods added to this helper will be available to all templates in the application.
+module ApplicationHelper
+end
View
7 app/helpers/orders_helper.rb
@@ -0,0 +1,7 @@
+module OrdersHelper
+
+ def order_date_format(order)
+ order.created_at.localtime.strftime('%d/%m/%Y %H:%M:%S')
+ end
+
+end
View
2 app/helpers/products_helper.rb
@@ -0,0 +1,2 @@
+module ProductsHelper
+end
View
10 app/helpers/store_helper.rb
@@ -0,0 +1,10 @@
+module StoreHelper
+
+ def hidden_div_if(condition, attributes = {}, &block)
+ if condition
+ attributes["style"] = "display: none"
+ end
+ content_tag("div", attributes, &block)
+ end
+
+end
View
2 app/helpers/users_helper.rb
@@ -0,0 +1,2 @@
+module UsersHelper
+end
View
28 app/models/cart.rb
@@ -0,0 +1,28 @@
+class Cart
+
+ attr_reader :items
+
+ def initialize
+ @items = []
+ end
+
+ def add_product(product)
+ current_item = @items.find {|item| item.product == product}
+ if current_item
+ current_item.increment_quantity
+ else
+ current_item = CartItem.new(product)
+ @items << current_item
+ end
+ current_item
+ end
+
+ def total_price
+ @items.sum { |item| item.price }
+ end
+
+ def total_items
+ @items.sum { |item| item.quantity }
+ end
+
+end
View
21 app/models/cart_item.rb
@@ -0,0 +1,21 @@
+class CartItem
+
+ attr_reader :product, :quantity
+
+ def initialize(product)
+ @product = product
+ @quantity = 1
+ end
+
+ def increment_quantity
+ @quantity += 1
+ end
+
+ def title
+ @product.title
+ end
+
+ def price
+ @product.price * @quantity
+ end
+end
View
28 app/models/conversation.rb
@@ -0,0 +1,28 @@
+class Conversation < ActiveRecord::Base
+
+ NEW_ID = 'new'
+
+ set_primary_key :uuid
+ include UUIDHelper
+
+ serialize :parameters
+
+ def self.get_or_create(uuid)
+ if uuid == NEW_ID
+ self.new
+ else
+ conversation = self.find_by_uuid(uuid)
+ unless conversation
+ conversation = self.new
+ end
+ conversation
+ end
+ end
+
+ def self.destroy_if_exists(uuid)
+ if uuid != NEW_ID and self.exists? uuid
+ self.destroy uuid
+ end
+ end
+
+end
View
17 app/models/line_item.rb
@@ -0,0 +1,17 @@
+class LineItem < ActiveRecord::Base
+
+ set_primary_key :uuid
+ include UUIDHelper
+
+ belongs_to :order
+ belongs_to :product
+
+ def self.from_cart_item(cart_item)
+ li = self.new
+ li.product = cart_item.product
+ li.quantity = cart_item.quantity
+ li.total_price = cart_item.price
+ li
+ end
+
+end
View
32 app/models/order.rb
@@ -0,0 +1,32 @@
+class Order < ActiveRecord::Base
+
+ set_primary_key :uuid
+ include UUIDHelper
+
+ has_many :line_items
+
+ PAYMENT_TYPES = [['Credit Card', 'cc'], ['PayPal', 'pp']]
+
+ validates_presence_of :name, :address, :email, :pay_type
+ validates_inclusion_of :pay_type, :in => PAYMENT_TYPES.map {|disp, value| value}
+
+ def add_line_items_from_cart(cart)
+ cart.items.each do |item|
+ li = LineItem.from_cart_item(item)
+ self.line_items << li
+ end
+ end
+
+ def payment_type
+ PAYMENT_TYPES.select{|disp, value| value == self.pay_type}.map{|disp, value| disp}.first
+ end
+
+ def total_price
+ self.line_items.map{|item| item.total_price}.sum
+ end
+
+ def total_items
+ self.line_items.map{|item| item.quantity}.sum
+ end
+
+end
View
26 app/models/product.rb
@@ -0,0 +1,26 @@
+class Product < ActiveRecord::Base
+
+ set_primary_key :uuid
+ include UUIDHelper
+
+ validates_presence_of :title, :description, :image_url
+ validates_uniqueness_of :title
+
+ validates_format_of :image_url,
+ :with => %r{\.(gif|jpg|png)$}i,
+ :message => 'must be a URL for GIF, JPG ' + 'or PNG image.'
+
+ validates_numericality_of :price
+ validate :price_must_be_at_least_a_cent
+
+ def self.find_products_for_sale
+ find(:all, :order => "title")
+ end
+
+ protected
+
+ def price_must_be_at_least_a_cent
+ errors.add(:price, 'should be at least 0.01') if price.nil? || price < 0.01
+ end
+
+end
View
59 app/models/user.rb
@@ -0,0 +1,59 @@
+class User < ActiveRecord::Base
+
+ set_primary_key :uuid
+ include UUIDHelper
+
+ validates_presence_of :name
+ validates_uniqueness_of :name
+
+ attr_accessor :password_confirmation
+ validates_confirmation_of :password
+
+ validate :password_non_blank
+
+ def self.authenticate(name, password)
+ user = self.find_by_name(name)
+ if user
+ expected_password = encrypted_password(password, user.salt)
+ if user.hashed_password != expected_password
+ user = nil
+ end
+ end
+ user
+ end
+
+ def after_destroy
+ if User.count.zero?
+ raise "Can't delete last user"
+ end
+ end
+
+ # 'password' is a virtual attribute
+
+ def password
+ @password
+ end
+
+ def password=(pwd)
+ @password = pwd
+ return if pwd.blank?
+ create_new_salt
+ self.hashed_password = User.encrypted_password(self.password, self.salt)
+ end
+
+ private
+
+ def password_non_blank
+ errors.add(:password, "is required") if hashed_password.blank?
+ end
+
+ def create_new_salt
+ self.salt = self.object_id.to_s + rand.to_s
+ end
+
+ def self.encrypted_password(password, salt)
+ string_to_hash = password + "wibble" + salt
+ Digest::SHA1.hexdigest(string_to_hash)
+ end
+
+end
View
5 app/views/admin/index.html.haml
@@ -0,0 +1,5 @@
+%h1 Welcome
+
+It's #{Time.now.localtime.strftime('%d/%m/%Y %H:%M:%S')}.
+%br
+We have #{pluralize(@total_orders, "order")}.
View
12 app/views/admin/login.html.haml
@@ -0,0 +1,12 @@
+.depot-form
+ - form_tag do
+ %fieldset
+ %legend Please Log In
+ %div
+ %label{:for => 'name'} Name
+ = text_field_tag :name, params[:name]
+ %div
+ %label{:for => 'password'} Password
+ = password_field_tag :password, params[:password]
+ %div
+ = submit_tag "Login"
View
27 app/views/layouts/admin.html.haml
@@ -0,0 +1,27 @@
+!!!
+%html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en")
+ %head
+ %meta(http-equiv="content-type" content="text/html;charset=UTF-8")
+ %title Pragprog Books Online Store
+ = stylesheet_link_tag 'scaffold', 'depot'
+ %body#store
+ #banner
+ = image_tag("logo.png")
+ = @page_title || "Pragmatic Bookshelf"
+ #columns
+ #side
+ %a(href="/store/index") Home
+ %br
+ - if session[:user_id]
+ = link_to 'Orders', :controller => 'orders'
+ %br
+ = link_to 'Products', :controller => 'products'
+ %br
+ = link_to 'Users', :controller => 'users'
+ %br
+ = link_to 'Logout', :controller => 'admin', :action => 'logout'
+ %br
+ #main
+ - if flash[:notice]
+ #notice= flash[:notice]
+ = yield
View
27 app/views/layouts/store.html.haml
@@ -0,0 +1,27 @@
+!!!
+%html(xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en")
+ %head
+ %meta(http-equiv="content-type" content="text/html;charset=UTF-8")
+ %title Pragprog Books Online Store
+ = stylesheet_link_tag "depot", :media => "all"
+ = javascript_include_tag :defaults
+ %body#store
+ #banner
+ = image_tag("logo.png")
+ = @page_title || "Pragmatic Bookshelf"
+ #columns
+ #side
+ - hidden_div_if(@cart.items.empty?, :id => "cart") do
+ = render(:partial => "store/cart", :object => @cart)
+ %a(href="/store/index") Home
+ %br
+ %a(href="/store/index") Questions
+ %br
+ %a(href="/store/index") News
+ %br
+ %a(href="/store/index") Contact
+ %br
+ #main
+ - if flash[:notice]
+ #notice= flash[:notice]
+ = yield :layout
View
21 app/views/orders/index.html.haml
@@ -0,0 +1,21 @@
+#order-list
+
+ %h1 Listing orders
+
+ %table
+ %tr
+ %th Date
+ %th Name
+ %th Email
+ %th Pay Type
+ %th Item Count
+ %th Total Price
+ - for order in @orders
+ %tr
+ %td= order_date_format(order)
+ %td&= order.name
+ %td&= order.email
+ %td= order.payment_type
+ %td.quantity= order.total_items
+ %td.price= number_to_currency(order.total_price)
+ %td= link_to 'Show', order
View
36 app/views/orders/show.html.haml
@@ -0,0 +1,36 @@
+#order
+
+ %p
+ %b Date:
+ = order_date_format(@order)
+
+ %p
+ %b Name:
+ &= @order.name
+
+ %p
+ %b Email:
+ &= @order.email
+
+ %p
+ %b Address:
+ &= @order.address
+
+ %p
+ %b Payment Type:
+ = @order.payment_type
+
+ %p
+ %b Order Total:
+ = number_to_currency(@order.total_price)
+
+ %table
+ %tr
+ %th Product
+ %th Quantity
+ %th Item Total
+ - for line_item in @order.line_items
+ %tr
+ %td&= line_item.product.title
+ %td.quantity= line_item.quantity
+ %td.price= number_to_currency(line_item.total_price)
View
28 app/views/products/edit.html.haml
@@ -0,0 +1,28 @@
+%h1 Editing product
+
+- form_for(@product) do |f|
+ = f.error_messages
+ %input{:type => 'hidden', :name => 'conversation_id', :value => @conversation_id}
+ %p
+ = f.label :title
+ %br
+ = f.text_field :title
+ %p
+ = f.label :description
+ %br
+ = f.text_area :description
+ %p
+ = f.label :image_url
+ %br
+ = f.text_field :image_url
+ %p
+ = f.label :price
+ %br
+ = f.text_field :price
+ %p
+ = f.submit 'Update'
+
+%p
+ = link_to 'Show', @product
+ |
+ = link_to 'Back', products_path
View
21 app/views/products/index.html.haml
@@ -0,0 +1,21 @@
+#product-list
+
+ %h1 Listing products
+
+ %table
+ - @products.each do |product|
+ %tr{:class => cycle('list-line-odd', 'list-line-even') }
+ %td= image_tag product.image_url, :class => 'list-image'
+ %td.list-description
+ %dl
+ %dt&= product.title
+ %dd&= truncate(product.description.gsub(/<.*?>/,''), :length => 80)
+ %dd&= product.price
+ %td.list-actions
+ = link_to 'Show', product
+ %br
+ = link_to 'Edit', edit_product_path(product)
+ %br
+ = link_to 'Delete', product, :confirm => 'Are you sure?', :method => :delete
+
+ %p= link_to 'New product', new_product_path
View
26 app/views/products/new.html.haml
@@ -0,0 +1,26 @@
+%h1 New product
+
+- form_for(@product) do |f|
+ = f.error_messages
+ %input{:type => 'hidden', :name => 'conversation_id', :value => @conversation_id}
+ %p
+ = f.label :title
+ %br
+ = f.text_field :title
+ %p
+ = f.label :description
+ %br
+ = f.text_area :description
+ %p
+ = f.label :image_url
+ %br
+ = f.text_field :image_url
+ %p
+ = f.label :price
+ %br
+ = f.text_field :price
+ %p
+ = f.submit 'Create'
+
+%p
+ = link_to 'Back', products_path
View
20 app/views/products/show.html.haml
@@ -0,0 +1,20 @@
+%p
+ %b Title:
+ &= @product.title
+
+%p
+ %b Description:
+ = @product.description
+
+%p
+ %b Image url:
+ &= @product.image_url
+
+%p
+ %b Price:
+ &= @product.price
+
+%p
+ = link_to 'Edit', edit_product_path(@product)
+ |
+ = link_to 'Back', products_path
View
10 app/views/store/_cart.html.haml
@@ -0,0 +1,10 @@
+.cart-title Your Cart
+
+%table
+ = render(:partial => "cart_item", :collection => cart.items)
+ %tr.total-line
+ %td{:colspan => 2} Total
+ %td.total-cell= number_to_currency(cart.total_price)
+
+= button_to "Checkout", :action => 'new_order'
+= button_to 'Empty cart', :action => 'empty_cart'
View
4 app/views/store/_cart_item.html.haml
@@ -0,0 +1,4 @@
+%tr{:id => ('current_item' if cart_item == @current_item)}
+ %td #{cart_item.quantity} &times;
+ %td&= cart_item.title
+ %td.item-price= number_to_currency(cart_item.price)
View
4 app/views/store/add_to_cart.js.rjs
@@ -0,0 +1,4 @@
+page.select("div#notice").each { |div| div.hide }
+page.replace_html("cart", :partial => "cart", :object => @cart)
+page[:cart].visual_effect :blind_down if @cart.total_items == 1
+page[:current_item].visual_effect :highlight, :startcolor => "#88ff88", :endcolor => "#114411"
View
18 app/views/store/checkout.html.haml
@@ -0,0 +1,18 @@
+.depot-form
+ = error_messages_for 'order'
+ - form_for :order, :url => { :action => :save_order, :id => @conversation_id } do |form|
+ %fieldset
+ %legend Please Enter Your Details
+ %div
+ = form.label :name, "Name:"
+ = form.text_field :name, :size => 40
+ %div
+ = form.label :address, "Address:"
+ = form.text_area :address, :rows => 3, :cols => 40
+ %div
+ = form.label :email, "E-Mail:"
+ = form.text_field :email, :size => 40
+ %div
+ = form.label :pay_type, "Pay with:"
+ = form.select :pay_type, Order::PAYMENT_TYPES, :prompt => "Select a payment method"
+ = submit_tag "Place Order", :class => "submit"
View
11 app/views/store/index.html.haml
@@ -0,0 +1,11 @@
+%h1 Your Pragmatic Catalog
+
+- @products.each do |product|
+ .entry
+ = image_tag(product.image_url)
+ %h3&= product.title
+ = product.description
+ .price-line
+ %span.price= number_to_currency(product.price)
+ - form_remote_tag :url => { :action => 'add_to_cart', :id => product } do
+ = submit_tag "Add to Cart"
View
19 app/views/users/edit.html.haml
@@ -0,0 +1,19 @@
+.depot-form
+ - form_for(@user) do |f|
+ = f.error_messages
+ %input{:type => 'hidden', :name => 'conversation_id', :value => @conversation_id}
+ %fieldset
+ %legend Edit #{h @user.name}
+ %div
+ = f.label :password, 'Password'
+ = f.password_field :password, :size => 40
+ %div
+ = f.label :password_confirmation, 'Confirm'
+ = f.password_field :password_confirmation, :size => 40
+ %div
+ = f.submit 'Update'
+
+%p
+ = link_to 'Show', @user
+ |
+ = link_to 'Back', users_path
View
15 app/views/users/index.html.haml
@@ -0,0 +1,15 @@
+#user-list
+
+ %h1 Listing users
+
+ %table
+ %tr
+ %th Name
+ - for user in @users
+ %tr
+ %td&= user.name
+ %td= link_to 'Show', user
+ %td= link_to 'Edit', edit_user_path(user)
+ %td= link_to 'Delete', user, :confirm => 'Are you sure?', :method => :delete
+
+ %p= link_to 'New user', new_user_path
View
20 app/views/users/new.html.haml
@@ -0,0 +1,20 @@
+.depot-form
+ - form_for(@user) do |f|
+ = f.error_messages
+ %input{:type => 'hidden', :name => 'conversation_id', :value => @conversation_id}
+ %fieldset
+ %legend Enter User Details
+ %div
+ = f.label :name
+ = f.text_field :name, :size => 40
+ %div
+ = f.label :password, 'Password'
+ = f.password_field :password, :size => 40
+ %div
+ = f.label :password_confirmation, 'Confirm'
+ = f.password_field :password_confirmation, :size => 40
+ %div
+ = f.submit "Add User", :class => "submit"
+
+%p
+ = link_to 'Back', users_path
View
8 app/views/users/show.html.haml
@@ -0,0 +1,8 @@
+%p
+ %b Name:
+ &= @user.name
+
+%p
+ = link_to 'Edit', edit_user_path(@user)
+ |
+ = link_to 'Back', users_path
View
110 config/boot.rb
@@ -0,0 +1,110 @@
+# Don't change this file!
+# Configure your app in config/environment.rb and config/environments/*.rb
+
+RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
+
+module Rails
+ class << self
+ def boot!
+ unless booted?
+ preinitialize
+ pick_boot.run
+ end
+ end
+
+ def booted?
+ defined? Rails::Initializer
+ end
+
+ def pick_boot
+ (vendor_rails? ? VendorBoot : GemBoot).new
+ end
+
+ def vendor_rails?
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
+ end
+
+ def preinitialize
+ load(preinitializer_path) if File.exist?(preinitializer_path)
+ end
+
+ def preinitializer_path
+ "#{RAILS_ROOT}/config/preinitializer.rb"
+ end
+ end
+
+ class Boot
+ def run
+ load_initializer
+ Rails::Initializer.run(:set_load_path)
+ end
+ end
+
+ class VendorBoot < Boot
+ def load_initializer
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+ Rails::Initializer.run(:install_gem_spec_stubs)
+ Rails::GemDependency.add_frozen_gem_path
+ end
+ end
+
+ class GemBoot < Boot
+ def load_initializer
+ self.class.load_rubygems
+ load_rails_gem
+ require 'initializer'
+ end
+
+ def load_rails_gem
+ if version = self.class.gem_version
+ gem 'rails', version
+ else
+ gem 'rails'
+ end
+ rescue Gem::LoadError => load_error
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
+ exit 1
+ end
+
+ class << self
+ def rubygems_version
+ Gem::RubyGemsVersion rescue nil
+ end
+
+ def gem_version
+ if defined? RAILS_GEM_VERSION
+ RAILS_GEM_VERSION
+ elsif ENV.include?('RAILS_GEM_VERSION')
+ ENV['RAILS_GEM_VERSION']
+ else
+ parse_gem_version(read_environment_rb)
+ end
+ end
+
+ def load_rubygems
+ min_version = '1.3.2'
+ require 'rubygems'
+ unless rubygems_version >= min_version
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
+ exit 1
+ end
+
+ rescue LoadError
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ exit 1
+ end
+
+ def parse_gem_version(text)
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
+ end
+
+ private
+ def read_environment_rb
+ File.read("#{RAILS_ROOT}/config/environment.rb")
+ end
+ end
+ end
+end
+
+# All that for this:
+Rails.boot!
View
22 config/database.yml
@@ -0,0 +1,22 @@
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
View
41 config/environment.rb
@@ -0,0 +1,41 @@
+# Be sure to restart your server when you modify this file
+
+# Specifies gem version of Rails to use when vendor/rails is not present
+RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Add additional load paths for your own custom dirs
+ # config.load_paths += %W( #{RAILS_ROOT}/extras )
+
+ # Specify gems that this application depends on and have them installed with rake gems:install
+ # config.gem "bj"
+ # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
+ # config.gem "sqlite3-ruby", :lib => "sqlite3"
+ # config.gem "aws-s3", :lib => "aws/s3"
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Skip frameworks you're not going to use. To use Rails without a database,
+ # you must remove the Active Record framework.
+ # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
+
+ # Activate observers that should always be running
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names.
+ config.time_zone = 'UTC'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
+ # config.i18n.default_locale = :de
+end
View
17 config/environments/development.rb
@@ -0,0 +1,17 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# In the development environment your application's code is reloaded on
+# every request. This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes = false
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_view.debug_rjs = true
+config.action_controller.perform_caching = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
View
28 config/environments/production.rb
@@ -0,0 +1,28 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The production environment is meant for finished, "live" apps.
+# Code is not reloaded between requests
+config.cache_classes = true
+
+# Full error reports are disabled and caching is turned on
+config.action_controller.consider_all_requests_local = false
+config.action_controller.perform_caching = true
+config.action_view.cache_template_loading = true
+
+# See everything in the log (default is :info)
+# config.log_level = :debug
+
+# Use a different logger for distributed setups
+# config.logger = SyslogLogger.new
+
+# Use a different cache store in production
+# config.cache_store = :mem_cache_store
+
+# Enable serving of images, stylesheets, and javascripts from an asset server
+# config.action_controller.asset_host = "http://assets.example.com"
+
+# Disable delivery errors, bad email addresses will be ignored
+# config.action_mailer.raise_delivery_errors = false
+
+# Enable threaded mode
+# config.threadsafe!
View
28 config/environments/test.rb
@@ -0,0 +1,28 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The test environment is used exclusively to run your application's
+# test suite. You never need to work with it otherwise. Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs. Don't rely on the data there!
+config.cache_classes = true
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching = false
+config.action_view.cache_template_loading = true
+
+# Disable request forgery protection in test environment
+config.action_controller.allow_forgery_protection = false
+
+# Tell Action Mailer not to deliver emails to the real world.
+# The :test delivery method accumulates sent emails in the
+# ActionMailer::Base.deliveries array.
+config.action_mailer.delivery_method = :test
+
+# Use SQL instead of Active Record's schema dumper when creating the test database.
+# This is necessary if your schema can't be completely dumped by the schema dumper,
+# like if you have constraints or database-specific column types
+# config.active_record.schema_format = :sql
View
7 config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
View
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
View
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
View
21 config/initializers/new_rails_defaults.rb
@@ -0,0 +1,21 @@
+# Be sure to restart your server when you modify this file.
+
+# These settings change the behavior of Rails 2 apps and will be defaults
+# for Rails 3. You can remove this initializer when Rails 3 is released.
+
+if defined?(ActiveRecord)
+ # Include Active Record class name as root for JSON serialized output.
+ ActiveRecord::Base.include_root_in_json = true
+
+ # Store the full class name (including module namespace) in STI type column.
+ ActiveRecord::Base.store_full_sti_class = true
+end
+
+ActionController::Routing.generate_best_match = false
+
+# Use ISO 8601 format for JSON serialized times and dates.
+ActiveSupport.use_standard_json_time_format = true
+
+# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
+# if you're including raw json in an HTML page.
+ActiveSupport.escape_html_entities_in_json = false
View
15 config/initializers/session_store.rb
@@ -0,0 +1,15 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying cookie session data integrity.
+# If you change this key, all old sessions will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+ActionController::Base.session = {
+ :key => '_depot_session',
+ :secret => '65f226bcbcb42add1767558eeeefffa9316e20ca025b5cec28700cfc573e7e99aa6229ed7897933f7167e137c8f6956e5e647200438f2151a25565199df2c889'
+}
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rake db:sessions:create")
+ActionController::Base.session_store = :active_record_store
View
5 config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
View
50 config/routes.rb
@@ -0,0 +1,50 @@
+ActionController::Routing::Routes.draw do |map|
+
+ map.resources :orders
+
+ map.resources :users
+
+ map.resources :products
+
+ # The priority is based upon order of creation: first created -> highest priority.
+
+ # Sample of regular route:
+ # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
+ # Keep in mind you can assign values other than :controller and :action
+
+ # Sample of named route:
+ # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
+ # This route can be invoked with purchase_url(:id => product.id)
+
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
+ # map.resources :products
+
+ # Sample resource route with options:
+ # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
+
+ # Sample resource route with sub-resources:
+ # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
+
+ # Sample resource route with more complex sub-resources
+ # map.resources :products do |products|
+ # products.resources :comments
+ # products.resources :sales, :collection => { :recent => :get }
+ # end
+
+ # Sample resource route within a namespace:
+ # map.namespace :admin do |admin|
+ # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
+ # admin.resources :products
+ # end
+
+ # You can have the root of your site routed with map.root -- just remember to delete public/index.html.
+ # map.root :controller => "welcome"
+
+ # See how all your routes lay out with "rake routes"
+
+ # Install the default routes as the lowest priority.
+ # Note: These default routes make all actions in every controller accessible via GET requests. You should
+ # consider removing or commenting them out if you're using named routes and resources.
+ map.connect ':controller/:action/:id'
+ map.connect ':controller/:action/:id.:format'
+end
View
1 db/.gitignore
@@ -0,0 +1 @@
+*.sqlite3
View
15 db/migrate/20100308002138_create_products.rb
@@ -0,0 +1,15 @@
+class CreateProducts < ActiveRecord::Migration
+ def self.up
+ create_table :products, :id => false do |t|
+ t.string :uuid, :primary => true, :limit => 36
+ t.string :title
+ t.text :description
+ t.string :image_url
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :products
+ end
+end
View
9 db/migrate/20100308005313_add_price_to_product.rb
@@ -0,0 +1,9 @@
+class AddPriceToProduct < ActiveRecord::Migration
+ def self.up
+ add_column :products, :price, :decimal, :precision => 8, :scale => 2, :default => 0
+ end
+
+ def self.down
+ remove_column :products, :price
+ end
+end
View
61 db/migrate/20100308051101_add_test_data.rb
@@ -0,0 +1,61 @@
+class AddTestData < ActiveRecord::Migration
+
+ def self.up
+ Product.delete_all
+
+ Product.create(
+ :title => 'Pragmatic Project Automation',
+ :description =>
+ %{<p>
+ <em>Pragmatic Project Automation</em> shows you how to improve the
+ consistency and repeatability of your project's procedures using
+ automation to reduce risk and errors.
+ </p>
+ <p>
+ Simply put, we're going to put this thing called a computer to work
+ for you doing the mundane (but important) project stuff. That means
+ you'll have more time and energy to do the really
+ exciting---and difficult---stuff, like writing quality code.
+ </p>},
+ :image_url => '/images/auto.jpg',
+ :price => 29.95)
+
+ Product.create(
+ :title => 'Pragmatic Version Control',
+ :description =>
+ %{<p>
+ This book is a recipe-based approach to using Subversion that will
+ get you up and running quickly---and correctly. All projects need
+ version control: it's a foundational piece of any project's
+ infrastructure. Yet half of all project teams in the U.S. don't use
+ any version control at all. Many others don't use it well, and end
+ up experiencing time-consuming problems.
+ </p>},
+ :image_url => '/images/svn.jpg',
+ :price => 28.50)
+
+ Product.create(
+ :title => 'Pragmatic Unit Testing (C#)',
+ :description =>
+ %{<p>
+ Pragmatic programmers use feedback to drive their development and
+ personal processes. The most valuable feedback you can get while
+ coding comes from unit testing.
+ </p>
+ <p>
+ Without good tests in place, coding can become a frustrating game of
+ "whack-a-mole." That's the carnival game where the player strikes at a
+ mechanical mole; it retreats and another mole pops up on the opposite side
+ of the field. The moles pop up and down so fast that you end up flailing
+ your mallet helplessly as the moles continue to pop up where you least
+ expect them.
+ </p>},
+ :image_url => '/images/utc.jpg',
+ :price => 27.75)
+ end
+
+ def self.down
+ Product.delete_all
+ end
+
+end
View
16 db/migrate/20100308105046_create_sessions.rb
@@ -0,0 +1,16 @@
+class CreateSessions < ActiveRecord::Migration
+ def self.up
+ create_table :sessions do |t|
+ t.string :session_id, :null => false
+ t.text :data
+ t.timestamps
+ end
+
+ add_index :sessions, :session_id
+ add_index :sessions, :updated_at
+ end
+
+ def self.down
+ drop_table :sessions
+ end
+end
View
18 db/migrate/20100308122259_create_orders.rb
@@ -0,0 +1,18 @@
+class CreateOrders < ActiveRecord::Migration
+
+ def self.up
+ create_table :orders, :id => false do |t|
+ t.string :uuid, :primary => true, :limit => 36
+ t.string :name
+ t.text :address
+ t.string :email
+ t.string :pay_type, :limit => 10
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :orders
+ end
+
+end
View
18 db/migrate/20100308122348_create_line_items.rb
@@ -0,0 +1,18 @@
+class CreateLineItems < ActiveRecord::Migration
+
+ def self.up
+ create_table :line_items, :id => false do |t|
+ t.string :uuid, :primary => true, :limit => 36
+ t.string :product_id, :null => false, :options => "CONSTRAINT fk_line_item_products REFERENCES products(uuid)"
+ t.string :order_id, :null => false, :options => "CONSTRAINT fk_line_item_orders REFERENCES orders(uuid)"
+ t.integer :quantity, :null => false
+ t.decimal :total_price, :null => false, :precision => 8, :scale => 2
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :line_items
+ end
+
+end
View
18 db/migrate/20100308220639_create_conversations.rb
@@ -0,0 +1,18 @@
+class CreateConversations < ActiveRecord::Migration
+
+ def self.up
+ create_table :conversations, :id => false do |t|
+ t.string :uuid, :primary => true, :limit => 36
+ t.string :context
+ t.string :ref_id
+ t.text :parameters
+ t.timestamps
+ end
+ add_index :conversations, :ref_id
+ end
+
+ def self.down
+ drop_table :conversations
+ end
+
+end
View
17 db/migrate/20100309042714_create_users.rb
@@ -0,0 +1,17 @@
+class CreateUsers < ActiveRecord::Migration
+
+ def self.up
+ create_table :users, :id => false do |t|
+ t.string :uuid, :primary => true, :limit => 36
+ t.string :name
+ t.string :hashed_password
+ t.string :salt
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :users
+ end
+
+end
View
74 db/schema.rb
@@ -0,0 +1,74 @@
+# This file is auto-generated from the current state of the database. Instead of editing this file,
+# please use the migrations feature of Active Record to incrementally modify your database, and
+# then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your database schema. If you need
+# to create the application database on another system, you should be using db:schema:load, not running
+# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20100309042714) do
+
+ create_table "conversations", :id => false, :force => true do |t|
+ t.string "uuid", :limit => 36
+ t.string "context"
+ t.string "ref_id"
+ t.text "parameters"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "conversations", ["ref_id"], :name => "index_conversations_on_ref_id"
+
+ create_table "line_items", :id => false, :force => true do |t|
+ t.string "uuid", :limit => 36
+ t.string "product_id", :null => false
+ t.string "order_id", :null => false
+ t.integer "quantity", :null => false
+ t.decimal "total_price", :precision => 8, :scale => 2, :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "orders", :id => false, :force => true do |t|
+ t.string "uuid", :limit => 36
+ t.string "name"
+ t.text "address"
+ t.string "email"
+ t.string "pay_type", :limit => 10
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "products", :id => false, :force => true do |t|
+ t.string "uuid", :limit => 36
+ t.string "title"
+ t.text "description"
+ t.string "image_url"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.decimal "price", :precision => 8, :scale => 2, :default => 0.0
+ end
+
+ create_table "sessions", :force => true do |t|
+ t.string "session_id", :null => false
+ t.text "data"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
+ add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
+
+ create_table "users", :id => false, :force => true do |t|
+ t.string "uuid", :limit => 36
+ t.string "name"
+ t.string "hashed_password"
+ t.string "salt"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+end
View
7 db/seeds.rb
@@ -0,0 +1,7 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Major.create(:name => 'Daley', :city => cities.first)
View
2 doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
View
7 lib/uuid_helper.rb
@@ -0,0 +1,7 @@
+require "uuidtools"
+
+module UUIDHelper
+ def before_create
+ self.uuid = UUIDTools::UUID.random_create.to_s
+ end
+end
View
0 log/.gitignore
No changes.
View
30 public/404.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
View
30 public/422.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+</body>
+</html>
View
30 public/500.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>We're sorry, but something went wrong (500)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <h1>We're sorry, but something went wrong.</h1>
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
+ </div>
+</body>
+</html>
View
0 public/favicon.ico
No changes.
View
BIN public/images/auto.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN public/images/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN public/images/rails.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN public/images/svn.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN public/images/utc.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
275 public/index.html
@@ -0,0 +1,275 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title>Ruby on Rails: Welcome aboard</title>
+ <style type="text/css" media="screen">
+ body {
+ margin: 0;
+ margin-bottom: 25px;
+ padding: 0;
+ background-color: #f0f0f0;
+ font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
+ font-size: 13px;
+ color: #333;
+ }
+
+ h1 {
+ font-size: 28px;
+ color: #000;
+ }
+
+ a {color: #03c}
+ a:hover {
+ background-color: #03c;
+ color: white;
+ text-decoration: none;
+ }
+
+
+ #page {
+ background-color: #f0f0f0;
+ width: 750px;
+ margin: 0;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ #content {
+ float: left;
+ background-color: white;
+ border: 3px solid #aaa;
+ border-top: none;
+ padding: 25px;
+ width: 500px;
+ }
+
+ #sidebar {
+ float: right;
+ width: 175px;
+ }
+
+ #footer {
+ clear: both;
+ }
+
+
+ #header, #about, #getting-started {
+ padding-left: 75px;
+ padding-right: 30px;
+ }
+
+
+ #header {
+ background-image: url("images/rails.png");
+ background-repeat: no-repeat;
+ background-position: top left;
+ height: 64px;
+ }
+ #header h1, #header h2 {margin: 0}
+ #header h2 {
+ color: #888;
+ font-weight: normal;
+ font-size: 16px;
+ }
+
+
+ #about h3 {
+ margin: 0;
+ margin-bottom: 10px;
+ font-size: 14px;
+ }
+
+ #about-content {
+ background-color: #ffd;
+ border: 1px solid #fc0;
+ margin-left: -11px;
+ }
+ #about-content table {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 11px;
+ border-collapse: collapse;
+ }
+ #about-content td {
+ padding: 10px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ }
+ #about-content td.name {color: #555}
+ #about-content td.value {color: #000}
+
+ #about-content.failure {
+ background-color: #fcc;
+ border: 1px solid #f00;
+ }
+ #about-content.failure p {
+ margin: 0;
+ padding: 10px;
+ }
+
+
+ #getting-started {
+ border-top: 1px solid #ccc;
+ margin-top: 25px;
+ padding-top: 15px;
+ }
+ #getting-started h1 {
+ margin: 0;
+ font-size: 20px;
+ }
+ #getting-started h2 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;