Skip to content

Instantly share code, notes, and snippets.

@leahgarrett
Last active April 1, 2019 06:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leahgarrett/4e5694ead5e43848b3c6ca647d77350e to your computer and use it in GitHub Desktop.
Save leahgarrett/4e5694ead5e43848b3c6ca647d77350e to your computer and use it in GitHub Desktop.
MVC

MVC

MVC is an architectural pattern

An architectural pattern is a general, reusable solution to a commonly occurring problem in software architecture within a given context. Architectural patterns are similar to software design pattern but have a broader scope. - https://en.wikipedia.org/wiki/Software_architecture

MVC is a commonly used pattern. It is used in Rails, AngularJS, BackboneJS, .Net MVC, Laravel PHP, and Java Spring MVC.


How to the patterns help us?

Separation of concerns: the established way for architects to reduce complexity is to separate the concerns that drive the design. - https://en.wikipedia.org/wiki/Software_architecture

We want to give different pieces of code different responsibilities. Basically we don’t want one piece of code trying to handle everything itself so we break up similar parts or functionality into different files.


MVC

MVC stands for model, view and controller. We place all of our code having to do with data storage inside of a model. All of the code that will be presented to the user (in Rails this will basically be the web page itself) is placed inside of a view. And the business logic or glue that connects these two pieces together goes into the controller!

The model encapsulates the data. The view is the presentation layer it's what the user sees and interacts with. The controller is going to process and respond to events such as user actions and it's going to invoke changes to the model in the view based on that it's going to make decisions for us and control what happens.


MVC Example

Model

We will use a bar example (similar to the party parrot) to explore this pattern. The scenario is that we want to order a drink from the bar.

What are the different parts of ordering a drink at a bar?

Let’s leave out the money side of things for now and focus on the 3 most important parts.

  • The information the customer is presented with
  • The bartender
  • The bar (holds all of our drinks)

The first step is to understand exactly what a model does in our MVC pattern. A model is used for interacting with all of our data. Any functionality such as creating, reading, updating or deleting is within the model.

So out of the 3 above pieces in our scenario which piece do you believe should go in a model?

The bar! Lets create a bar class that will hold all of our drinks.

class BarModel
    def initialize
        @drink_amounts = {
            beer: 0,
            martini: 0,
            coke: 0
        }
    end
end

Done? Nope! We haven’t added in any functionality yet.

What functionality might we need for our Bar model?

Getting a drink Knowing whats available Restocking Ok so now we know what our Model needs to do lets code it in.

class BarModel
    def initialize
        @drink_amounts = {
            beer: 0,
            martini: 0,
            coke: 0
        }
        restock(beer: 20, martini: 20, coke: 20)
    end
    
    def restock(beer: 0, martini: 0, coke: 0)
        @drink_amounts[:beer] += beer
        @drink_amounts[:martini] += martini
        @drink_amounts[:coke] += coke
    end

    def get_drink(key)
        if @drink_amounts[key] and @drink_amounts[key] - 1 >= 0
            return @drink_amounts[key] -= 1
        end
    end

    def get_available_drinks()
        drinks = []
        
        @drink_amounts.each do | key, value|
            if value > 0
                drinks.push(key)
            end
        end
        
        return drinks
    end
end

Now that we have our model which deals with all of our data (in this case our data is drinks). Lets move on to the view.


View

A view is all of our interaction with the user. In a web application a view is literally what the user can see. It is our rendered web page. In our example the view is going to be the different prompts and interactions with our customer trying to order a drink.

What are the different interactions we can have while ordering a drink at a bar?

  • Greeting
  • Reading menu
  • Ordering a drink
  • Being asked if I would like more
  • Goodbye message
  • Lets code all this into our view
class BarView
    def greeting
        puts "Hi there how can I help?"
    end

    def selection
        puts "Enter (1) to look at a menu or (2) to order a drink"
        gets.chomp.to_i
    end

    def read_menu(items)
        items.each do |item|
            puts item.capitalize
        end
    end

    def order
        puts "What would you like?"
        gets.chomp.downcase.to_sym
    end

    def wrong_answer
        puts "Sorry I didn't quite understand you"
    end

    def goodbye(item)
        puts "Here is your #{item}. Thaks for coming, see ya later!"
    end
end

So now we have to connect these two pieces together.


Controller

Our controller is the glue that connects our models and our views. This is where all of our logic goes to decide what to do next. In our scenario that would be the bartender. They are the one that will interact with the customer and also our bar.

class BarController
    def initialize
        @model = BarModel.new
        @view = BarView.new
    end

    def run
        selection = greet_customer_and_take_selection
        items = @model.get_available_drinks

        if selection == 1
            @view.read_menu(items)
        end

        order = take_order(items)
        @view.goodbye(order)
    end

    def greet_customer_and_take_selection
        @view.greeting

        while true
            answer = @view.selection
            if (answer != 1 and answer != 2)
                @view.wrong_answer
            else
                return answer
            end
        end
    end

    def take_order(items)
        while true
            order = @view.order
            drink_count = @model.get_drink(order)

            if drink_count != nil
                break
            end

            @view.wrong_answer
            @view.read_menu(items)
        end
    
        order
    end
end

Lets run our app and see it in action!

controller = BarController.new
controller.run

Now we could easily extend out our application to have more logic and more advanced interactions without our code becoming a jumbled mess. That’s what is so great about the MVC pattern. It allows us to build some pretty complex applications that don’t become cumbersome to maintain because all of the code has been abstracted into logic components.


Challenge details in `bar_runner.rb` below

Resources

Ruby on Rails: Understanding MVC architecture - short video
What is MVC - freecodecamp
10 Common Software Architectural Patterns in a nutshell

class BarController
def initialize
@model = BarModel.new
@view = BarView.new
end
def run
selection = greet_customer_and_take_selection
items = @model.get_available_drinks
if selection == 1
@view.read_menu(items)
end
order = take_order(items)
@view.goodbye(order)
end
def greet_customer_and_take_selection
@view.greeting
while true
answer = @view.selection
if (answer != 1 and answer != 2)
@view.wrong_answer
else
return answer
end
end
end
def take_order(items)
while true
order = @view.order
drink_count = @model.get_drink(order)
if drink_count != nil
break
end
@view.wrong_answer
@view.read_menu(items)
end
order
end
end
class BarModel
def initialize
@drink_amounts = {
beer: 0,
martini: 0,
coke: 0
}
restock(beer: 20, martini: 20, coke: 20)
end
def restock(beer: 0, martini: 0, coke: 0)
@drink_amounts[:beer] += beer
@drink_amounts[:martini] += martini
@drink_amounts[:coke] += coke
end
def get_drink(key)
if @drink_amounts[key] and @drink_amounts[key] - 1 >= 0
return @drink_amounts[key] -= 1
end
end
def get_available_drinks()
drinks = []
@drink_amounts.each do | key, value|
if value > 0
drinks.push(key)
end
end
return drinks
end
end
controller = BarController.new
controller.run
# Check if the code runs
# Add require_relative statements
# Run the code
# Use rubocop to clean up the code
# Add a blank line between questions
# Change the selection message to be 'Enter (1) to look at a menu or (2) to order a drink, (0) to quit'
# Change the code to repeatedtly take orders or enter 0 to quit
# Test what happens if someone tried to order a drink that is out of stock (change the stock levels from 20 to 2 for this)
# Add a better message for ordering a drink that is out of stock
# Beast++
# Look at refactoring other code to be MVC eg: terminal app
class BarView
def greeting
puts "Hi there how can I help?"
end
def selection
puts "Enter (1) to look at a menu or (2) to order a drink"
gets.chomp.to_i
end
def read_menu(items)
items.each do |item|
puts item.capitalize
end
end
def order
puts "What would you like?"
gets.chomp.downcase.to_sym
end
def wrong_answer
puts "Sorry I didn't quite understand you"
end
def goodbye(item)
puts "Here is your #{item}. Thanks for coming, see ya later!"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment