Git Product home page Git Product logo

adonis-bumblebee's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar greenkeeper[bot] avatar rhwilr avatar romainlanz avatar spamoom avatar vincentducorps avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adonis-bumblebee's Issues

[Question] Accessing Manager from inside Transformer

Hi, just curious if there is a way to change serializer from inside the transformer.

Sometime when returning X-to-one relation it's better to change from DataSerializer into PlainSerializer (Resource.item()) since it will have another nesting variable inside the relations.

Already read the code, but can't find out myself. Thanks for the help.

How to access to pivot table data?

Hello :)

I have 3 tables :

  • users
  • skills
  • user_skill (pivot table)

In user_skill (the pivot table) I have a field for the level (rank) of the user on this skill.

How to get this field in my transformer?
Thanks

CODE BELLOW DOESN'T WORK

"Cannot read property 'rank' of undefined"

Model

class User extends Model {
  skills() {
    return this.belongsToMany('App/Models/Skill')
      .pivotTable('user_skill')
      .withPivot(['rank'])
  }
}

Controller

class UserController {
    async show({ params, request, response, transform }) {
        let user = await User.find(params.id)

        if (!user) {
          return response.status(404).json({ data: 'Resource not found' })
        }

        await user.loadMany(['skills'])

        return transform.include(['skills']).item(user, UserTransformer)
  }
}

Transformer

class UserTransformer extends TransformerAbstract {
  availableInclude() {
    return [
      'skills'
    ]
  }

  transform(user) {
    return {
      id: user.id,
      firstname: user.firstname,
      lastname: user.lastname
    }
  }

  includeSkills(user) {
    return this.collection(user.getRelated('skills'), skill => ({
        id: skill.id,
        name: skill.name,
        rank: skill.pivot.rank
    }))
}

How to handle .paginate metadata?

When transforming, for example, users collection and using .paginate to query them... Is it possible to get classic adonis response containing paginate metadata?

When using:

const users = await User.query().paginate()
return transform.collection(users, UserTransformer)

Expected JSON response would be:

{
	"data": {
		"total": 112,
		"perPage": 10,
		"page": 1,
		"lastPage": 12,
		"data": [{..... transformed users here .... } ....
...
}

Suggestion: return array of strings after transform

For example I have TagTransformer which should return tags as array of tag.title strings like:
tags: ['tag1', 'tag2', 'tag3' ... ]

Currently it's only possible to return array of tag objects like:
tags: [{title: 'tag1'}, {title: 'tag2'}, {title: 'tag3'} ...]

add before/after hooks

Currently on a project that I'm working on, we have implemented our custom transformers for data and we have logic to do something before transformation of each resource begin.

We use this to populate likes data for each home (user can like homes on our site). Before hook was made so we query likes table only once even if we are returning list of 20 homes per page...

This is part of our implemntation showing how we reuse one query data to populate a list of homes with user likes... So only two queries will happen. One that queries 20 homes, and one that queries likes of users for ids of homes in that 20 list.

class HomePopulator extends BasePopulator {

    async beforeHook(model, ctx, meta) {
        const currentUser = ctx.getUserId()
        // here we fetch all like metadata, using whereIn query (**thanks to before hook**)
        meta._likes = await HomeLike.query()
            .select(['home_id', 'type']) // we have likes, and type of like (happy smile, angry smile, etc...)
            .count('type as count')
            .whereIn('home_id', model.rows ? model.rows.map(o => o.id) : model.id)
            .groupBy(['home_id', 'type'])
        meta._likes = _.mapValues(_.groupBy(meta._likes, 'home_id'), value => _.map(value, i => _.pick(i, ['type', 'count'])))

        // also we are fetching data if currently logged in user already liked something from the list...
        meta._myLike = currentUser && await HomeLike.query()
            .whereIn('home_id', model.rows ? model.rows.map(o => o.id) : model.id)
            .where('user_id', currentUser)
        meta._myLike = _.keyBy(meta._myLike, 'home_id')

        return model
    }

     // this is almost identical logic as bumblebee transform...
    async populate(model, ctx, meta) {

        let payload = {
            id: model.id,
            slug: model.slug,
            title: model.title,
            // .... other key/values
        }

        // but here we can access before hook data, and connect data without queries...
        payload.likes = meta._likes[model.id]
        payload.myLike = meta._myLike[model.id] && meta._myLike[model.id].type

        return payload
    }
}

Include underscore not working

First of all, i'm not a native speaker and i used google translator to help me write this issue.
By now congratulations on the package. It is very useful and practical

I am having difficulties adding an underscore property in the available include

On my Transformer:

static get availableInclude () {
     return ['amount_used', 'administrator']
   }

includeAmountUsed (coupon) {
     const {amount_used} = coupon.toJSON ()

     return amount_used
   }

In Controller:

async show ({...}) {
...
const transformed = await transform
         .include ('amount_used, administrator')
         .item (coupon, CouponTransformer)

       return response.json (transformed)
}

JSON response example:

{
  "administrator": {
    "id": 12,
    "first_name": "Kate",
    "last_name": "Julien",
    "email": "[email protected]",
    "cpf": "58278510784",
    "date_birth": "2102-05-05T03: 00: 00.000Z"
  },
  "id": 8,
  "code": "jTh) vM5!",
  "valid_from": null,
  "valid_until": null,
  "type": "currency",
  "quantity": 995,
  "discount": "78.53"
}

Already tried a debug or add a console log in includeAmountUsed, but the function is not called. I have also tried renaming the function to includeAmount_used, includeAmount_Used.
If rename the property to amountUsed works perfectly, but I wanted JSON to be amount_used, can you help me? Thanks

Suggestion: make include{Name} method name accept context as second param

I belive this is sometimes needed.
For example, you want to transform users who have posts.
Posts can have status of unpublished, and when fetching a list of users you shouldn't see unpublished posts attached to a user. But, if you are an admin or you are a user that is accessing his own profile (logged in user). You would for example like to see all unpublished posts also.

This would be easy to implement if include{Name} could also have access to ctx param. For example:

includePosts(model, ctx) {
    const currentUser = ctx.auth.getUser()
    let query = model.posts()
    if(currentUser) {
            // modify query to your liking...
    } else {
            // make default query, not including for example unpublished posts
    }

    return this.collection(query.fetch(), TagTransformer)
}

A transformer must be a function or a class extending TransformerAbstract

Sometimes i see this error, but I do not understand why the error appears and disappears. Sometimes this is decided by a rebuild

Error: A transformer must be a function or a class extending TransformerAbstract
at Scope._getTransformerInstance (/app/node_modules/adonis-bumblebee/src/Bumblebee/Scope.js:204:11)
at Scope._fireTransformer (/app/node_modules/adonis-bumblebee/src/Bumblebee/Scope.js:121:38)
at Scope._executeResourceTransformers (/app/node_modules/adonis-bumblebee/src/Bumblebee/Scope.js:93:52)
at

An exception is thrown if an include function does not exist

If an include is listed as available or default, but the function does not exist, an exception is thrown.

this[includeName] is not a function

Instead a more readable exception should be throne like:

The include function "name" is not defined in the transformer "name"

Error: Transformer is not a function

is the first time i use this package, can you help me?

I'm getting this error when sending a request to the endpoint that uses the transform method.

captura de tela 2018-10-16 as 12 42 23

availableInclude not working as expected

When I try to use the include method I don't get the expected response, it's like the "transform.include('product')" I used was ignored. But when i try to do the same using the defaultInclude, it's work.

right now im using the following method on my controller:

async index({ request, response, pagination, transform }) {
	const query = Category.query()

	let categories = await query.paginate(pagination.page, pagination.limit)
	categories = await transform.include('product').paginate(categories, Transformer)
	
	return response.send(categories)
}

and thats on my transformer class:

class ProductsCategoryTransformer extends BumblebeeTransformer {
	static get availableInclude() {
		return ['product', 'category']
	}
	
	transform(model) {
		return {
			category_id: model.category_id,
			product_id: model.product_id,
		}
	}

	includeCategory(model) {
		return this.item(model.getRelated('category'), CategoryTransformer)
	}

	includeProduct(model) {
		return this.item(model.getRelated('product'), ProductTransformer)
	}
}

module.exports = ProductsCategoryTransformer

.paginate can't call getRelated method

As the title says, it seams like it's impossible to transform related content if using .paginate() instead, for example, .collection().

This will work (but it will not return adonis pagination info):

const posts = await Posts.query().with('tags').paginate()
transform.include('tags').collection(posts, PostTransformer)

This will not work:

const posts = await Posts.query().with('tags').paginate()
transform.include('tags').paginate(posts, PostTransformer)

Error thrown is: model.getRelated is not a function... PostTransformer has this include:

includeTags(model) {
        return this.collection(model.getRelated('tags'), TagTransformer)
    }

Without .include() and defaultIncludes inside transformer, .paginate() works as expected

Error in README

I think there is a mistake in the README.md :

Shouldn't this :

const TransformerAbstract = use('Adonis/Addons/Bumblebee/TransformerAbstract')

class UserTransformer extends TransformerAbstract {
  transform (model) {
    return {
      id: model.id,

      firstname: user.first_name,
      lastname: user.last_name
    }
  }
}

module.exports = UserTransformer

be this instead :

const TransformerAbstract = use('Adonis/Addons/Bumblebee/TransformerAbstract')

class UserTransformer extends TransformerAbstract {
  transform (model) {
    return {
      id: model.id,

      firstname: model.first_name,
      lastname: model.last_name
    }
  }
}

module.exports = UserTransformer

? (model instead of user)

PS: Thank you for your job!

eagerloadIncludedResource tries to load when not needed

Let's say that we have users who have avatar image relation in another media table.

User is not required to have avatar uploaded, so sometimes this relation can be NULL.

If you do the query:
const users = await User.query().with('avatar').fetch()
And then afterwards you transform it via transformer
transform.include('avatar').collection(users, UserTransformer)

Bumbleebee will inside TransformerAbstract eagerloadIncludedResource try to loadMany avatar for each user that has this realtion as NULL.

Suggestion:
eagerloadIncludedResource filter should change from:
return (data[resource] instanceof Function) && !data.getRelated(resource)
to:
return (data[resource] instanceof Function) && !data.getRelated(resource) && data.$relations[resource] !== null

Access transform object from services

Hi!

I'm not being able to see any documentation on how I can access 'transform' instance outside controller. Is such a thing bad design choice, or is there any way that I can do that.

I have an ProductController and a ProductService, instead of transforming data in controller I would like to be able to use transform object inside service, something like this
const transform = use('Transform')
and then do the transformation.

Thanks :)

Honor setHidden and setVisible

Thanks for creating such a useful and easy to use library!

I have just started playing around with it and have bumped into something I would like to hear your thoughts about. I am used to applying setHidden or setVisible in my queries to filter out redundant data. For example, consider the following query:

const study = Study
    .where('id', params.id)
    .with('jobs.variables')
    .with('participants')
    .first()

This provides me with the result:

{
  "data": {
    "jobs": [
      {
        "variables": [
          {
            "id": 1,
            "study_id": 1,
            "dtype_id": 1,
            "name": "distractor",
            "created_at": "2020-06-30T12:40:02.000Z",
            "updated_at": "2020-06-30T12:40:02.000Z"
          }
        ],
        "id": 1,
        "study_id": 1,
        "order": 1,
        "created_at": "2020-06-30T12:40:02.000Z",
        "updated_at": "2020-06-30T12:40:02.000Z"
      },
      {
        "variables": [
          {
            "id": 1,
            "study_id": 1,
            "dtype_id": 1,
            "name": "distractor",
            "created_at": "2020-06-30T12:40:02.000Z",
            "updated_at": "2020-06-30T12:40:02.000Z"
          }
        ],
        "id": 2,
        "study_id": 1,
        "order": 2,
        "created_at": "2020-06-30T12:40:02.000Z",
        "updated_at": "2020-06-30T12:40:02.000Z"
      }
    ],
    "id": 1,
    "name": "Attentional Capture",
    "description": "Basic attentional capture experiment",
    "active": 1,
    "osexp_path": "/public/osexp/attentional-capture.osexp",
    "created_at": "2020-06-30T12:40:02.000Z",
    "updated_at": "2020-06-30T12:40:02.000Z"
  }
}

Because the data of jobs is provided as related data for study, I for instance don't see any use for including study_id in each job record (and often I leave away even more data). I used to omit superfluous data by writing my first query as:

const study = await auth.user.studies()
  .where('id', params.id)
  .with('jobs', (builder) => {
    builder
      .setHidden(['study_id'])
      .with('variables')
  })
  .with('participants')
  .first()

This effectively hides away all study_id fields in job records if I simply return {data: study} at the end of the controller. However, if I use a transformer, this study_id field is present again! Thus if I end the controller action with:

return transform
  .include('jobs')
  .item(study, 'StudyTransformer')

where my transformers look like:

class StudyTransformer extends BumblebeeTransformer {
  static get availableInclude () {
    return ['jobs', 'variables', 'participants']
  }

  transform (model) {
    return { ...model.$attributes }
  }

  includeJobs (study) {
    return this.collection(study.getRelated('jobs'), 'JobTransformer')
  }
  ...
}

class JobTransformer extends BumblebeeTransformer {
  static get defaultInclude () {
    return ['variables']
  }

  transform (model) {
    return { ...model.$attributes }
  }

  includeVariables (job) {
    return this.collection(job.getRelated('variables'), 'VariableTransformer')
  }
}

I know the problem probably lies in my usage of return {...model.$attributes} in the transform function, but shouldn't its usage be subjected to or honor the 'setVisible/Hidden' too?

Suggestion: try to reuse .getRelated instead of always fetching on include

This is solvable using "smarter" code inside incude methods, but imho this package should try and handle this out of the box.

So what's the issue?

Let's take Transformer from example in README.md:

class BookTransformer extends TransformerAbstract {
  defaultInclude () {
    return [
      'author'
    ]
  }

  transform (book) {
    return {
      id: book.id,
      title: book.title,
      year: book.yr
    }
  }

  includeAuthor (book) {
    return this.item(book.author().fetch(), AuthorTransformer)
  }
}

module.exports = BookTransformer

If someone is going to show 10 books, including authors, he will probably do it something like this:

    async index({transform}) {
        const books = await Book.all()

        return transform.include('author').collection(books)
    }

This will do 11 queries... :(

To optimize this a bit, one can do something like:

    async index({transform}) {
        const books = await Book.query().with('author').fetch()

        return transform.include('author').collection(books)
    }

Now, Lucid will select all books with authors in 2 queries... and transformer needs to be updated a bit:

  includeAuthor (book) {
    return this.item(book.getRelated('author'), AuthorTransformer)
  }

So by using .getRelated('author') instead of .author().fetch(), we lowered amount of queries by 9!

For now, I'm using include methods like this:

    includeAuthor(book) {
        const author = book.getRelated('author') || book.author().fetch()

        if(author instanceof Promise) Logger.warning('Lazy load detected!')

        return this.item(author, AuthorTransformer)
    }

It would be awesome if include could be a little bit smarter and try to reuse already queried relations if possible...

What do you think?

A transformer must be a function or a class extending TransformerAbstract

When I try to use any of the following: "ProductController.index" or "CategoryController.show" everything works as expected. But after making the first one, at the next request I get the following error message: "A transformer must be a function or class extending TransformerAbstract" .

Below is the summary of the classes that I am using.
 

class Product extends Model {
    categories() {
        return this.belongsToMany('App/Models/Category', 'product_id', 'category_id').pivotTable('product_categories')
    }
}
class Category extends Model {
    products() {
        return this.belongsToMany('App/Models/Product', 'category_id', 'product_id').pivotTable('product_categories')
    }
}
class ProductCategorySchema extends Schema {
	up() {
		this.create('product_categories', table => {
			table.increments()
			table
				.integer('category_id')
				.unsigned()
				.references('id')
				.inTable('categories')
				.onDelete('cascade')
			table
				.integer('product_id')
				.unsigned()
				.references('id')
				.inTable('products')
				.onDelete('cascade')
		})
	}
}
class ProductController {
    async index({ request, response, pagination, transform }) {
        const query = Product.query()
        let products = await query.paginate(pagination.page, pagination.limit)
        products = await transform.include('categories').paginate(products, Transformer)
        return response.send(products)
    }
}
class CategoryController {
	async show({ params: { id }, response, transform }) {
		let category = await Category.findOrFail(id)
		category = await transform.include('products').item(category, Transformer)
		return response.send(category)
	}
}
class ProductTransformer extends BumblebeeTransformer {
	static get availableInclude() {
		return ['categories']
	}
	includeCategories(model) {
		return this.collection(model.getRelated('categories'), CategoryTransformer)
	}
}
class CategoryTransformer extends BumblebeeTransformer {
	static get availableInclude() {
		return ['products']
	}
	includeProducts(model) {
		return this.collection(model.getRelated('products'), ProductTransformer)
	}
}

Some ideas for a future release

Hey all 👋

First of all, thanks for this package. It's really useful and helps to keep an API consistent and simple.

I've been working with Bumblebee for some projects and found some things that could be changed to improve the developer experience. I can work on them if they are accepted.


1. Accept namespace instead of an instance of transformers

Currently, we need to require the transformer in our controller (or another transformer) and then use this require to use it.

const UserTransformer = use('App/Transformers/UserTransformer')

...

return transform.collection(users, UserTransformer)

It could be better to directly use the namespace.

return transform.collection(users, 'App/Transformers/UserTransformer')
// or via default prefix
return transform.collection(users, 'UserTransformer')

2. Rename method toArray => toJSON
When we use the fluent interface the latest method to call is named toArray(). In fact, this method transforms your object to JSON syntax, so it would be better to name it toJSON().


3. Use JS getter
When we define our own transformer we make use of JS method. In the meanwhile, using static JS getter would be way faster (~2x).

// before
class BookTransformer extends TransformerAbstract {
  defaultInclude () {
    return ['author']
  }
}

// after
class BookTransformer extends TransformerAbstract {
  static get defaultInclude () {
    return ['author']
  }
}

4. Defines the transformer for collection and item
Currently, we have only one transform method inside our custom transformer. It could be better to have a transformCollection and transformItem when you need a different schema for a collection and an item.

For the moment, you need to create two transformers ArticleItemTransformer ArticleCollectionTransformer.


Let me know what you think about them.

Namespaced Transformers Support

Hi, again...

I think is good to implement a way for developers create transformers inside Folders.

E.g. User Run: adonis make:transformer Products/Product

The output should be a file inside the folder app/Transformers/Products/ProductTransformer.js

So, i've just created a Pull Request supporting this feature.

Chain Transformers with Routes

It would be nice to do:

Route
  .get('/users', 'UsersController.list')
  .validator('vue/Pagination')
  .middleware(['auth'])
  .transform('UsersTransformer.withPosts')

Is it hard to implement?

Thank you for addon!

Includes don't work for many to many relations

Hi guys! Now I'm creating my first project with adonisJS and in fact i enjoy it at all, but there is a problem with bumblebee transformer and i could not find any solution for that. When I use "include" method in transformer for many to many relations - it doesn't work and there is no kind of error, it just ignores relations. I'm doing all straight by docs, "include" works for belongsTo relations and doesn't work for "pivot table" to get all related entities for transforming model. Can you help me and describe why it does't it work properly with many to many relation, how could you miss that moment (i don't believe you could, but it seems to me that it's a pure fact) and which solution for many to many relations in transformer i can get out from box? Thank you!

property transform does not exits in ctx

Hey Geeks,
After installing transformer I found out property transform does not exist in httpContextContract in adonis JS even after installing bumblebee-transformer.When will it be available or will v5 make us use bumblbee transformer like mentioned.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.