Meteor: collection helpers or transform on FS.Coll

2019-09-17 03:24发布

问题:

With dburles:collection-helpers package you can add collection helpers on any Mongo.collection. But I can't do that on FS.Collection. I get TypeError: Object [object Object] has no method 'helpers'. Transform function doesn't work either.

var createUploader = function(fileObj, readStream, writeStream) {
    fileObj.uploadedBy = Meteor.users.find({_id: fileObj.uploader});
    readStream.pipe(writeStream);
};
Photos = new FS.Collection("photos", {
    stores: [
        new FS.Store.GridFS("photos", {transformWrite: createUploader})
    ],
    filter: {
        allow: {
            contentTypes: ['image/*']
        }
    }
});

Can't do this? Notice when a photo is inserted from the client FS.File gets userId, hence fileObj.uploadedBy = Meteor.users.find({_id: fileObj.uploader});

回答1:

Ok I know this is not the not-so-easy solution I was looking for. Since I am using publish-composite package. I can just publish users' data(only profile field) with photos. And on the client I can do template helper like this:

Template.photo.helpers({
    photoUploader: function() {
        var currentPhoto = Photos.findOne();
        var user = Meteor.users.findOne({_id: currentPhoto.uploader});
        return user.profile.name;
    },
});

and

<template name="photos">
    {{#each photos}}
      {{> photo}}
    {{/each}}
    ...

then

<template name="photo">
  {{photoUploader}}
  ...


回答2:

matb33-collection-helpers package works by applying a transform function to a collection. CollectionFS already has its own transform function applied, therefore you cannot overwrite that with ones from the collection helpers package.

As suggested at the issue tracker

Since CFS already applies a transform, it would not be a good idea to use collection helpers. You should be able to do pretty much the same thing by extending the FS.File prototype with your own functions, though.

You could define custom functions on the prototype. The prototype will have access to other properties of the doc through this so you would basically have the same functionality with collection helpers.

Another option would be to store the file related information on the individual file object during the insert as metadata such as:

Template.photoUploadForm.events({
  'change .photoInput': function(event, template) {
    FS.Utility.eachFile(event, function(file) {
      var newPhoto = new FS.File(file);
      newPhoto.metadata = {uploadedBy: Meteor.user().profile.name};
      Photos.insert(newPhoto, function (err, fileObj) {
        if (!err) console.log(fileObj._id + " inserted!")
      });
    });
  }
});

Your code can also be rewritten to implement a beforeWrite filter instead of a transformWrite as in

Photos = new FS.Collection("photos", {
    stores: [
        new FS.Store.GridFS("photos", {
          beforeWrite: function (fileObj) {
            fileObj.metadata = {uploadedBy: Meteor.user().profile.name};
          }
        })
    ],
    filter: {
        allow: {
            contentTypes: ['image/*']
        }
    }
});

Finally, you can opt to storing the ID of the user and publishing a reactive join

Photos = new FS.Collection("photos", {
    stores: [
        new FS.Store.GridFS("photos", {
          beforeWrite: function (fileObj) {
            fileObj.metadata = {
              uploadedBy: Meteor.userId()
            };
          }
        })
    ],
    filter: {
        allow: {
            contentTypes: ['image/*']
        }
    }
});

And for the publication, you can use reywood:publish-composite

Meteor.publishComposite('photosWithUsers', function() {
  return {
    find: function() {
      return Photos.find();
    },
    children: [
      {
        find: function(photo) {
          return Meteor.users.find(photo.uploadedBy, {
            fields: {username: 1, 'profile.name': 1}
          });
        }
      }
    ]
  };
});

Of course on the client, you need to subscribe to the photosWithUsers publication.

Now to access that information in the client, since you cannot apply a transform or a helper on the collectionFS documents, you can create a global template helper:

Template.registerHelper('getUsername', function(userId) {
  check(userId, String);
  var user = Meteor.users.findOne(userId);
  return user && user.profile.name + ' (' + user.username + ')';
});

Now you can use that helper in your templates:

<template name="somePhoto">
  {{#with FS.GetFile "Photos" photo}}
    <img src="{{url}}" alt="This photo has been uploaded by {{getUsername uploadedBy}}"> 
  {{/with}}
</template>

Template.somePhoto.helpers({
  photo: function() {
    return Photos.findOne();
  }
})