Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Val & Carly - OO Ride Share - Edges #18

Open
wants to merge 30 commits into
base: master
Choose a base branch
from

Conversation

valgidzi
Copy link

@valgidzi valgidzi commented Sep 1, 2018

OO Ride Share

Congratulations! You're submitting your assignment!

Comprehension Questions

Question Answer
Describe a design decision you had to make when working on this project. What options were you considering? What helped you make your final decision? We don't feel like we really made any major design decisions, but more so followed the design laid out in the project instructions and according to the given tests. One example of a design decision from the instructions is that the Driver class inherits from the User class.
Describe any examples of composition that you encountered in this project, if any One example of composition in this project is the User class contains an array of Trip instances.
Describe the relationship between User and Driver Driver inherits from the User class, four of its attributes and three of its methods. The net_expenditures method in the Driver class inherits and then modifies the User class net_expenditures method.
Describe a nominal test that you wrote for this assignment. For the User class net_expenditure method, we created a test to check that the method returns the total amount of money that a user has spent on their trips.
Describe an edge case test that you wrote for this assignment For the Driver class total_revenue method, we created a test to check that it returns zero if there are no driven trips.
Describe a concept that you/your pair gained more clarity on as you worked on this assignment We gained more clarity on inheritance, more specifically using super keyword in child class methods, and writing tests.
What are two discussion points that you and your pair discussed when giving/receiving feedback from each other that you would be willing to share? We discussed the need for independent comprehension time and how our learning styles differed, and we adjusted our workflow accordingly along the way.

CarlyReiter and others added 30 commits August 27, 2018 15:03
…p#initialize for TripDispatcher#request_trip method to work
@Hamled
Copy link

Hamled commented Sep 7, 2018

Ride Share

What We're Looking For

Feature Feedback
Baseline
Used Git Regularly
Answer comprehension questions
Wave 1
Appropriate use of Ruby's Time
Trip has a helper method to calculate duration
User (passenger) has a method to calculate total cost of all trips
Tests for wave 1
Wave 2
Driver inherits from User
Driver has add_driven_trip method
Driver has method to calculate average rating
Driver has method to calculate net expenditures and it uses super
Driver has a method to calculate total revenue
Tests for wave 2
I would also consider adding a test for add_driven_trip that checks the added trip is now included within the driven_trips array (right now we're only checking that the array has a new entry, but not that it's the right thing).
Wave 3
TripDispatcher has a new method to create trips
creating a trip in TripDispatcher relies on methods in Driver and User (passenger) to modify their own attributes Mostly.
On line 123 in TripDispatcher#request_trip we're directly setting the status for the driver on the new trip. The ideal, according to the OO principles of encapsulation and single responsibility, would be to have the Driver#add_driven_trip method check whether the new trip was finished (e.g. did it have a non-nil end_time), and if not set its own status to unavailable.
Complex logic was correctly implemented
Tests for request_trip
Methods from wave 1 and 2 handle incomplete trips No. It looks like the first step in implementing this piece would be to write a method on Trip that returns true/false if the trip is finished. Then we would need to call that method in User#net_expenditures, User#total_time_spent, etc.
Tests for wave 1 and 2 methods with incomplete trips No, I was unable to find any tests where a Trip had been set up without an end time. Apologies if I missed where this was done.
Wave 4 (Optional)
TripDispatcher now assigns trips to either the newest driver (no trips), or the driver who has not driven in the longest time No.
Appropriate helper methods were made to help with complex logic No.
Tests for wave 4 No.
Overall I think this was well done! I have many comments in there, but for the most part it's just suggestions of alternatives / extensions on what you've already got.


def calculate_duration
duration = @end_time - @start_time
return duration
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment: I think that a better name for this method would be simply duration.

The reason for this is due to the implication behind the phrasing "calculate duration". In that case we're explicitly saying that the duration will be calculated by this method (using the equation on line 30). This is in contrast to a method that simply returns the value of a previously saved/assigned instance variable.

Naming a method in a way that indicates how the method works is considered to be a more fragile approach than avoiding those details. Because if we end up needing to change the method's code, the name may no longer be accurate.

For this reason the Ruby convention for method names like this is to stick with a noun rather than a verb+object combination phrase. Examples:

Non-Ruby style Ruby style
Trip#calculate_duration Trip#duration
Person#find_house Person#house
Invoice#sum_line_items Invoice#price

@status = input[:status]

unless @status == :AVAILABLE
@status == :UNAVAILABLE
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is not doing what you probably intended it to do.

Please review this code and figure out what should be changed. Once you know what should be changed -- stop! Before you actually make the change, try to write a test first!

Figure out what kind of test would be necessary to verify that line 28 is working, then write that test. Running your tests should then show that the new one is failing. After that you can implement your fix, and run the tests again to verify that it worked.

If you have any trouble following the above suggestions, let me know!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less important comment on this unless statement: This code is probably a bit "too fragile", meaning it's coded with a very strict expectation of what data values might be in the @status variable.

As written, we're expecting that the only valid values for @status are :AVAILABLE and :UNVAILABLE. This is fine for now as the project requirements only involve those two options, but we could definitely imagine additional status options (like :LOCKED for a driver who has been banned or otherwise prevented from using the system).

In that case it might be simpler to write the conditional this way:

if @status.nil?
   # set @status to :UNAVAILABLE
end

With that above code we have logic that makes :UNAVAILABLE the default status, but we aren't preventing other values from being provided.

@driven_trips << driven_trip

unless driven_trip.is_a? Trip
raise ArgumentError
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check should happen as the first thing within this method.

Traditionally, raising an error like we're doing on this line means that the application stops right then. However, it is possible to use rescue blocks to catch those errors and allow the program to continue running.

In that situation, we will have added an invalid trip to the @driven_trips array, and the rest of the program may function incorrectly and result in more bugs or more incorrect results for users.

Finally, as with my previous comment, I suggest fixing this method by moving the check and raise lines to the beginning of the method. However, first y'all should write a test will fail until the method is fixed.

end

if @driven_trips.length == 0
average = 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment: This check could be implemented as an "early exit" as well:

def average_rating
  return 0 if @driven_trips.empty?

  # ... rest of the method

I also think it's worth considering whether zero is the correct return value when a driver has not yet driven any trips.

end

if @driven_trips.length == 0
total = 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this check is necessary for the total_revenue. We have the same check in average_rating, but it's necessary there to avoid potentially dividing by zero on line 56.

In this case we have no division, so it should be fine to run all of the code when @driven_trips is empty. However, this check does correct for one bug (but only in one case): without this check the method would return a negative value for drivers without any trips.

Clearly that would be an invalid result, so it's good that we're avoiding it. However, we're still possibly running into that bug when @driven_trips is not empty. If there was a driver who had one trip but the cost for that trip was less than $1.65, then total_revenue will still return a negative value.

So, my suggestion would be to remove the check about @driven_trips being empty, and instead use the same calculation in all cases. Then, have the method return a minimum value of zero: return [0, total].max.

expect(@driver.id).must_be_kind_of Integer
expect(@driver.name).must_be_kind_of String
expect(@driver.vin).must_be_kind_of String
expect(@driver.status).must_be_kind_of Symbol
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor: This test could be further dried up:

it "is set up for specific attributes and data types" do
  {id: Integer, name: String, vin: String, status: Symbol, driven_trips: Array}.each do |prop, type|
    expect(@driver).must_respond_to prop
    expect(@driver.send(prop)).must_be_kind_of type
  end
end

return @passengers.find { |passenger| passenger.id == id }
end
def request_trip(user_id)
available_drivers = @drivers.select { |driver| driver.status == :AVAILABLE}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line would be a good candidate for being moved into its own "helper" method. Doing so would be in keeping with the "separation of concerns" concept discussed in POODR.


trip = Trip.new(parsed_trip)
def create_trip(trip_data, driver, passenger, trip_list)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this method! When we have constraints/requirements that are somewhat "implicit", factory methods like this can help ensure that we don't write code that accidentally breaks those constraints.

In this case the Trip class has an implicit constraint that the associated Passenger and Driver must also include the trip instance in their own arrays. Because we have this factory method then we can more easily enforce that the only way to create a new Trip instance in this project is to call the create_trip method.

Minor note: Because the list of trips is provided as a parameter rather than assumed to be the @trips instance variable, this method could actually be a class method. That is usually the convention for factory methods, actually.

I would also consider that providing the trip list, and having code to push the new trip into that list, is probably a violation of "separation of concerns". It's possible that we could want to create a trip and not put it into an array, which is not supported by the current version of this method.

end

it "throws an argument error for a bad ID" do
expect { @dispatcher.find_driver(0) }.must_raise ArgumentError
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment: I think the correct behavior here would be for TripDispatcher#find_driver to return nil when no driver was found with the given ID.

The reasoning for this is that technically it's not an invalid argument -- the ID was actually an integer, it just didn't happen to match anything in our data set. Another reason for preferring to return nil is because Rails (which the all and find methods in these projects are based upon) also works that way.


it "selects first :AVAILABLE driver" do
trip = @dispatcher.request_trip(1)
expect(trip.driver.id).must_equal 5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a solid test! We could expand on it by requesting a second trip after the first one, and then verifying that we got the next available driver (and not driver #5 a second time).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants