Turn any Eloquent model into a list!

Related tags

Laravel listify
Overview

Listify

Turn any Eloquent model into a list!

Description

Listify provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a position column defined as an integer on the mapped database table. Listify is an Eloquent port of the highly useful Ruby gem acts_as_list (https://github.com/swanandp/acts_as_list).

Build Status Coverage Status

Latest Stable Version

Requirements

  • Listify currently requires php >= 5.5 (Listify is implemented via the use of traits).
  • Laravel 5.0 or higher

For use with Laravel 4, please use version 1.0.6.

Installation

Listify is distributed as a composer package, which is how it should be used in your app.

Install the package using Composer. Edit your project's composer.json file to require lookitsatravis/listify.

  "require": {
    "laravel/framework": "~5.0",
    "lookitsatravis/listify": "~1.2"
  }

Once this operation completes, the final step is to add the service provider. Open app/config/app.php, and add a new item to the providers array.

    'Lookitsatravis\Listify\ListifyServiceProvider'

Optionally, you can define an alias to the Listify trait. Open app/config/app.php, and add a new item to the aliases array.

    'Listify' => 'Lookitsatravis\Listify\Listify'

Quickstart

First things first, you'll need to add a column to store the position. From the command line, use the migration generator:

php artisan listify:attach {table_name} {position_column_name}
php artisan migrate

{table_name} is a required argument. {position_column_name} is optional and the default value is "position".

Then, in your model:

class User extends Eloquent
{
    use \Lookitsatravis\Listify\Listify;

    public function __construct(array $attributes = array(), $exists = false) {

        parent::__construct($attributes, $exists);

        $this->initListify();
    }
}

Make sure that the initListify() method is called after parent::__construct() of your model.

That's all it takes to get access to the Listify hotness.

Overview

Instance Methods Added To Eloquent Models

You'll have a number of methods added to each instance of the Eloquent model to which Listify is added.

In Listify, "higher" means further up the list (a lower position), and "lower" means further down the list (a higher position). That can be confusing, so it might make sense to add tests that validate that you're using the right method given your context.

Methods That Change Position and Reorder List

  • eloquentModel.insertAt(2)
  • eloquentModel.moveLower() will do nothing if the item is the lowest item
  • eloquentModel.moveHigher() will do nothing if the item is the highest item
  • eloquentModel.moveToBottom()
  • eloquentModel.moveToTop()
  • eloquentModel.removeFromList()

Methods That Change Position Without Reordering List Immediately

Note: a changed position will still trigger updates to other items in the list once the model is saved
  • eloquentModel.incrementPosition()
  • eloquentModel.decrementPosition()
  • eloquentModel.setListPosition(3)

Methods That Return Attributes of the Item's List Position

  • eloquentModel.isFirst()
  • eloquentModel.isLast()
  • eloquentModel.isInList()
  • eloquentModel.isNotInList()
  • eloquentModel.isDefaultPosition()
  • eloquentModel.higherItem()
  • eloquentModel.higherItems() will return all the items above eloquentModel in the list (ordered by the position, ascending)
  • eloquentModel.lowerItem()
  • eloquentModel.lowerItems() will return all the items below eloquentModel in the list (ordered by the position, ascending)

##Configuration

There are a few configuration options available. You'll need to pass these in as an array argument for initListify() in your model's constructor. Here are the options:

  • top_of_list sets the integer position for the top of the list (default: 1).
  • column sets the name of your position column that you chose during installation (default: 'position').
  • add_new_at sets the name of your position column that you chose during installation (default: 'bottom', options: 'top' or 'bottom').
  • scope allows you to scope the items in your list. This one requires a bit of elaboration. There are three posible values accepted:
    • string
    • Illuminate\Database\Eloquent\Relations\BelongsTo object
    • Illuminate\Database\Query\Builder object

###String

If string is passed in, a raw string is passed in as a whereRaw to the scope. This allows you to do something like 'custom_foreign_key = 42' and have all of the items scoped to that result set. You can pass as complicated of a where clause as you want, and it will be passed straight into each DB operation.

Example:

class User extends Eloquent
{
    use \Lookitsatravis\Listify\Listify;

    public function __construct(array $attributes = array(), $exists = false) {

        parent::__construct($attributes, $exists);

        $this->initListify([
            'scope' => 'answer_to_ltuae = 42'
        ]);
    }
}

Results in a scope of:

WHERE answer_to_ltuae = 42


###Illuminate\Database\Eloquent\Relations\BelongsTo

If Illuminate\Database\Eloquent\Relations\BelongsTo is passed in, Listify will match up the foreign key of the scope to the value of the corresponding foreign key of the model instance.

Example:

class ToDoListItem extends Eloquent
{
    use \Lookitsatravis\Listify\Listify;

    public function __construct(array $attributes = array(), $exists = false) {

        parent::__construct($attributes, $exists);

        $this->initListify([
            'scope' => $this->toDoList()
        ]);
    }

    public function toDoList()
    {
        $this->belongsTo('ToDoList');
    }
}

Results in a scope of:

WHERE to_do_list_id = {{value of toDoListItem.to_do_list_id}}


###Illuminate\Database\Query\Builder

And lastly, if Illuminate\Database\Query\Builder is passed in, Listify will extract the where clause of the builder and use it as the scope of the Listify items. This scope type was added in an attempt to keep parity between the acts_as_list version and Listify; however, due to differences in the languages and in ActiveRecord versus Eloquent, it is a limited implementation so far and needs impovement to be more flexible and secure. This is a big limitation and will be the first thing addressed in upcoming releases.

This one is tricky, because in order for it to work the query objects where array is prepared with the bindings outside of PDO and then passed in as a raw string. So, please keep in mind that this route can open your application up to abuse if you are not careful about how the object is built. If you use direct user input, please sanitize the data before using this as a scope for Listify.

Example:

class ToDoListItem extends Eloquent
{
    use \Lookitsatravis\Listify\Listify;

    public function __construct(array $attributes = array(), $exists = false) {

        parent::__construct($attributes, $exists);

        $this->initListify([
            'scope' => DB::table($this->getTable())->where('type', '=', 'Not A List of My Favorite Porn Videos')
        ]);
    }
}

Results in a scope of:

to_do_list_items.type = 'Not A List of My Favorite Porn Videos'


Changing the configuration

You may also change any configuration value during runtime by using $this->setListifyConfig('key', 'value');. For example, to change the scope, you can do this:

$this->setListifyConfig('scope', 'what_does_the_fox_say = "ring ding ding ding"');

When an update is processed, the original scope will be used to remove the record from that list, and insert it into the new list scope. Be careful here. Changing the configuration during processing can have unpredictable effects.

Notes

All position queries (select, update, etc.) inside trait methods are executed without the default scope, this will prevent nasty issues when the default scope is different from Listify scope.

The position column is set after validations are called, so you should not put a presence validation on the position column.

Future Plans

  • Add support for using a closure as a scope
  • Update Illuminate\Database\Query\Builder scope to be more secure and flexible
  • Additional features for the install command. Things like:
    • update the model with trait automatically (including init method in constructor)
    • generate (or add to) a controller with actions for each public method for Listify, including adding necessary routes. This would make it easy to, say, call something like http://localhost:8000/foos/1/move_lower through an AJAX-y front end.

Aside from that, I hope to just keep in parity with the Ruby gem acts_as_list (https://github.com/swanandp/acts_as_list) as necessary.

Contributing to Listify

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
  • Fork the project
  • Start a feature/bugfix branch
  • Commit and push until you are happy with your contribution
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the Composer.json, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
  • I would recommend using Laravel 5.0 and higher for testing the build before a pull request.

Copyright

Copyright (c) 2013-2017 Travis Vignon, released under the MIT license

Comments
  • Inifinite loop when scoping to a self-reference

    Inifinite loop when scoping to a self-reference

    I've got a hierarchical structure of pages in a site, so my model has a "belongsTo" relationship to itself (child page points to its parent page, and vice-versa). I want the position to be scoped within each parent, so users can sort pages within their parent... but since I am calling the "belongsTo" method in the model constructor, it just keeps calling this over and over again for each parent (eventually the parent's refer to themselves at the "root" of the hierarchy).

    I tried a few workarounds (using a string to specify the parent id, having the relationship method only call $this->belongsTo() when parent is not null)... but they all rely on the model knowing its parent id and unfortunately that data isn't available yet in the model constructor. Any other ideas on how I might address this problem?

    Thanks.

    opened by jordanlev 10
  • Soft Deletes Not Taken Into Account, Namely $model->restore()

    Soft Deletes Not Taken Into Account, Namely $model->restore()

    I know there is little detail here, but when using soft deletes, this package does not detect the "restoring" event to properly insert an item back into the list. Details to come.

    opened by adamgoose 5
  • Bugfix: Move item to bottom of list on scope change

    Bugfix: Move item to bottom of list on scope change

    The addToListBottom function was only setting the new list position if the item was not already in the list. This created an issue when updating an existing record to have a new scope (via the checkScope function)... because the record already exists so it was already in the list, and hence it was never getting its position number updated within the new scope.

    So I removed the if($this->isNotInList()) check in the addToListBottom function, and it works now.

    opened by jordanlev 4
  • Status of Laravel 6/7 release in develop branch

    Status of Laravel 6/7 release in develop branch

    Hey @lookitsatravis,

    I hope you're doing well man. Clay and I are working on getting the Clarus app upgraded to Laravel 7 and are currently sitting at v6.0 (https://github.com/eClinicHealthcare/eclinic-api/pull/121). I was digging around the codebase last night and stumbled upon the develop branch which it looks like you upgraded to be compatible w/ the newer versions of Laravel but never released.

    I was wondering what the status of the develop branch is? I don't see anything in the codebase that would cause problems w/ Laravel (everything looks pretty solid to me) but would it be possible for you to tag it as a new release (and possibly add Laravel 7 support via dependency requirement)? If there are things left to do before it works 100%, I can take care of whatever those things are and send a PR you way as well 😄.

    Thanks!

    opened by erikgall 3
  • Broken in Laravel 5.4.36 ?

    Broken in Laravel 5.4.36 ?

    This package seems to be broken in Laravel 5.4.36. It worked fine with "5.4 from this spring".

    Instead of updating the position column it creates a new item row altogether.

    opened by eminos 3
  • [Bug] `setListPosition` not work on database connection with `prefix`

    [Bug] `setListPosition` not work on database connection with `prefix`

    When use setListPosition() on a database connection with prefix config, we will received an error that the primary key field of Eloquent model is not matched with its database prefix.

    And this bugs will be fixed if we change this line of code

         /**
         * Returns the primary key of the current Eloquent instance
         * @return string
         */
        private function primaryKey()
        {
            return $this->getConnection()->getTablePrefix() . $this->getQualifiedKeyName();
        }
    
    opened by trungtnm 3
  • Added support for Laravel 5 ( not compatible with Laravel 4)

    Added support for Laravel 5 ( not compatible with Laravel 4)

    The Laravel 5 support that was added did not work, because Laravel 5 loads packages in a different way. So i modified the package because i needed it for a Laravel 5 application.

    opened by vlczero 3
  • Composer fixes

    Composer fixes

    This allows use of Illuminate >4 to allow support for upcoming Laravel 5.0.

    It also is explicit about what packages in Illuminate to require rather than requiring framework. For require-dev this still requires framework.

    opened by rtablada 3
  • Fix AttachCommand errors & finish adding support for Laravel 6 & 7

    Fix AttachCommand errors & finish adding support for Laravel 6 & 7

    Hey @lookitsatravis

    I think I finished off what needed to happen so this will work w/ Laravel 6 & 7. I used the develop branch as the base. The only thing I saw that I could tell needed to be fixed was the AttachCommand so I refactored that and made it work a little bit better instead of relying on views and having to register the migration view w/ Laravel. I ran all of your tests and they were all still passing (I didn't add any other tests because of limited time).

    I'm not sure if there was anything else that needed to be done in the core Listify trait but I pretty much left that alone.

    Let me know if we can get this merged and then merged into master w/ a new version tagged as well.

    Thanks man!

    opened by erikgall 2
  • Application::bindShared() deprecated since laravel 5.1

    Application::bindShared() deprecated since laravel 5.1

    This method was deprecated and removed in #4b57379 of laravel/framework

    Now it throws fatal exception:

    [RuntimeException] Error Output: PHP Fatal error: Call to undefined method Illuminate\Foundation\Application::bindShared() in /workspaces/itdevels/woodman/vendor/lookitsatravis/listify/src/Lookitsatravis/Listify/Lis tifyServiceProvider.php on line 55

    opened by Astyk 1
  • Double-quotes in query from Query Builder scope causing Postgres error

    Double-quotes in query from Query Builder scope causing Postgres error

    when getConditionStringFromQueryBuilder builds $theQuery (line 253), it's using double-quotes, which Postgres sees as a system identifier.

    so where "type" = "internal" throws an error saying that the column "internal" does not exist. changing it to single-quotes, as in where "type" = 'internal', fixes the issue

    Potential solution for Postgres queries:

    LINE 253:

    $theQuery .= '\'' . $bindings[$i] . '\'';
    
    opened by ccwittwer 1
Releases(1.1.0)
This package provides a trait that will generate a unique uuid when saving any Eloquent model.

Generate slugs when saving Eloquent models This package provides a trait that will generate a unique uuid when saving any Eloquent model. $model = new

Abdul Kudus 2 Oct 14, 2021
Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Rubik 4 May 12, 2022
A simple laravel state machine to handle model transitions, based on a pre-defined list of rules

A simple state machine that allows transitioning model states based on pre-defined rules. Installation You can install the package via composer: compo

Jack Mollart 18 Apr 2, 2022
Merge of staudenmeir/eloquent-eager-limit and staudenmeir/laravel-adjacency-list

This Laravel package merges staudenmeir/eloquent-eager-limit and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

Jonas Staudenmeir 7 Nov 1, 2022
A laravel package to handle sanitize process of model data to create/update model records.

Laravel Model UUID A simple package to sanitize model data to create/update table records. Installation Require the package using composer: composer r

null 66 Sep 19, 2022
A laravel package to generate model hashid based on model id column.

Laravel Model Hashid A package to generate model hash id from the model auto increment id for laravel models Installation Require the package using co

Touhidur Rahman 13 Jan 20, 2022
A package to filter laravel model based on query params or retrieved model collection

Laravel Filterable A package to filter laravel model based on query params or retrived model collection. Installation Require/Install the package usin

Touhidur Rahman 17 Jan 20, 2022
Laravel-model-mapper - Map your model attributes to class properties with ease.

Laravel Model-Property Mapper This package provides functionality to map your model attributes to local class properties with the same names. The pack

Michael Rubel 15 Oct 29, 2022
Collection of the Laravel/Eloquent Model classes that allows you to get data directly from a Magento 2 database.

Laragento LAravel MAgento Micro services Magento 2 has legacy code based on abandoned Zend Framework 1 with really ugly ORM on top of outdated Zend_DB

Egor Shitikov 87 Nov 26, 2022
Laravel Quran is static Eloquent model for Quran.

Laravel Quran بِسْمِ ٱللّٰهِ الرَّحْمٰنِ الرَّحِيْمِ Laravel Quran is static Eloquent model for Quran. The Quran has never changed and never will, bec

Devtical 13 Aug 17, 2022
A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache.

Laravel OTP Introduction A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache. The ca

Lim Teck Wei 52 Sep 6, 2022
🔥 Fire events on attribute changes of your Eloquent model

class Order extends Model { protected $dispatchesEvents = [ 'status:shipped' => OrderShipped::class, 'note:*' => OrderNoteChanged:

Jan-Paul Kleemans 252 Dec 7, 2022
Cast your Eloquent model attributes to Value Objects with ease.

Laravel Value Objects Cast your Eloquent model attributes to value objects with ease! Requirements This package requires PHP >= 5.4. Using the latest

Red Crystal Code 23 Dec 30, 2022
Add eloquent model events fired after a transaction is committed or rolled back

Laravel Transactional Model Events Add transactional events to your eloquent models. Will automatically detect changes in your models within a transac

Mark van Duijker 62 Dec 22, 2022
Eloquent model-caching made easy.

Model Caching for Laravel Supporting This Package This is an MIT-licensed open source project with its ongoing development made possible by the suppor

GeneaLabs, LLC 2k Dec 26, 2022
A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)

Laravel MongoDB This package adds functionalities to the Eloquent model and Query builder for MongoDB, using the original Laravel API. This library ex

Jens Segers 6.3k Jan 7, 2023
Generate UUID for a Laravel Eloquent model attribute

Generate a UUIDv4 for the primary key or any other attribute on an Eloquent model.

Alex Bouma 4 Mar 1, 2022
A DynamoDB based Eloquent model and Query builder for Laravel.

Laravel DynamoDB A DynamoDB based Eloquent model and Query builder for Laravel. You can find an example implementation in kitar/simplechat. Motivation

Satoshi Kita 146 Jan 2, 2023
Laravel package to create autonumber for Eloquent model

Laravel AutoNumber Laravel package to create autonumber for Eloquent model Installation You can install the package via composer: composer require gid

null 2 Jul 10, 2022