# Models ### app/models/customer.rb ```ruby class Customer < ActiveRecord::Base has_many :tickets has_many :item_ownerships has_many :items, through: :item_ownerships has_many :comments, as: :commentable validates :name, presence: true end ``` ### app/models/item.rb ```ruby class Item < ActiveRecord::Base validates :name, presence: true end ``` ### app/models/item_ownership.rb ```ruby class ItemOwnership < ActiveRecord::Base # This class models the ownership relationship between items and customers # For example, you'd have just one entry in the "items" table for an # iPhone 6, and a separate row in the "item_ownerships" table for every # customer who owns one. # The "items" table defines the various types of items, and the "item_ownerships" # table records each unique customer/item combination by referencing the item # type id from the "items" table and the customer id from the "customers" # table. belongs_to :item belongs_to :customer # Require both a customer and item type to be specified validates :customer, presence: true validates :item, presence: true end ``` ### app/models/ticket.rb ```ruby class Ticket < ActiveRecord::Base belongs_to :customer has_and_belongs_to_many :item_ownerships has_and_belongs_to_many :service_tasks has_many :comments, as: :commentable # Can't create ticket without both customer and description. # Associated items are optional. validates :description, presence: true validates :customer, presence: true def total_services_price self.service_tasks.map(&:price).reduce(:+) end def total_parts_price self.service_tasks.map(&:total_parts_price).reduce(:+) end def total_parts_cost self.service_tasks.map(&:total_parts_cost).reduce(:+) end end ``` ### app/models/service_task.rb ```ruby class ServiceTask < ActiveRecord::Base has_many :part_requirements has_many :parts, through: :part_requirements validates :description, presence: true validates :price, numericality: true def total_parts_price self.parts.map(&:price).reduce(:+) end def total_parts_cost self.parts.map(&:cost).reduce(:+) end end ``` ### app/models/part.rb ```ruby class Part < ActiveRecord::Base belongs_to :part_requirement validates :name, presence: true validates :price, numericality: true validates :cost, numericality: true end ``` ### app/models/part_requirement.rb ```ruby class PartRequirement < ActiveRecord::Base belongs_to :service_task belongs_to :part validates :quantity, { numericality: { greater_than: 0 } } end ``` ### app/models/comments.rb ```ruby class Comment < ActiveRecord::Base belongs_to :commentable, polymorphic: true validates :commentable, presence: true validates :text, presence: true end ``` # Database Migration ### db/migrations/create_initial_tables.rb ```ruby class CreateInitialTables < ActiveRecord::Migration def change create_table :customers do |t| t.timestamps t.string :name # add any other fields you want for customers here end create_table :items do |t| t.timestamps t.string :name # add any other fields you want here end create_table :item_ownerships do |t| t.timestamps t.references :customer, index: true t.references :item, index: true t.timestamps end create_table :tickets do |t| t.timestamps t.string :description t.references :customer, index: true end create_join_table :items, :tickets do |t| t.index :item_id t.index :ticket_id end create_table :service_tasks do |t| t.timestamps t.string :description t.decimal :price, precision: 8, scale: 2 end create_join_table :service_tasks, :tickets do |t| t.index :service_task_id t.index :ticket_id end create_table :parts do |t| t.timestamps t.string :name t.decimal :price, precision: 8, scale: 2 t.decimal :cost, precision: 8, scale: 2 end create_table :part_requirements do |t| t.timestamps t.integer :quantity t.references :part t.references :service_task end create_table :comments do |t| t.timestamps t.references :commentable, polymorphic: true, index: true t.text :text end end end ``` # Usage ## Run Database Migrations ```bash $ rake db:migrate ``` ## Rails console for tinkering ```bash $ rails console ``` ```ruby # Lets add an iPhone 6 to the items table iphone6 = Item.create(name: "iPhone 6") # Now we'll create a service task definition for replacing an iPhone 6 screen replaceScreen = ServiceTask.create(description: "Replace iPhone 6 Screen", price: 30.00) # We can associate the parts needed (the actual screen) with the service task. # First, create the part: iphone6Screen = Part.create(name: "iPhone 6 Screen", cost: 30.00, price: 65.00) # Then add a new PartRequirement to the ServiceTask we just created: replaceScreen.part_requirements.create(part: iphone6Screen, quantity: 1) # Oh look, our first customer! brian = Customer.create(name: "Brian McKelvey") # And he has an iPhone 6. brian.items << Item.find_by_name("iPhone 6") # Now we can create a ticket for the broken screen: ticket = Ticket.create(customer: brian, description: 'Needs screen replaced.') # ...and add the "Replace iPhone 6 Screen" task to the ticket: ticket.service_tasks << replaceScreen # ...and add not just any old iPhone 6, but Brian's iPhone 6: ticket.item_ownerships << brian.item_ownerships.last # Because the "iPhone 6 Screen" part is linked to the "Replace iPhone 6 Screen" # service task, which is linked to the customer's ticket, we can now calculate # how much the parts will cost in addition to the labor cost. puts "Price breakdown:" puts "Labor: $ #{sprintf("%.2f", ticket.total_services_price)}" puts "Parts: $ #{sprintf("%.2f", ticket.total_parts_price)}" ```