1. Trang chủ
  2. » Công Nghệ Thông Tin

Agile Web Development with Rails phần 10 docx

60 354 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 60
Dung lượng 1,72 MB

Nội dung

Appendix C Source Code This appendix contains three things. • Full listings for the files we created, and the generated files that we modified, for the final Depot application. • The source for an e-mail exception notifier starts on page 511. • A cross-reference listing for all the code samples in the book starts on page 512. All code is available for download from our website at http://pragmaticprogrammer.com/titles/railscode.html. C.1 The Full Depot Application Database Files depot_final/config/database.yml: File 105 development: adapter: mysql database: depot_development host: localhost username: password: test: adapter: mysql database: depot_test host: localhost username: password: production: adapter: mysql database: depot_development host: localhost username: password: THE FULL DEPOT APPLICATION 487 depot_final/db/create.sql: File 106 drop table if exists users; drop table if exists line_items; drop table if exists orders; drop table if exists products; create table products ( id int not null auto_increment, title varchar(100) not null, description text not null, image_url varchar(200) not null, price decimal(10,2) not null, date_available datetime not null, primary key (id) ); create table orders ( id int not null auto_increment, name varchar(100) not null, email varchar(255) not null, address text not null, pay_type char(10) not null, shipped_at datetime null, primary key (id) ); create table line_items ( id int not null auto_increment, product_id int not null, order_id int not null, quantity int not null default 0, unit_price decimal(10,2) not null, constraint fk_items_product foreign key (product_id) references products(id), constraint fk_items_order foreign key (order_id) references orders(id), primary key (id) ); create table users ( id int not null auto_increment, name varchar(100) not null, hashed_password char(40) null, primary key (id) ); /* password = 'secret' */ insert into users values(null, 'dave', 'e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4'); Controllers depot_final/app/controllers/application.rb: File 84 # Application-wide functionality used by controllers. # # Also establishes Cart, LineItem, and User as models. This # is necessary because these classes appear in sessions and # hence have to be preloaded class ApplicationController < ActionController::Base model :cart model :line_item private # Set the notice if a parameter is given, then redirect back Report erratum THE FULL DEPOT APPLICATION 488 # to the current controller's +index+ action def redirect_to_index(msg = nil) #:doc: flash[:notice] = msg if msg redirect_to(:action => 'index') end # The #authorize method is used as a <tt>before_hook</tt> in # controllers that contain administration actions. If the # session does not contain a valid user, the method # redirects to the LoginController.login. def authorize #:doc: unless session[:user_id] flash[:notice] = "Please log in" redirect_to(:controller => "login", :action => "login") end end end depot_final/app/controllers/admin_controller.rb: File 83 # The administration functions allow authorized users # to add, delete, list, and edit products. The class # was initially generated from a scaffold but has since been # modified, so do not regenerate. # # Only logged-in administrators can use the actions here. See # Application.authorize for details. # # See also: Product class AdminController < ApplicationController before_filter :authorize # An alias for #list, listing all current products. def index list render_action 'list' end # List all current products. def list @product_pages, @products = paginate :product, :per_page => 10 end # Show details of a particular product. def show @product = Product.find(@params[:id]) end # Initiate the creation of a new product. # The work is completed in #create. def new @product = Product.new end # Get information on a new product and # attempt to create a row in the database. def create @product = Product.new(@params[:product]) if @product.save flash[ 'notice']='Product was successfully created.' redirect_to :action => 'list' else render_action 'new' end end # Initiate the editing of an existing product. Report erratum THE FULL DEPOT APPLICATION 489 # The work is completed in #update. def edit @product = Product.find(@params[:id]) end # Update an existing product based on values # from the form. def update @product = Product.find(@params[:id]) if @product.update_attributes(@params[:product]) flash[ 'notice']='Product was successfully updated.' redirect_to :action => 'show', :id => @product else render_action 'edit' end end # Destroy a particular product. def destroy Product.find(@params[:id]).destroy redirect_to :action => 'list' end # Ship a number of products. This action normally dispatches # back to itself. Each time it first looks for orders that # the user has marked to be shipped and ships them. It then # displays an updated list of orders still awaiting shipping. # # The view contains a checkbox for each pending order. If the # user selects the checkbox to ship the product with id 123, then # this method will see <tt>things_to_ship[123]</tt> set to "yes". def ship count = 0 if things_to_ship = params[:to_be_shipped] count = do_shipping(things_to_ship) if count > 0 count_text = pluralize(count, "order") flash.now[:notice] = "#{count_text} marked as shipped" end end @pending_orders = Order.pending_shipping end private def do_shipping(things_to_ship) count = 0 things_to_ship.each do |order_id, do_it| if do_it == "yes" order = Order.find(order_id) order.mark_as_shipped order.save count += 1 end end count end def pluralize(count, noun) case count when 0: "No #{noun.pluralize}" when 1: "One #{noun}" else "#{count} #{noun.pluralize}" end end end Report erratum THE FULL DEPOT APPLICATION 490 depot_final/app/controllers/login_controller.rb: File 85 # This controller performs double duty. It contains the # #login action, which is used to log in administrative users. # # It also contains the #add_user, #list_users, and #delete_user # actions, used to maintain the users table in the database. # # The LoginController shares a layout with AdminController # # See also: User class LoginController < ApplicationController layout "admin" # You must be logged in to use all functions except #login before_filter :authorize, :except => :login # The default action displays a status page. def index @total_orders = Order.count @pending_orders = Order.count_pending end # Display the login form and wait for user to # enter a name and password. We then validate # these, adding the user object to the session # if they authorize. def login if request.get? session[:user_id] = nil @user = User.new else @user = User.new(params[:user]) logged_in_user = @user.try_to_login if logged_in_user session[:user_id] = logged_in_user.id redirect_to(:action => "index") else flash[:notice] = "Invalid user/password combination" end end end # Add a new user to the database. def add_user if request.get? @user = User.new else @user = User.new(params[:user]) if @user.save redirect_to_index("User #{@user.name} created") end end end # Delete the user with the given ID from the database. # The model raises an exception if we attempt to delete # the last user. def delete_user id = params[:id] if id && user = User.find(id) begin user.destroy flash[:notice] = "User #{user.name} deleted" rescue Report erratum THE FULL DEPOT APPLICATION 491 flash[:notice] = "Can't delete that user" end end redirect_to(:action => :list_users) end # List all the users. def list_users @all_users = User.find(:all) end # Log out by clearing the user entry in the session. We then # redirect to the #login action. def logout session[:user_id] = nil flash[:notice] = "Logged out" redirect_to(:action => "login") end end depot_final/app/controllers/store_controller.rb: File 86 # The StoreController runs the buyer side of our store. # # [#index] Display the catalog # [#add_to_cart] Add a selected product to the current cart # [#display_cart] Show the contents of the cart # [#empty_cart] Clear out the cart # {#checkout} Initiate the checkout # [#save_order] Finalize the checkout by saving the order class StoreController < ApplicationController before_filter :find_cart, :except => :index # Display the catalog, a list of all salable products. def index @products = Product.salable_items end # Add the given product to the current cart. def add_to_cart product = Product.find(params[:id]) @cart.add_product(product) redirect_to(:action => 'display_cart') rescue logger.error("Attempt to access invalid product #{params[:id]}") redirect_to_index( 'Invalid product') end # Display the contents of the cart. If the cart is # empty, display a notice and return to the # catalog instead. def display_cart @items = @cart.items if @items.empty? redirect_to_index("Your cart is currently empty") end if params[:context] == :checkout render(:layout => false) end end # Remove all items from the cart def empty_cart @cart.empty! redirect_to_index( 'Your cart is now empty') end Report erratum THE FULL DEPOT APPLICATION 492 # Prompt the user for their contact details and payment method, # The checkout procedure is completed by the #save_order method. def checkout @items = @cart.items if @items.empty? redirect_to_index("There 's nothing in your cart!") else @order = Order.new end end # Called from checkout view, we convert a cart into an order # and save it in the database. def save_order @order = Order.new(params[:order]) @order.line_items << @cart.items if @order.save @cart.empty! redirect_to_index( 'Thank you for your order.') else render(:action => 'checkout') end end private # Save a cart object in the @cart variable. If we already # have one cached in the session, use it, otherwise create # a new one and add it to the session def find_cart @cart = (session[:cart] ||= Cart.new) end end Models depot_final/app/models/cart.rb: File 88 # A Cart consists of a list of LineItem objects and a current # total price. Adding a product to the cart will either add a # new entry to the list or increase the quantity of an existing # item in the list. In both cases the total price will # be updated. # # Class Cart is a model but does not represent information # stored in the database. It therefore does not inherit from # ActiveRecord::Base. class Cart # An array of LineItem objects attr_reader :items # The total price of everything added to this cart attr_reader :total_price # Create a new shopping cart. Delegates this work to #empty! def initialize empty! end # Add a product to our list of items. If an item already # exists for that product, increase the quantity # for that item rather than adding a new item. def add_product(product) item = @items.find {|i| i.product_id == product.id} Report erratum THE FULL DEPOT APPLICATION 493 if item item.quantity += 1 else item = LineItem.for_product(product) @items << item end @total_price += product.price end # Empty the cart by resetting the list of items # and zeroing the current total price. def empty! @items = [] @total_price = 0.0 end end depot_final/app/models/line_item.rb: File 89 # Line items tie products to orders (and before that, to carts). # Because the price of a product may change after an order is placed, # the line item contains a copy of the product price at the time # it was created. class LineItem < ActiveRecord::Base belongs_to :product belongs_to :order # Return a new LineItem given a Product. def self.for_product(product) item = self.new item.quantity = 1 item.product = product item.unit_price = product.price item end end depot_final/app/models/order.rb: File 90 # An Order contains details of the purchaser and # has a set of child LineItem rows. class Order < ActiveRecord::Base has_many :line_items # A list of the types of payments we accept. The key is # the text displayed in the selection list, and the # value is the string that goes into the database. PAYMENT_TYPES = [ [ "Check", "check" ], [ "Credit Card", "cc" ], [ "Purchase Order", "po" ] ].freeze validates_presence_of :name, :email, :address, :pay_type # Return a count of all orders pending shipping. def self.count_pending count("shipped_at is null") end # Return all orders pending shipping. def self.pending_shipping find(:all, :conditions => "shipped_at is null") end # The shipped_at column is +NULL+ for Report erratum THE FULL DEPOT APPLICATION 494 # unshipped orders, the dtm of shipment otherwise. def mark_as_shipped self.shipped_at = Time.now end end depot_final/app/models/product.rb: File 91 # A Product is something we can sell (but only if #we 're past its +date_available+ attribute). class Product < ActiveRecord::Base validates_presence_of :title validates_presence_of :description validates_presence_of :image_url validates_uniqueness_of :title validates_numericality_of :price validates_format_of :image_url, :with => %r{^http:.+\.(gif|jpg|png)$}i, :message => "must be a URL for a GIF, JPG, or PNG image" # Return a list of products we can sell (which means they have to be # available). Show the most recently available first. def self.salable_items find(:all, :conditions => "date_available <= now()", :order => "date_available desc") end protected # Validate that the product price is a positive Float. def validate #:doc: errors.add(:price, "should be positive") unless price.nil? || price > 0.0 end end depot_final/app/models/user.rb: File 92 require "digest/sha1" # A User is used to validate administrative staff. The class is # complicated by the fact that on the application side it # deals with plain-text passwords, but in the database it uses # SHA1-hashed passwords. class User < ActiveRecord::Base # The plain-text password, which is not stored # in the database attr_accessor :password # We never allow the hashed password to be # set from a form attr_accessible :name, :password validates_uniqueness_of :name validates_presence_of :name, :password # Return the User with the given name and # plain-text password def self.login(name, password) hashed_password = hash_password(password || "") STDERR.puts hashed_password find(:first, :conditions => ["name = ? and hashed_password = ?", name, hashed_password]) end Report erratum THE FULL DEPOT APPLICATION 495 # Log in if the name and password (after hashing) # match the database, or if the name matches # an entry in the database with no password def try_to_login User.login(self.name, self.password) || User.find_by_name_and_hashed_password(name, "") end # When a new User is created, it initially has a # plain-text password. We convert this to an SHA1 hash # before saving the user in the database. def before_create self.hashed_password = User.hash_password(self.password) end before_destroy :dont_destroy_dave # Don 't delete 'dave' from the database def dont_destroy_dave raise "Can 't destroy dave" if self.name == 'dave' end # Clear out the plain-text password once we 've # saved this row. This stops it being made available # in the session def after_create @password = nil end private def self.hash_password(password) Digest::SHA1.hexdigest(password) end end Views depot_final/app/views/layouts/admin.rhtml: File 96 <html> <head> <title>ADMINISTER Pragprog Books Online Store</title> <%= stylesheet_link_tag "scaffold", "depot", "admin", :media => "all" %> </head> <body> <div id="banner"> <%= @page_title || "Administer Bookshelf" %> </div> <div id="columns"> <div id="side"> <% if session[:user_id] -%> <%= link_to("Products", :controller => "admin", :action => "list")%><br /> <%= link_to("Shipping", :controller => "admin", :action => "ship")%><br /> <hr/> <%= link_to("Add user", :controller => "login", :action => "add_user")%><br /> <%= link_to("List users", :controller => "login", :action => "list_users")%><br /> <hr/> <%= link_to("Log out", :controller => "login", :action => "logout")%> Report erratum [...]... views/lib/rdoc_template.rb web2 /app/controllers/example_controller.rb web2 /app/controllers/guesswhat_controller.rb web2 /app/controllers/list_controller.rb web2 /app/controllers/list_no_ajax_controller.rb web2 /app/models/item.rb web2 /app/views/example/check.rhtml web2 /app/views/example/compat.rhtml web2 /app/views/example/compat_form.rhtml web2 /app/views/example/effects.rhtml web2 /app/views/example/effectswithoutajax.rhtml web2 /app/views/example/index.rhtml... depot_final/app/controllers/admin_controller.rb depot_final/app/controllers/application.rb depot_final/app/controllers/login_controller.rb Report erratum 513 C ROSS -R EFERENCE 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 OF... web2 /app/views/example/index.rhtml web2 /app/views/example/multiple.rhtml web2 /app/views/example/observer.rhtml web2 /app/views/example/periodic.rhtml web2 /app/views/example/say_hello.rhtml web2 /app/views/example/search.rhtml web2 /app/views/example/update_many.rhtml web2 /app/views/guesswhat/_form.rhtml web2 /app/views/guesswhat/index.rhtml web2 /app/views/layouts/list.rhtml web2 /app/views/list/index.rhtml web2 /app/views/list_no_ajax/_item.rhtml... Ruby on Rails http://www.rubyonrails.com/ The official Rails home page, with links to testimonials, documentation, community pages, downloads, and more Some of the best resources for beginners include the movies showing folks coding Rails applications Ruby on Rails (for developers) http://dev.rubyonrails.com/ The page for serious Rails developers... request.env["HTTP_HOST"], "rails_ root" => rails_ root } @sent_on = sent_on @from = SYSTEM_EMAIL_ADDRESS @recipients = EXCEPTION_RECIPIENTS @headers = {} end private def sanitize_backtrace(trace) re = Regexp.new(/^#{Regexp.escape (rails_ root)}/) trace.map do |line| Pathname.new(line.gsub(re, " [RAILS_ ROOT]")).cleanpath.to_s end end Report erratum 511 C ROSS -R EFERENCE OF C ODE S AMPLES def rails_ root @rails_ root ||=... ActionController::TestResponse.new end # Replace this with your real tests def test_truth assert true end # This test won't pass! def test_index get :index assert_response :success end def test_index_without_user Report erratum 504 T HE F ULL D EPOT A PPLICATION 505 get :index assert_redirected_to :action => "login" assert_equal "Please log in", flash[:notice] end def test_login _with_ invalid_user post :login, :user... cookies/cookie1/app/controllers/cookies_controller.rb cookies/cookie1/app/controllers/session_controller.rb cookies/cookie1/app/sweepers/article_sweeper.rb cookies/cookie1/db/create.sql depot1/db/create.sql depot10/app/controllers/store_controller.rb depot10/app/views/layouts/store.rhtml depot10/app/views/store/display_cart.rhtml depot11/app/controllers/store_controller.rb depot11/app/helpers/application_helper.rb depot11/app/models/cart.rb depot11/app/views/store/display_cart.rhtml... font-size: x-small; text-align: right; padding-left: 1em; } ListLine0 { background: #e0f8f8; } ListLine1 { background: #f8e0f8; } depot_final/public/stylesheets/depot.css: File 108 #banner { background: #9c9; padding-top: 10px; padding-bottom: 10px; border-bottom: 2px solid; font: small-caps 40px/40px "Times New Roman", serif; color: #282; text-align: center; } #banner img { float: left; } #columns { background:... @backtrace.first %> Request information: * URL: * Parameters: * Rails root: Session dump: * : . for download from our website at http://pragmaticprogrammer.com/titles/railscode.html. C.1 The Full Depot Application Database Files depot_final/config/database.yml: File 105 development: adapter:. mysql database: depot _development host: localhost username: password: test: adapter: mysql database: depot_test host: localhost username: password: production: adapter: mysql database: depot _development host:. products ( id int not null auto_increment, title varchar (100 ) not null, description text not null, image_url varchar(200) not null, price decimal (10, 2) not null, date_available datetime not null, primary

Ngày đăng: 07/08/2014, 00:22