$ rails new depot
$ rails generate scaffold Product title:string description:text image_url:string price:decimal
Creates depot_a/db/migrate/20110211000001_create_products.rb class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :title t.text :description t.string :image_url t.decimal :price, :precision => 8, :scale => 2 t.timestamps end end end
$ rake db:migrate
$ rails server
depot_b/db/seeds.rb Product.delete_all # . . . Product.create(:title => ‘Programming Ruby 1.9’, :description => %{<p> Ruby is the fastest growing and most exciting dynamic language out there. If you need to get working programs delivered fast, you should add Ruby to your toolbox. </p>}, :image_url => ‘/images/ruby.jpg’, :price =
$ rake db:seed
Validation In app/models/product.rb: class Product < ActiveRecord::Base validates :title, :description, :image_url, :presence => true validates :title, :uniqueness => true validates :price, :numericality => {:greater_than_or_equal_to => 0.01} validates :image_url, :format => { :with => %r{\.(gif|jpg|png)$}i, :message => ‘must be a URL for GIF, JPG or PNG image.’ } end
$ rake test
create the test. In this case is unit test
depot_c/test/functional/products_controller_test.rb depot_c/test/unit/product_test.rb
Fixtures(YAML or yml) Has data to test The name of the file must be the same as the table in database Ex: test/fixtures/products.yml It has the entry of each row to be inserted in database To load the data class ProductTest < ActiveSupport::TestCase fixtures :products
Catalog Display Creating the Catalog Listing Create the controller store
rails generate controller store index create app/controllers/store_controller.rb route get “store/index” invoke erb create app/views/store create app/views/store/index.html.erb invoke test_unit create test/controllers/store_controller_test.rb invoke helper create app/helpers/store_helper.rb invoke test_unit create test/helpers/store_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/store.js.coffee invoke scss create app/assets/stylesheets/store.css.scss In config/routes.rb Include the rout to store->index Depot::Application.routes.draw do get “store/index” resources :products # … # You can have the root of your site routed with “root” # just remember to delete public/index.html. # root :to => “welcome#index” root :to => ‘store#index’ , :as => ‘store’ # end
Instead localhost:3000/store/index with this root, store/index is accessed using localhost:3000/store/
In depot_d/app/controllers/store_controller.rb class StoreController < ApplicationController def index @products = Product.all end end
Include a default_scope to define the order of each query
depot_d/app/models/product.rb class Product < ActiveRecord::Base default_scope :order => ‘title’ # validation end
fill depot/app/views/store/index.html.erb change the depot/app/views/layouts/application.html.erb The CSS create a file public/stylesheets/depot.css Using Helper to format price In index.html.erb <span class="price"><%= number_to_currency(product.price) %></span> put more test in products_controller_test.rb
Chart Creation $ rails generate scaffold cart invoke active_record create db/migrate/20131226000221_create_carts.rb create app/models/cart.rb invoke test_unit create test/models/cart_test.rb create test/fixtures/carts.yml invoke resource_route route resources :carts invoke scaffold_controller create app/controllers/carts_controller.rb invoke erb create app/views/carts create app/views/carts/index.html.erb create app/views/carts/edit.html.erb create app/views/carts/show.html.erb create app/views/carts/new.html.erb create app/views/carts/_form.html.erb invoke test_unit create test/controllers/carts_controller_test.rb invoke helper create app/helpers/carts_helper.rb invoke test_unit create test/helpers/carts_helper_test.rb invoke jbuilder create app/views/carts/index.json.jbuilder create app/views/carts/show.json.jbuilder invoke assets invoke coffee create app/assets/javascripts/carts.js.coffee invoke scss create app/assets/stylesheets/carts.css.scss invoke scss identical app/assets/stylesheets/scaffolds.css.scss
$ rake db:migrate
Change application_controller.rb to get the cart from session.
Create the line_item using scaffold. A connection between product and cart
$ rails generate scaffold line_item product_id:integer cart_id:integer
$ rake db:migrate
In app/models/cart.rb class Cart < ActiveRecord::Base has_many :line_items, :dependent => :destroy end In app/models/line_item.rb class LineItem < ActiveRecord::Base belongs_to :product belongs_to :cart end In app/models/producs.rb class Product < ActiveRecord::Base default_scope :order => ‘title’ has_many :line_items before_destroy :ensure_not_referenced_by_any_line_item # ensure that there are no line items referencing this product def ensure_not_referenced_by_any_line_item if line_items.count.zero? return true else errors.add(:base, ‘Line Items present’ ) return false end end Include link to add to cart in store/index.html.erb <div class=“price_line” > <span class=“price” ><%= number_to_currency(product.price) %></span> <%= button_to ‘Add to Cart’ , line_items_path(:product_id => product) %> </div>
In controllers/line_items_controller.rb, include the method to create.
A Smarter cat $ rails generate migration add_quantity_to_line_item quantity:integer invoke active_record create db/migrate/20131226042106_add_quantity_to_line_item.rb Create a migration file to add quality to line_item. Add the quality column. class AddQuantityToLineItem < ActiveRecord::Migration def self.up add_column :line_items, :quantity, :integer, :default => 1 end def self.down remove_column :line_items, :quantity end end
Include a add_product method to the Cart in models/cart.rb def add_product(product_id) current_item = line_items.where(:product_id => product_id).first if current_item current_item.quantity += 1 else current_item = line_items.build(:product_id => product_id) end current_item end
Modify the controller/create of controllers/line_items_controller.rb def create @cart = current_cart product = Product.find(params) @line_item = @cart.add_product(product.id) respond_to do |format| if @line_item.save format.html { redirect_to(@line_item.cart, :notice => ‘Line item was successfully created.’ ) } format.xml { render :xml => @line_item, :status => :created, :location => @line_item } else format.html { render :action => “new” } format.xml { render :xml => @line_item.errors, :status => :unprocessable_entity } end end end
and the carts/show.html.erb to reflect the change of quantity <h2>Your Pragmatic Cart</h2> <ul> <% @cart.line_items.each do |item| %> <li><%= item.quantity %> × <%= item.product.title %></li> <% end %> </ul>
Create a new migrate to remove the items already created and recreate with the quantity $ rails generate migration combine_items_in_cart invoke active_record create db/migrate/20131226045904_combine_items_in_cart.rb
And create this methods def self.up # replace multiple items for a single product in a cart with a single item Cart.all.each do |cart| # count the number of each product in the cart sums = cart.line_items.group(:product_id).sum(:quantity) sums.each do |product_id, quantity| if quantity > 1 # remove individual items cart.line_items.where(:product_id=>product_id).delete_all # replace with a single item cart.line_items.create(:product_id=>product_id, :quantity=>quantity) end end end end
$ rake db:migrate
To handle with erros Change the cart_controller to receiver wrong product id. def show begin @cart = Cart.find(params) rescue ActiveRecord::RecordNotFound logger.error “Attempt to access invalid cart #{params}” redirect_to store_url, :notice => ‘Invalid cart’ else respond_to do |format| format.html # show.html.erb format.xml { render :xml => @cart } end end end To empty cart In cat/show.html.erb <%= button_to ‘Empty cart’ , @cart, :method => :delete, :confirm => ‘Are you sure?’ %>
In controllers/carts_controller.rb Empty the cart def destroy @cart = current_cart @cart.destroy session = nil respond_to do |format| format.html { redirect_to(store_url, :notice => ‘Your cart is currently empty’ ) } format.xml { head :ok } end end
Add a Dash of Ajax Include partials carts/_cart.html.erb
line_item/_line_item.html.erb <% if line_item == @current_item %> <tr id=“current_item” > <% else %> <tr> <% end %> <td><%= line_item.quantity %>×</td> <td><%= line_item.product.title %></td> <td class=“item_price” ><%= number_to_currency(line_item.total_price) %></td> </tr> In store/index.html.erb <%= button_to ‘Add to Cart’ , line_items_path(:product_id => product), :remote => true %> Change the line_items_controller.rb to receive the ajax and send to a js format.js { @current_item = @line_item } And create a views/line_items/create.js.rjs page.replace_html(‘cart’ , render(@cart)) page.visual_effect :blind_down if @cart.total_items == 1 page.visual_effect :highlight, :startcolor => “#88ff88”, :endcolor => “#114411”
in layouts/application.html.erb
Check out git checkout -b checkout
Product Cart -> Order \ / Line_Item
$ rails generate scaffold order name:string address:text email:string pay_type:string invoke active_record
create db/migrate/20131230025845_create_orders.rb
create app/models/order.rb
invoke test_unit
create test/models/order_test.rb
create test/fixtures/orders.yml
invoke resource_route
route resources :orders
invoke scaffold_controller
create app/controllers/orders_controller.rb
invoke erb
create app/views/orders
create app/views/orders/index.html.erb
create app/views/orders/edit.html.erb
create app/views/orders/show.html.erb
create app/views/orders/new.html.erb
create app/views/orders/_form.html.erb
invoke test_unit
create test/controllers/orders_controller_test.rb
invoke helper
create app/helpers/orders_helper.rb
invoke test_unit
create test/helpers/orders_helper_test.rb
invoke jbuilder
create app/views/orders/index.json.jbuilder
create app/views/orders/show.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/orders.js.coffee
invoke scss
create app/assets/stylesheets/orders.css.scss
invoke scss
identical app/assets/stylesheets/scaffolds.css.scss
$ rails generate migration add_order_id_to_line_item order_id:integer invoke active_record
create db/migrate/20131230025942_add_order_id_to_line_item.rb $ rake db:migrate
Pagination in gemfile gem ‘will_paginate’, ‘>= 3.0.pre’ $ bundle install Create some orders in depot/script/load_orders.rb Order.transaction do (1..100).each do |i| Order.create(:name => “Customer #{i}” , :address => “ #{i} Main Street” , :email => “customer- #{i}@example.com” , :pay_type => “Check” ) end end $ rails runner script/load_orders.rb in controllers/orders_controller.rb def index @orders = Order.paginate :page=>params, :order=>‘created_at desc’ , :per_page => 10 respond_to do |format| format.html # index.html.erb format.xml { render :xml => @orders } end end in app/views/orders/index.html.erb <p><%= will_paginate @orders %></p>
Sending email Three parts configuring how e-mail is to be sent determining when to send the e-mail specifying what you want to say. Configuring in config/environments/development.rb config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { :address => “smtp.gmail.com” , :port => 587, :domain => “domain.of.sender.net” , :authentication => “plain” , :user_name => “dave” , :password => “secret” , :enable_starttls_auto => true } Send e-mail Generate the mailer that works like the Controller. Intead of rendering, it “mail :to” $ rails generate mailer Notifier order_received order_shipped
create app/mailers/notifier.rb
invoke erb
create app/views/notifier create app/views/notifier/order_received.text.erb create app/views/notifier/order_shipped.text.erb invoke test_unit create test/mailers/notifier_test.rb In the created file app/mailers/notifier.rb class Notifier < ActionMailer::Base default :from => ‘Sam Ruby <depot@example.com>’ en.notifier.order_received.subject def order_received @greeting = “Hi” mail :to => “to@example.org” end def order_shipped @greeting = “Hi” mail :to => “to@example.org” end end E-mail template In app/vies/notifier/order_received.text.erb
Integration test $ rails generate integration_test user_stories invoke test_unit create test/integration/user_stories_test.rb
Logging in Adding Users Create model and database table to hold administrator, username and password Password will be stored in a 256-bit SHA2. $ rails generate scaffold user name:string hashed_password:string salt:string invoke active_record create db/migrate/20140103064302_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml invoke resource_route route resources :users invoke scaffold_controller create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb invoke test_unit create test/controllers/users_controller_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit create test/helpers/users_helper_test.rb invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder invoke assets invoke coffee create app/assets/javascripts/users.js.coffee invoke scss create app/assets/stylesheets/users.css.scss invoke scss identical app/assets/stylesheets/scaffolds.css.scss
$ rake db:migrate
app/models/user.rb class User < ActiveRecord::Base validates :name, :presence => true, :uniqueness => true
validates :password, :confirmation => true attr_accessor :password_confirmation attr_reader :password
validate :password_must_be_present private def password_must_be_present errors.add(:password, “Missing password” ) unless hashed_password.present? end end
Authenticating Users $ rails generate controller sessions new create destroy identical app/controllers/sessions_controller.rb route get “sessions/destroy” route get “sessions/create” route get “sessions/new” invoke erb exist app/views/sessions identical app/views/sessions/new.html.erb identical app/views/sessions/create.html.erb identical app/views/sessions/destroy.html.erb invoke test_unit identical test/controllers/sessions_controller_test.rb invoke helper identical app/helpers/sessions_helper.rb invoke test_unit identical test/helpers/sessions_helper_test.rb invoke assets invoke coffee identical app/assets/javascripts/sessions.js.coffee invoke scss identical app/assets/stylesheets/sessions.css.scss
$ rails generate controller admin index create app/controllers/admin_controller.rb route get “admin/index” invoke erb create app/views/admin create app/views/admin/index.html.erb invoke test_unit create test/controllers/admin_controller_test.rb invoke helper create app/helpers/admin_helper.rb invoke test_unit create test/helpers/admin_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/admin.js.coffee invoke scss create app/assets/stylesheets/admin.css.scss
In controllers/sessions_controller.rb def create if user = User.authenticate(params, params) session = user.id redirect_to admin_url else redirect_to login_url, :alert => “Invalid user/password combination” end end
Limiting Access In application_controller.rb before_filter :authorize
protected
def authorize
unless User.find_by_id(session[:user_id])
redirect_to login_url, :notice => "Please log in"
end
end
If you don't want to pass this filter use:
class StoreController < ApplicationController
skip_before_filter :authorize
class SessionsController < ApplicationController skip_before_filter :authorize
class CartsController < ApplicationController skip_before_filter :authorize, :only => [:create, :update, :destroy]
Internationalization $ git checkout -b i18n
create config/initializers/i18n.rb #encoding: utf-8 I18n.default_locale = :en LANGUAGES = [
['English' , 'en' ], ["Portugues".html_safe, 'pt' ] ] in config/routes.rb scope '(:locale)' do
resources :users resources :orders resources :line_items resources :carts resources :products do get :who_bought, :on => :member end root :to => ‘store#index’ , :as => ‘store’ end
In application_controller.rb class ApplicationController < ActionController::Base before_filter :set_i18n_locale_from_params
protected def set_i18n_locale_from_params if params if I18n.available_locales.include?(params.to_sym) I18n.locale = params else flash.now[:notice] = “ #{params} translation not available” logger.error flash.now[:notice] end end end def default_url_options { :locale => I18n.locale } end end
In views/layouts/application.html.erb t helper will look for name in template that begins with “.” <%= @page_title || t(‘.title’ ) %>
Fill config/locales/en.yml config/locates/pt.yml
To change the locale in views/layouts/application.html.erb <%= form_tag store_path, :class => ‘locale’ do %> <%= select_tag ‘set_locale’ , options_for_select(LANGUAGES, I18n.locale.to_s), :onchange => ‘this.form.submit()’ %> <%= submit_tag ‘submit’ %> <%= javascript_tag “$$(‘.locale input’).each(Element.hide)” %> <% end %> in controllers/store_controller.rb def index
if params[:set_locale] redirect_to store_path(:locale => params[:set_locale]) else @products = Product.all @cart = current_cart end end
Commiting $ git add . $ git commit -m “i18n” $ git checkout master $ git merge i18n