官术网_书友最值得收藏!

Generators for the application

The time has come to use the generators. We will follow the same flow as the previous chapter. We will develop the create travel section first and then the listing, followed by the search and finally the reservation.

Creating travel

Run the iron g:scaffold createTravel command and you will see the following files created and updated:

created app/lib/collections/create_travel.js
created app/client/templates/create_travel
created app/client/templates/create_travel/create_travel.html
created app/client/templates/create_travel/create_travel.js
created app/client/templates/create_travel/create_travel.css
created app/lib/controllers/create_travel_controller.js
updated ../BookMyTravel2/app/lib/routes.js
updated ../BookMyTravel2/app/server/publish.js

The generator has created a collection (create_travel.js) and a publication (in publish.js), which are not needed. We can ignore them. However, we will use the other components generated. Along with these files, we need a different layout for the creation screen. Let's create one by adding the create_travel directory inside the layouts directory. Add create_travel_layout.html inside the directory and add the following code to it:

<template name="CreateTravelLayout">
  <div class="create-container">
      <header class="header">
    <h1>
      {{#linkTo route="home"}}
        BookMyTravel2
      {{/linkTo}}
    </h1>
    <ul class="nav nav-pills">
      <li>{{#linkTo route="home"}}List{{/linkTo}}</li>
    </ul>
      </header>
      <section class="create-container__section">
    {{> yield}}
      </section>
      <footer class="footer">Copyright @BookMyTravel2</footer>
  </div>
</template>

We will add this layout to the CreateTravelController in create_travel_controller.js. Add the following piece of code as a property along with the subscriptions:

layoutTemplate: 'CreateTravelLayout',

Now, go to http://localhost:3001/create_travel and you will find that the layout reflects in the screen. Let's create the collection and then the form to create the documents in the collection. Run the following generator command to generate the busservice collection:

iron g:collection busservice

This command will create a busservice.js file in app/lib/collections. Go visit this file and you will find the busservice collection creation code as follows:

Busservice = new Mongo.Collection('busservice');

Followed by this line, there will be allow and deny methods that give us the ability to perform Role-Based Access (RBA) checks. Say, for example, if you want to deny a guest user from creating a bus service, you can do the check in the deny method's insert part and return true if positive. Doing so will deny the document insertion in the collection. You can use these methods to do extensive RBAC for your application. The skeleton is self-explanatory to explain the parameters we will get inside each operation. These insert, update, and remove operation pre-handlers will be executed before doing the respective operations via the collection instance (in our case Busservice). Different applications need different kinds of RBA conditions and so I leave this portion to you. Do not forget to make use of this in-built feature of MeteorJS as it is one of the important security features. We can perform authorization checks extensively using these handlers.

It is time to define the schema. If we cannot keep track of schema in a well-structured format, then it is going to be a big pain for maintenance. MeteorJS doesn't have built-in ways to create or define the schema. However, we have packages that can help us do this. We are going to use the aldeed: collection2 package, which will help us to attach a schema and do validation if required. The Git repo https://github.com/aldeed has some important packages that are very useful for development. Add the package by running the following command:

iron add aldeed:collection2

The schema for the busservice collection is as follows:

// Validation keys and messages
SimpleSchema.messages({
  source_destination_same: "[label] cannot be same as Starting point",
  destination_source_same: "[label] cannot be same as Destination point",
  endDateTime_lessthan_startDateTime: "[label] cannot be past to start date and time",
  startDateTime_lessthan_endDateTime: "[label] cannot be past to start date and time"
});

//Schema for busservice collection
BusServiceSchema = new SimpleSchema({
    name:{
      type: String,
      label: "Name",
      max: 200
    },
    agency:{
      type: String,
      label: "Agency",
      max: 1024
    },
    seats: {
      type: Number,
      label: "Total Seats",
      min: 10,
      max: 50
    },
    source: {
      type: String,
      label: "Starting Point",
      max: 200,
      custom: function() {
        if((this.value || "").toLowerCase() == (this.field("destination").value || "").toLowerCase()) {
          return "destination_source_same";
        }
      }
    },
    destination: {
      type: String,
      label: "Destination Point",
      max: 200,
      custom: function() {
        if((this.value || "").toLowerCase() == (this.field("source").value || "").toLowerCase()) {
          return "source_destination_same";
        }
      }
    },
    startDateTime: {
      type: Date,
      label: "Departure Time",
      min: moment().add(1, "days").toDate(),
      max: moment().endOf("year").toDate(),
      custom: function() {
        if(this.value >= this.field("startDateTime").value) {
          return "startDateTime_lessthan_endDateTime";
        }
      }
    },
    endDateTime: {
      type: Date,
      label: "Arrival TIme",
      min: moment().add(1, "days").toDate(),
      max: moment().endOf("year").toDate(),
      custom: function() { //custom validation
        if(this.value <= this.field("startDateTime").value) {
    //error message identifier added in SimpleSchema.messages api.
          return "endDateTime_lessthan_startDateTime";
        }
      }
    },
    fare: {
      type: Number,
      label: "Fare",
      min: 100
    },
    createdAt: {
      type: Date,
      label: "Created At",
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        }
      }
    },
    updatedAt: {
      type: Date,
      label: "Updated At",
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    },
    available_seats: {
      type: Number,
      label: "Available Seats",
      autoValue: function(doc) {
        if (this.isInsert) {
          return doc.seats;
        }
      }
    },
    createdBy: {
      type: String,
      optional: true,
      autoValue: function() {
        return this.userId
      }
    }
  });

Busservice.attachSchema(BusServiceSchema);

Add the preceding code after the busservice collection instantiation. The code is self-explanatory. For more details on the meteor-collection2 package, visit https://github.com/aldeed/meteor-collection2. You will find a lot of useful information and methods that can help you to do a better job in schema definition and validation. We can define many kinds of validations right in the schema definition itself, and the error messages can be defined along with the schema. If you go through the schema definition, you can figure out the minimum and maximum validations for some fields and custom validation for the arrival date and time and the departure date and time, which compares the dates. Similarly, one can define any custom validation and can access the document data from the form inside the custom validation methods.

The best part of defining a schema is the form creation. The aldeed repository has another package called AutoForm, which can read schema and create forms without much effort. Let's install the AutoForm package by running the following command:

iron add aldeed:autoform

Visit the AutoForm package documentation at https://github.com/aldeed/meteor-autoform. There is a lot of information to craft your forms carefully with plenty of options.

Inside the create_travel.html file in client/templates/create_travel, add the following template code:

{{> quickForm collection="Busservice" id="CreateBusServiceForm" type="insert" omitFields="createdBy, updatedAt, createdAt, available_seats" buttonContent="Create"}}

Visit the browser and you will find that the form is created with less effort. In the preceding code, we have just specified the collection name, which is the instance we created (not the mongo collection name). Along with that, we have specified id, type. There is an attribute omitFields that facilitates to omit fields that we don't want to show in the form. The buttonContent attribute value will appear in the submit button of the form. A single line has created a form for us. Pretty timesaving, isn't it?

The styles are missing. Add the twbs:bootstrap package to the application. This will add styles to some extent. The forms generated by AutoForm are bootstrap complaints with bootstrap-related classes in the markups. We will add the rest of the styles from the previous chapter. Copy the custom styles we had created in the previous chapter to the main.css file of this application. Now, the form is usable, but there are some more details we need to understand.

The schema that we had defined has the Date type for the fields for arrival time, departure time, createdAt, and updatedAt, which you might have noticed. They are Date and not DateTime. There is no DateTime type and so the AutoForm package will also generate forms with the appropriate fields having the Date type and not Datetime. In our case, the arrival time and departure time must be Datetime and not just Date. So, we cannot use the quickform component as it is. To solve this problem, when we need customization in the form, the AutoForm package has given us ways to define the customized form in compliance with the schema. Replace {{quickform ...}} with the following code:

{{#autoForm collection="Busservice" id="CreateBusServiceForm" type="method" class="container" meteormethod="createBusService"}}
    {{> afQuickField name='name'}}
    {{> afQuickField name='agency'}}
    {{> afQuickField name='seats' min="10" max="50"}}
    {{> afQuickField name='source'}}
    {{> afQuickField name='destination'}}
    {{> afQuickField name='startDateTime' type="datetime-local"}}
    {{> afQuickField name='endDateTime' type="datetime-local"}}
    {{> afQuickField name='fare'}}
    <button class="btn btn-lg btn-primary btn-block" type="submit">Create</button>
  {{/autoForm}}

Instead of using quickForm, we are using the autoForm and afQuickField components to create our form. You can learn the syntax and other possible attribute options from the AutoForm package documentation. What we have done here is a minimal usage. However, a notable thing is type and meteormethod. We have specified the type of the form to be method and when we use method as type, it is mandatory to mention the method name in the meteormethod attribute. What this means is, on submit, the meteor method createBusService will be called by passing all the field values. The afQuickField component takes the names that must be the keys defined in the schema and other form field attributes. We needed a datetime input field and thus we are using the type="datetime-local" attribute in the appropriate fields.

Another important feature of the AutoForm package is validation and error display. Based on the schema definition, the form values are validated and an appropriate error message is displayed right below the fields. Try wrong inputs in the fields and submit the form. You will see the error messages below the fields. We don't need to wire the error handling and the error message display. They are taken care by the package itself. Also, AutoForm provides hooks to perform any pre or post-submission operations, if necessary.

All the client-related work is done and we have to define the server method to insert the form. In our case, we just want to validate and insert the document. Create the createBusService method in app/server/methods.js as follows:

Meteor.methods({
  createBusService: function(busService) {
      busService.createdAt = new Date();
    busService.available_seats = parseInt(busService.seats, 10);
    check(busService, BusServiceSchema); //validates the form data against the schema in the server side
    Busservice.insert(busService);
  }
});

If all goes well, you will be able to save the data to the collection. Check in the database to confirm.

Listing and search

Next is the listing part. Visit http://localhost:3000 and you will find the text Find me in app/client/templates/home/home. Our home page is going to be the listing and search page. Let's start adding the layout, then the list template, and wire the data. Things start from the route. Our route for home page points to HomeController and action method. Check the HomeController and you will find that the layout is MasterLayout and the action method calls the render of the Home template.

In master_layout.html, add the following code that builds the two columns layout for our home page:

<template name="MasterLayout">
  <div class="home-container">
    <header class="header">
      <h1>
        {{#linkTo route="home"}}
          BookMyTravel2
        {{/linkTo}}</h1>
      <ul class="nav nav-pills">
        <li>
          {{#linkTo route="createTravel"}}
            Create
          {{/linkTo}}
        </li>
      </ul>
    </header>
    <section class="home-container__section">
      <div class="home-container__section__left container-fluid">
          {{> yield region="search"}}
        </div>
        <div class="main">
          {{> yield}}
        </div>
    </section>
    <footer class="footer">Copyright @BookMyTravel2</footer>
  </div>
</template>

This will create the two column layout. For maintainability purpose, instead of using the Home template from home.html, we will create the BusServiceList template using the generator and call it in the Home template. Run the following command in the root directory of the application:

iron g:template BusServiceList

The command will create the bus_service_list directory inside app/client/templates. We will use the bus_service_list.html file to define our listing template. Add the following code inside the template tag:

<div class="container bus-list">
  <div class="row bus-list__row bus-list__header">
    <div class="bus-list__row__col col-md-3">Bus</div>
    <div class="bus-list__row__col col-md-1">Available seats</div>
    <div class="bus-list__row__col col-md-1">Start point</div>
    <div class="bus-list__row__col col-md-1">End point</div>
    <div class="bus-list__row__col col-md-2">Start time</div>
    <div class="bus-list__row__col col-md-2">Reaching time</div>
    <div class="bus-list__row__col col-md-1">Fare</div>
    <div class="bus-list__row__col last col-md-1">Book</div>
  </div>
  <div class="row bus-list__body">
  {{#if hasItem}}
    {{#each list}}
      <div class="bus-list__row">
    <div class="bus-list__row__col col-md-3">{{name}} <br />{{agency}}</div>
    <div class="bus-list__row__col col-md- 1">{{available_seats}}/{{seats}}</div>
    <div class="bus-list__row__col col-md-1">{{source}}</div>
    <div class="bus-list__row__col col-md-1">{{destination}}</div>
    <div class="bus-list__row__col col-md-2">{{humanReadableDate startDateTime}}</div>
    <div class="bus-list__row__col col-md-2">{{humanReadableDate endDateTime}}</div>
    <div class="bus-list__row__col col-md-1">{{fare}}</div>
    <div class="bus-list__row__col last col-md-1"> <a href="/book/{{_id}}">Book</a></div>
    </div>
    <div class="clear"></div>
    {{/each}}
  {{else}}
  <div class="row bus-list__row bus-list__row-empty">
    <div class="bus-list__row__col last col-md-12 text-center"> No buses found</div>
  </div>
  {{/if}}
   </div>
   </div>

We have used some helper methods inside the template. Let's define them inside the bus_service_list.js file. Replace the helper's skeleton with the following code:

Template.BusServiceList.helpers({
  list: function() {
    return this.get();
  },
  hasItem: function() {
    return this.get().count();
  },
  humanReadableDate: function(date) {
    var m = moment(date);
    return m.format("MMM,DD YYYY HH:mm");
  }
});

One last thing to do is call the BusServiceList template in the Home template. Go to Home in home.html and replace the content with the following code:

<template name="Home">
  {{> BusServiceList}}
</template>

An empty list will be visible in the browser by this time. We have to wire the data. The busservice collection has to be published first. Let us use the generator itself to create the publication. Run the following command in the terminal:

iron g:publish busservice

This will add the publication code to publish.js in app/server. However, we need a slight modification here. Our publication should publish data, by default, in sorted order. So, let us change the return statement of the publication as follows:

return Busservice.find({}, {sort: {createdAt: -1}});

We have a proper place to subscribe the data. In HomeController, we have the subscriptions method where we can subscribe the data by name. Add the following subscription line of code to the subscriptions method:

this.subscribe("busservice", {});

Along with this, we will do the search as well. To give a small recap, because we are going to filter the same collection in the list template using the values from search template, we have used reactive variables. Whenever there is a search value, we filter the collection and update the reactive variable, which will update the list as per the search.

We will generate search-related templates and helpers using the generator. Run the following command:

iron g:template search

This will generate the search related files in the search directory under app/client/templates. In the Search template inside search.html, replace the existing content with the following code:

<div class="col-xs-12 col-sm-12 col-md-12 text-center top-space well well-sm">Search</div>
<div class="col-xs-12 col-sm-12 col-md-12 well well-sm">
    <div class="form" id="search-form">
         {{#autoForm collection="Busservice" id="SearchBusServiceForm"}}
          {{> afQuickField name='source'}}
      {{> afQuickField name='destination'}}
      {{> afQuickField name='startDateTime' type="date"}}
      {{> afQuickField name='fare'}}
      {{/autoForm}}
    </div>
</div>

The template is ready. To render this search template to the search region in MasterLayout, add the following code to the action method in HomeController:

this.render('Search', {to: 'search'});

This will render the Search template to the search region. Visit the browser and you will see the search form.

We have to introduce the reactive variable. To use reactive variables, we have to install the reactive-var package. Run the following command to add the package:

iron add reactive-var

Add the following initialization code to the beginning of the action method:

this.ReactiveBusServices = new ReactiveVar([]);

The controller supports the data method such as the subscriptions method. In the data method, we can prepare and format data that will be passed to the template to render. Add the following data preparation code to the HomeController:

data: function() {
    this.ReactiveBusServices.set(Busservice.find({}));
    return this.ReactiveBusServices;
},

From the code, you can figure out that we set the Busservice collection to the reactive variable we had created in the preceding snippet and then return the reactive variable. Visit the browser and you will find the list of services you had created earlier.

It is time to put the search in place. In search.js, inside the app/client/templates/search directory, replace the events skeleton with the following code:

Template.Search.events({
  "keyup input": _.throttle(function(e) {
    var source = $("[name='source']").val().trim(),
    destination = $("[name='destination']").val().trim(),
    date = $("[name='startDateTime']").val().trim(),
    fare = $("[name='fare']").val().trim(),
    search = {};
    if(source) search.source = {$regex: new RegExp(source), $options: "i"};
    if(destination) search.destination = {$regex: new RegExp(destination), $options: "i"};
    if(date) {
      var userDate = new Date(date);
      search.startDateTime = {
        $gte: userDate,
              $lte: new Date(moment(userDate).add(1, "day").unix()*1000)
          }
      }
    if(fare) search.fare = {$lte: parseInt(fare, 10)};
    if(Template.instance()) {
    Template.instance().data.set( Busservice.find(search, {sort: {createdAt: -1}}));
    }
  }, 200),
  "submit": function(e) {
    e.preventDefault();
  }
});

In the input field's keyup handler, we collect the form data, prepare them to be a proper search query, and filter the collection. Then, we set the filtered collection into the reactive variable. If you notice, Template.instance().data is the reactive variable we had returned from the data method of the HomeController. Perform a search and you will find that things are working as expected. Finally, listing and search is done.

Reservation

The last part of the application is blocking and reserving seats. We need the reservations and blocked_seats collections to store the information. Let's use the generator to generate the collection. Run the following commands to generate the collections:

iron g:collection reservations
iron g:collection blockedSeats

These commands will create two files, reservations.js and blocked_seats.js under app/lib/collections. Each file has its own instantiation to the collections, respectively. We will define the schema to each of these collections as we did for the busservice collection.

Add the following schema definition to blocked_seats.js:

BlockedSeats.attachSchema(
  new SimpleSchema({
    bus:{
      type: String,
      label: "Bus",
      max: 200
    },
    seat:{
      type: Number,
      label: "Blocked Seat"
    },
    createdAt: {
      type: Date,
      label: "Created At",
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        }
      }
    },
    updatedAt: {
      type: Date,
      label: "Updated At",
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    },
    createdBy: {
      type: String,
      optional: true,
      autoValue: function() {
        return this.userId
      }
    }
  })
);

Again, we can define validations if needed. I will leave that to you. Similarly, we will add the schema definition for reservations collection as well. Add the following code to reservations.js:

Reservations.attachSchema(
  new SimpleSchema({
    bus:{
      type: String,
      label: "Bus",
      max: 200
    },
    seats_booked:{
      type: [Object],
      label: "Seats Booked",
      minCount: 1,
      maxCount: 10
    },
    "seats_booked.$.seat": {
      type: Number,
      optional: false
    },
    createdAt: {
      type: Date,
      label: "Created At",
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        }
      }
    },
    updatedAt: {
      type: Date,
      label: "Updated At",
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    },
    createdBy: {
      type: String,
      optional: true,
      autoValue: function() {
        return this.userId
      }
    }
  })
);

Now, we have to define the route to reach the reservation part. We will use the generator to create the route. Run the following command to generate the route:

iron g:route book

The generator adds a route for us in the routes.js file and creates a BookController, templates, and helpers. The new route generated is not what we needed. Let's modify it to the way we want it to be. Change the route path from book to book/:_id. The route clearly says we need _id, which we will get from the listing. Also, we need the bus service information of the concerned bus, reservation information of the bus, and the blocked seats information of the bus. In the listing, we already have the link to the reservation page. Let's wire the proper data and create the templates to show the seating information and other required information.

We have to register the required publications first. As we did for the busservice collection, we will use generators to generate the publications for the reservations and blocked_seats collections. Run the following commands one after the other to generate them:

iron g:publish reservations
iron g:publish blocked_seats

To the generated publications, we need to do small modifications to pull the data of only the concerned bus service. Modify the code to look like the code as follows:

Meteor.publish('busservice', function (query) {
  query = query || {};
    return Busservice.find(query, {sort: {createdAt: -1}});
});
Meteor.publish('reservations', function (query) {
  return Reservations.find(query);
});
Meteor.publish('blocked_seats', function (query) {
  return BlockedSeats.find(query);
});

Note that we have also modified the busservice publication to accommodate the query parameter. Similarly, other publications will also get an object, based on which the data is published.

We will also use CreateTravelLayout for the reservation page. Add the layoutTemplate property to the controller and put the value as CreateTravelLayout.

The next step is subscribing to these data. In BookController, add the following piece of code to subscribe the data in the subscriptions method:

this.subscribe('busservice', {
        _id: this.params._id
    });
    this.subscribe('reservations', {
        bus: this.params._id
    });
    this.subscribe('blocked_seats', {
        bus: this.params._id
    });

Next, we have to pass the data to the template. Add the following code to the data method:

var templateData = {
    _id: this.params._id,
    bus: Busservice.findOne({
        _id: this.params._id
    }),
    reservations: Reservations.find({
        bus: this.params._id
    }).fetch(),
    blockedSeats: BlockedSeats.find({
        bus: this.params._id
    }).fetch()
};
return templateData;

This is the data that is available in the Book template.

Let us create the UI to show the seating arrangement. Replace the content of the Book template in book.html under app/client/templates/book, with the following piece of code:

<div class="container busView">
  <div class="row text-center busView__title">
    {{bus.name}}<br />{{bus.agency}}
  </div>
  <div class="row col-md-4 busView__seats">
    <div class="col-md-12 busView__left">
    {{#each seatArrangement}}
      <div class="col-md-12 row-fluid">
      {{#each this}}
      <div id="seat{{this.seat}}" class="busView__seat {{blocked}} {{reserved}}">
      {{this.seat}}
      </div>
      {{#if middleRow}}<div class="busView__divider col-md-offset- 3"></div>{{/if}}
      {{/each}}
      </div>
    {{/each}}
    </div>
  </div>
  <div class="row text-center busView__book"><button id="book" class="btn btn-primary">Book My Seats</button></div>
</div>

We need some helpers and event handlers to handle the interactions. Replace the skeleton events and helper methods with the following set of code:

Template.Book.events({
  "click .busView__seat:not(.reserved):not(.blocked)": function (e) {
    e.target.classList.add("blocked");
    var seat = {
      bus: Template.currentData().bus._id,
      seat: parseInt(e.target.id.replace("seat", ""), 10)
    };
    Meteor.call("blockThisSeat", seat, function(err, result) {
      if(err) {
        e.target.classList.remove("blocked");
      } else {
        var blockedSeats = Session.get("blockedSeats") || [];
        blockedSeats.push(seat);
        Session.set("blockedSeats", blockedSeats);
      }
    });
  },
  "click #book": function() {
    var blockedSeats = Session.get("blockedSeats");
    if(blockedSeats && blockedSeats.length) {
      Meteor.call("bookMySeats", blockedSeats, function (error, result) {
        if(result) {
          Meteor.call("unblockTheseSeats", blockedSeats, function() {
            Session.set("blockedSeats", []);
          });
        } else {
          alert("Reservation failed");
          console.log(error);
        }
      });
    } else {
      alert("No seat selected");
    }
  }
});

Template.Book.helpers({
  seatArrangement: function() {
    var arrangement = [],
      totalSeats = (this.bus || {}).seats || 0,
      blockedSeats = _.map(this.blockedSeats || [], function(item) {return item.seat}),
      reservedSeats = _.flatten(_.map(this.reservations || [], function(item) {return _.map(item.seats_booked, function(seat){return seat.seat;});})),
      tmpIndex = 0;
      Session.set("blockedSeats", this.blockedSeats);
    arrangement[tmpIndex] = [];
    for(var l = 1; l <= totalSeats; l++) {
      arrangement[tmpIndex].push({
        seat: l,
        blocked: blockedSeats.indexOf(l) >= 0 ? "blocked" : "", 
        reserved: reservedSeats.indexOf(l) >= 0 ? "reserved" : "",
      });
      if(l % 4 === 0 && l != totalSeats) {
        tmpIndex++;
        arrangement[tmpIndex] = arrangement[tmpIndex] || [];
      }
    }
    return arrangement;
  },
  middleRow: function () {
    return (this.seat % 2) === 0;
  }
});

I don't have to explain this code as you will be familiar with it from the previous chapter. Now, you will be able to see the seating arrangement of the bus in the browser. The only leftover portion is server-side handling.

Add the server methods to methods.js in app/server. We need three server methods that are called from the template handlers. Add the following code to Meteor.methods:

  bookMySeats: function(reservations) {
    var insertRes = reservations.map(function(res) {
      return {
        seat: res.seat
      }
    });
    return Reservations.insert({
      bus: reservations[0].bus,
      seats_booked: insertRes
    }, function (error, result) {
      if(result) {
        Busservice.update({_id: reservations[0].bus}, {
          $inc: {
            available_seats: -insertRes.length
          }
        }, function() {});
      }
    });
  },
  blockThisSeat: function(seat) {
    debugger;
    BlockedSeats.insert(seat, function(error, result) {
      console.log(error);
      if(error) {
        throw Meteor.Error("Block seat failed");
      } else {
        Meteor.setTimeout(function() {
          BlockedSeats.remove({_id: result});
        }, 600000);// 10 mins
      }
    });
  },
  unblockTheseSeats: function(seats) {
    seats.forEach(function (seat) {
      BlockedSeats.remove({_id: seat._id});
    });
  }

Now, you can block and reserve seats from the application. This not much, but what we have learned so far is pretty interesting, isn't it?

We have recreated the same old application in a much more maintainable way like a pro-developer. We have used advanced scaffolding techniques to build the application and have increases maintainability and predictability. Also, we have learned to secure database operations with the help of allow and deny methods available in the collection instances.

Is that all for this chapter? No. To become a real pro-developer, two more things are essential. One is debugging and another one is testing. We are going to cover both of them in this chapter.

主站蜘蛛池模板: 嘉义县| 陕西省| 龙井市| 拉孜县| 榆社县| 鹰潭市| 宣威市| 滦平县| 宜都市| 攀枝花市| 内丘县| 偏关县| 辉南县| 威海市| 乐陵市| 福鼎市| 邵阳市| 靖安县| 鄂尔多斯市| 兴国县| 龙泉市| 抚顺县| 梁平县| 凤山县| 古蔺县| 杨浦区| 博湖县| 富宁县| 临桂县| 新民市| 武胜县| 鞍山市| 英山县| 固安县| 织金县| 鄂托克旗| 平利县| 介休市| 金乡县| 泾阳县| 华安县|