1

This is an example from the Practical Object-Oriented Design in Ruby book. I am interested in translating this ruby code into javascript to have a better understanding of duck-typing in JS. Can anyone help translate this code to clarify how best to write this in Javascript or at least help get me started?

The trip class here is acting as the interface to the other classes (Mechanic, Trip_Coordinator, Driver). Where the prepare method in the class Trip uses the Duck type preparer.

class Trip
    attr_reader :bicycles, :customers, :vehicle

    def prepare(preparers)
      preparers.each {|preparer|
         preparer.prepare_trip(self)}
    end
 end

class Mechanic
  def prepare_trip(trip)
    # Does something with trip.bicycles
  end
end


class Trip_Coordinator
  def prepare_trip(trip)
    # Does something with trip.customers
  end
end

class Driver
  def prepare_trip(trip)
    # Does something with trip.vehicle
  end
end

Update:

I have added code that I believe is a possible translation of the Ruby code above. However, when I run the code I get the following output and error:

mountain
2
jeep

/home/ubuntu/user/tests/trip.js:3
       preparer.prepare_trip(this)

TypeError: Object [object Object] has no method 'prepare_trip'

Advice or suggestions would be appreciated to further improve the understanding of duck-typing in JS.

Javascript Code:

var Trip = function(preparers){
    return preparers.forEach(function(preparer){
       preparer.prepare_trip(this)
    }
)};

var Mechanic = function(trip){
    // does something with trip.bicycles                                                                                                                                                           
    prepare_trip: console.log(trip.bicycles);
};

var TripCoordinator = function(trip){
    //does something with trip.customers                                                                                                                                                           
    prepare_trip: console.log(trip.customers);
};

var Driver = function(trip){
    //does something with trip.vehicle                                                                                                                                                             
    prepare_trip: console.log(trip.vehicle);
};

// customer wants to go on a trip for two and needs a car                                                                                                                                          
var planA = {
    bicycles: "mountain",
    customers: 2,
    vehicle: "jeep"
};

//customer wants to go a trip for one and only ride a bike                                                                                                                                         
var planB = {
    bicycles: "road",
    customers: 1
};

Trip([new Mechanic(planA), new TripCoordinator(planA), new Driver(planA)]);
Trip([new Mechanic(planB), new TripCoordinator(planB)]);

UPDATE 2

Per the solution and advice from fgb below, I have place the final solution for my question below. I added an agent to remove the dependency of the caller having to know what prepares they would need when create a trip plan.

var Agent = function(plan){
    if("bicycles" && "customers" && "vehicle" in plan){
        Trip([new Mechanic(plan), new TripCoordinator(plan), new Driver(plan)]);
    }
    else if(!("vehicle" in plan) && "bicycles" && "customers" in plan){ //A driver is not needed                                                                                                                                        
        Trip([new Mechanic(plan), new TripCoordinator(plan)]);
    }
};

var Trip = function(preparers){
    return preparers.forEach(function(preparer){
       preparer.prepare_trip(this)
    }
)};
var Mechanic = function(trip){
    // does something with trip.bicycles                                                                                                                                                           
    this.prepare_trip = function() {
        console.log(trip.bicycles);
    }
};

var TripCoordinator = function(trip){
    //does something with trip.customers                                                                                                                                                           
    this.prepare_trip = function() {
        console.log(trip.customers);
    }
};

var Driver = function(trip){
    //does something with trip.vehicle                                                                                                                                                             
    this.prepare_trip = function() {
        console.log(trip.vehicle);
    }
};

// customer wants to go on a trip for two and needs a car                                                                                                                                          
var planA = {
    bicycles: "mountain",
    customers: 2,
    vehicle: "jeep"
};

//customer wants to go a trip for one and only ride a bike                                                                                                                                         
var planB = {
    bicycles: "road",
    customers: 1
};

Agent(planB);

Related SO discussion Example of Javascript Duck Typing?

6
  • You can use the Opal compiler to translate it to JavaScript automatically: sitepoint.com/opal-ruby-browser-basics Commented Oct 4, 2014 at 1:42
  • @AndersonGreen: How is a code generator going to help them understand duck typing in JavaScript? Commented Oct 4, 2014 at 2:26
  • @muistooshort If this is a learning exercise, then it would be better to translate it manually, of course. Commented Oct 4, 2014 at 2:30
  • There seems to be a lot missing here. Is the DT you're wondering about just the presence of a prepare_trip method? Have you tried to write this in JavaScript and come across any specific problems? Commented Oct 4, 2014 at 2:30
  • I updated the Mechanic method to add the missing end. Translating this manually would be more helpful than using an automatic translator. I have not translated this code yet into JS. The Duck Type I am wondering about is the ability of being able to add a perparer later on if need be. That was the main reasoning for this ruby example in the text. Making your code capable to future changes through greater abstraction. After I learn this technic in JS, I would like to apply this to my own programming JS interests. Commented Oct 4, 2014 at 2:42

1 Answer 1

1

The code is almost right, but you're mixing up the syntax for constructors and object literals. When you do:

var Driver = function(trip){
    //does something with trip.vehicle                                                                                                                                                             
    prepare_trip: console.log(trip.vehicle);
};

The 'prepare_trip:' is actually a label, and doesn't define a property of an object.

One way of fixing it would be:

var Driver = function(trip){
    //does something with trip.vehicle                                                                                                                                                             
    this.prepare_trip = function() {
        console.log(trip.vehicle);
    };
};

Also note the code isn't quite the same as the Ruby code:

preparer.prepare_trip(this);

Here, this is the global object instead of the trip object, and the parameter isn't used in the method. In your code the trip parameter is passed in the construstor of the preparer instead.

Sign up to request clarification or add additional context in comments.

2 Comments

Do you think the answer I supplied is the most efficient solution? Every time I call trip with a plan, a programmer has to know which preparers it will need to call. Do you think it would be better to refactor this code even further so that just the plan is passed to Trip and the Trip function makes the decision of which preparers need to be called based on a specific plan? Thanks again for the help.
I would consider that. It depends what kind of design you want. If the trip function can decide what preparers to call without the caller needing to specify them, then that could make the calling code simpler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.