├── .meteor └── .gitignore ├── smart.json ├── package.js ├── smart.lock ├── README.md ├── publish_with_relations.coffee └── publish_with_relations_test.coffee /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | meteorite 3 | -------------------------------------------------------------------------------- /smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "publish-with-relations", 3 | "description": "Publish associated collections at once", 4 | "homepage": "https://github.com/erundook/meteor-publish-with-relations", 5 | "author": "Vitaly A. Sorokin", 6 | "version": "0.1.5", 7 | "git": "https://github.com/erundook/meteor-publish-with-relations.git" 8 | } 9 | 10 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | summary: "Publish associated collections at once." 3 | }); 4 | 5 | Package.on_use(function(api) { 6 | api.use('coffeescript', 'server'); 7 | api.add_files('publish_with_relations.coffee', 'server'); 8 | }); 9 | 10 | Package.on_test(function(api) { 11 | api.use('publish-with-relations'); 12 | 13 | api.add_files('publish_with_relations_test.coffee', 'server'); 14 | }); -------------------------------------------------------------------------------- /smart.lock: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": { 3 | "git": "https://github.com/meteor/meteor.git", 4 | "branch": "master", 5 | "commit": "f07715dc70de16a7aab84e56ab0c6cbd9c1f9dc6" 6 | }, 7 | "dependencies": { 8 | "basePackages": { 9 | "publish-with-relations": { 10 | "path": "" 11 | } 12 | }, 13 | "packages": { 14 | "publish-with-relations": { 15 | "path": "" 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Publish with relations 2 | 3 | Publish with relations builds on Tom's [gist](https://gist.github.com/tmeasday/4042603) 4 | to provide a convenient way to publish associated records. 5 | 6 | ## Installation 7 | 8 | Publish with relations can be installed with [Meteorite](https://github.com/oortcloud/meteorite/). 9 | From inside a Meteorite-managed app: 10 | 11 | ``` sh 12 | $ mrt add publish-with-relations 13 | ``` 14 | 15 | ## API 16 | 17 | ### Basics 18 | 19 | ```javascript 20 | Meteor.publish('post', function(id) { 21 | Meteor.publishWithRelations({ 22 | handle: this, 23 | collection: Posts, 24 | filter: id, 25 | mappings: [{ 26 | key: 'authorId', 27 | collection: Meteor.users 28 | }, { 29 | reverse: true, 30 | key: 'postId', 31 | collection: Comments, 32 | filter: { approved: true }, 33 | options: { 34 | limit: 10, 35 | sort: { createdAt: -1 } 36 | }, 37 | mappings: [{ 38 | key: 'userId', 39 | collection: Meteor.users 40 | }] 41 | }] 42 | }); 43 | }); 44 | ``` 45 | 46 | This will publish the post specified by id parameter together 47 | with user profile of its author and a list of ten approved comments 48 | with their author profiles as well. 49 | 50 | With one call we publish a post to the ```Posts``` collection, post 51 | comments to the ```Comments``` collection and corresponding authors to 52 | the ```Meteor.users``` collection so we have all the data we need to 53 | display a post. 54 | 55 | You can check another (more complex) example at this [gist](https://gist.github.com/erundook/5012259). 56 | 57 | ### Changelog 58 | #### v0.1.4 59 | - Foreign key can be an array now. Thanks [Tom](http://github.com/tmeasday)! 60 | 61 | ### Compatibility notes 62 | Use <= v0.1.1 for meteor version < 0.5.7 63 | 64 | -------------------------------------------------------------------------------- /publish_with_relations.coffee: -------------------------------------------------------------------------------- 1 | Meteor.publishWithRelations = (params) -> 2 | pub = params.handle 3 | collection = params.collection 4 | associations = {} 5 | publishAssoc = (collection, filter, options) -> 6 | collection.find(filter, options).observeChanges 7 | added: (id, fields) => 8 | pub.added(collection._name, id, fields) 9 | changed: (id, fields) => 10 | pub.changed(collection._name, id, fields) 11 | removed: (id) => 12 | pub.removed(collection._name, id) 13 | doMapping = (id, obj, mappings) -> 14 | return unless mappings 15 | for mapping in mappings 16 | mapFilter = {} 17 | mapOptions = {} 18 | if mapping.reverse 19 | objKey = mapping.collection._name 20 | mapFilter[mapping.key] = id 21 | else 22 | objKey = mapping.key 23 | mapFilter._id = obj[mapping.key] 24 | if _.isArray(mapFilter._id) 25 | mapFilter._id = {$in: mapFilter._id} 26 | _.extend(mapFilter, mapping.filter) 27 | _.extend(mapOptions, mapping.options) 28 | if mapping.mappings 29 | Meteor.publishWithRelations 30 | handle: pub 31 | collection: mapping.collection 32 | filter: mapFilter 33 | options: mapOptions 34 | mappings: mapping.mappings 35 | _noReady: true 36 | else 37 | associations[id][objKey]?.stop() 38 | associations[id][objKey] = 39 | publishAssoc(mapping.collection, mapFilter, mapOptions) 40 | 41 | filter = params.filter 42 | options = params.options 43 | collectionHandle = collection.find(filter, options).observeChanges 44 | added: (id, fields) -> 45 | pub.added(collection._name, id, fields) 46 | associations[id] ?= {} 47 | doMapping(id, fields, params.mappings) 48 | changed: (id, fields) -> 49 | _.each fields, (value, key) -> 50 | changedMappings = _.where(params.mappings, {key: key, reverse: false}) 51 | doMapping(id, fields, changedMappings) 52 | pub.changed(collection._name, id, fields) 53 | removed: (id) -> 54 | handle.stop() for handle in associations[id] 55 | pub.removed(collection._name, id) 56 | pub.ready() unless params._noReady 57 | 58 | pub.onStop -> 59 | for id, association of associations 60 | handle.stop() for key, handle of association 61 | collectionHandle.stop() 62 | -------------------------------------------------------------------------------- /publish_with_relations_test.coffee: -------------------------------------------------------------------------------- 1 | # a 'publication' that just records stuff 2 | class PubMock 3 | constructor: () -> 4 | @activities = [] 5 | 6 | added: (name, id) -> 7 | @activities.push {type: 'added', collection: name, id: id} 8 | 9 | changed: (name, id) -> 10 | @activities.push {type: 'changed', collection: name, id: id} 11 | 12 | removed: (name, id) -> 13 | @activities.push {type: 'removed', collection: name, id: id} 14 | 15 | ready: -> 16 | @activities.push {type: 'ready'} 17 | 18 | onStop: (cb) -> 19 | @_onStop = cb 20 | 21 | stop: () -> 22 | @_onStop() if @_onStop 23 | 24 | # will be overwritten 25 | Boards = Lists = Tasks = null 26 | settings = null 27 | 28 | prepare = (test) -> 29 | run = test.runId() 30 | Boards = new Meteor.Collection "boards_#{run}" 31 | Lists = new Meteor.Collection "lists_#{run}" 32 | Tasks = new Meteor.Collection "tasks_#{run}" 33 | 34 | # insert some data 35 | boardId = Boards.insert name: 'board' 36 | listId = Lists.insert name: 'list', boardId: boardId 37 | taskId = Tasks.insert name: 'task', listId: listId 38 | 39 | settings = 40 | collection: Boards 41 | filter: {} 42 | 43 | mappings: [{ 44 | collection: Lists 45 | key: 'boardId' 46 | reverse: true 47 | 48 | mappings: [{ 49 | collection: Tasks 50 | key: 'listId' 51 | reverse: true 52 | }] 53 | }] 54 | 55 | 56 | 57 | Tinytest.add "Publish with Relations: ready is only called once", (test) -> 58 | prepare(test) 59 | 60 | pub = new PubMock 61 | Meteor.publishWithRelations _.extend(settings, {handle: pub}) 62 | 63 | readys = (activity for activity in pub.activities when activity.type == 'ready') 64 | test.equal readys.length, 1 65 | 66 | Tinytest.add "Publish with Relations: Nested subscriptions stop", (test) -> 67 | prepare(test) 68 | 69 | pub = new PubMock 70 | Meteor.publishWithRelations _.extend(settings, {handle: pub}) 71 | 72 | count = pub.activities.length 73 | 74 | # stop the sub, but then insert some more stuff 75 | pub.stop() 76 | boardId = Boards.insert name: 'new board' 77 | listId = Lists.insert name: 'new list', boardId: boardId 78 | taskId = Tasks.insert name: 'new task', listId: listId 79 | 80 | # nothing new has happened 81 | test.equal pub.activities.length, count 82 | 83 | 84 | 85 | --------------------------------------------------------------------------------