├── .gitignore ├── .travis.yml ├── composer.json ├── phpunit.xml ├── provides.json ├── readme.md ├── src └── Rtablada │ └── EloquentRankable │ ├── RankableCollection.php │ └── RankableModel.php └── tests └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | before_script: 9 | - curl -s http://getcomposer.org/installer | php 10 | - php composer.phar install --dev 11 | 12 | script: phpunit -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtablada/eloquent-rankable", 3 | "description": "A Rankable Eloquent Model Package", 4 | "keywords": [ 5 | "laravel", 6 | "lpm", 7 | "eloquent", 8 | "orm", 9 | "rankable" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Ryan Tablada", 14 | "email": "ryan.tablada@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.3.0", 19 | "illuminate/support": "4.0.x", 20 | "illuminate/database": "4.0.x" 21 | }, 22 | "autoload": { 23 | "psr-0": { 24 | "Rtablada\\EloquentRankable": "src/" 25 | } 26 | }, 27 | "minimum-stability": "dev", 28 | "license": "MIT" 29 | } 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /provides.json: -------------------------------------------------------------------------------- 1 | { 2 | "providers": [ 3 | ], 4 | "aliases": [ 5 | { 6 | "alias": "Rankable", 7 | "facade": "Rtablada\EloquentRankable" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Eloquent Rankable 2 | ================== 3 | 4 | This package makes rankable models for sorting quick and easy. 5 | 6 | Setting up a Rankable Model 7 | --------------------------- 8 | 9 | Making Rankable Models is just as easy as creating regular Eloquent Models with just one more property `protected $metricsWeight`! 10 | 11 | An example model would look something like this: 12 | 13 | ```php 14 | use Rtablada\EloquentRankable\RankableModel; 15 | 16 | class Friend extends RankableModel 17 | { 18 | protected $metricWeights = array( 19 | 'search' => 0.8, 20 | 'name' => 0.2 21 | ); 22 | 23 | protected $fillable = array('name', 'rank'); 24 | } 25 | ``` 26 | 27 | In your Schema remember to include a `rank` column (I suggest using a Decimal fieldtype with a 10 digits and 4 decimals). 28 | 29 | The `$metricWeight` Property 30 | --------------------------- 31 | 32 | The `$metricWeight` property is an easy way to modify the ranking property of your models. 33 | You can set weights for whenever you use an `updateMetric*` function. 34 | 35 | So if you want to update a model's rank when you get a result in a search you could run `$model->updateMetricSearch()` which will raise the ranking by 0.8 points. 36 | 37 | These `updateMetric` functions can also be used in mutators or accessors. 38 | 39 | ```php 40 | public function setNameAttribute($value) 41 | { 42 | $this->attributes['name'] = $value; 43 | $this->updateMetricName(); 44 | } 45 | ``` 46 | 47 | Rank Queries 48 | --------------------------- 49 | 50 | Any time you want to get results already sorted descending by `rank` you can just prepend your wanted query builder function with `rank`. 51 | For example: 52 | 53 | ```php 54 | $friends = Friend::rankAll(); 55 | $friendsPaginated = Friend::rankPaginate(); 56 | ``` 57 | 58 | 59 | Updating Rank In Comparison To Other Entries 60 | --------------------------- 61 | 62 | The model also gives you the ability to `rankBefore`, `rankBetween`, or `rankAfter` another model instance. 63 | 64 | ```php 65 | $friendLow = Friend::find(1); 66 | $friendHigh = Friend::find(1); 67 | 68 | $friendLow->rankBefore($friendHigh); 69 | ``` 70 | 71 | Updating All Entries With Sorted `ids` 72 | --------------------------- 73 | 74 | For uses such as Javascript Web Apps, Rankable gives you a quick and easy way to update the rankings between entries. 75 | 76 | ```php 77 | $desiredIds = array(1,2,3); 78 | $friends = Friend::rankOrderSet($desiredIds); 79 | ``` 80 | -------------------------------------------------------------------------------- /src/Rtablada/EloquentRankable/RankableCollection.php: -------------------------------------------------------------------------------- 1 | sortByRank(); 16 | 17 | $i = 0; 18 | $length = count($this->items) - 1; 19 | $last = null; 20 | 21 | foreach ($this->items as $item) { 22 | if ($item->id != $idResults[$i]) { 23 | $result = $this->find($idResults[$i]); 24 | if ($i === 0) { 25 | $result->rankBefore($item); 26 | } else { 27 | $result->rankBetween($item, $last); 28 | } 29 | return $this->updateRanksByIds($idResults); 30 | } 31 | $i ++; 32 | $last = $item; 33 | } 34 | 35 | return $this->sortByRank(); 36 | } 37 | 38 | public function sortByRank() 39 | { 40 | $collection = $this->sortBy(function($model) 41 | { 42 | return - $model->rank; 43 | }); 44 | 45 | return $collection; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Rtablada/EloquentRankable/RankableModel.php: -------------------------------------------------------------------------------- 1 | setAttribute('rank', 0); 25 | } 26 | 27 | $this->fill($attributes); 28 | } 29 | 30 | /** 31 | * Metrics array used to store values to be modified 32 | * using the touch magic methods 33 | * 34 | * @var array 35 | */ 36 | protected $metricWeights = array(); 37 | 38 | /** 39 | * Create a new Rankable Eloquent Collection instance. 40 | * 41 | * @param array $models 42 | * @return \Illuminate\Database\Eloquent\Collection 43 | */ 44 | public function newCollection(array $models = array()) 45 | { 46 | return new RankableCollection($models); 47 | } 48 | 49 | /** 50 | * Handle dynamic method calls into the method. 51 | * Checks for Metric Touch Availability 52 | * 53 | * @param string $method 54 | * @param array $parameters 55 | * @return mixed 56 | */ 57 | public function __call($method, $parameters) 58 | { 59 | if ($metric = $this->findMetric($method)) { 60 | $parameters[0] = empty($parameters) ? 1 : $parameters[0]; 61 | return $this->updateMetric($metric, $parameters[0]); 62 | } else if (preg_match('/ranked(.*)/', $method, $matches)) { 63 | $instance = $this->orderBy('rank', 'DESC'); 64 | 65 | if ($matches[1] == 'All') { 66 | return call_user_func_array(array($instance, 'get'), $parameters); 67 | } else { 68 | return call_user_func_array(array($instance, $matches[1]), $parameters); 69 | } 70 | 71 | } 72 | else { 73 | return parent::__call($method, $parameters); 74 | } 75 | } 76 | 77 | public function rankBefore(RankableModel $model) 78 | { 79 | $lowerRank = $model->rank; 80 | $this->attributes['rank'] = $lowerRank + 1; 81 | $this->save(); 82 | } 83 | 84 | public function rankAfter(RankableModel $model) 85 | { 86 | $lowerRank = $model->rank; 87 | $this->attributes['rank'] = $lowerRank - 1; 88 | $this->save(); 89 | } 90 | 91 | public function rankBetween(RankableModel $high, RankableModel $low) 92 | { 93 | $lowerRank = $low->rank; 94 | $higherRank = $high->rank; 95 | $this->attributes['rank'] = ($higherRank + $lowerRank) / 2; 96 | $this->save(); 97 | } 98 | 99 | public static function rankOrderSet(array $order) 100 | { 101 | $collection = static::all(); 102 | return $collection->updateRanksByIds($order); 103 | } 104 | 105 | /** 106 | * Checks to see if method call began with updateMetric 107 | * Checks to see if metric key exists in metrics property 108 | * 109 | * @param string $method 110 | * @param array $parameters 111 | * @return string 112 | */ 113 | protected function findMetric($method) 114 | { 115 | $matches = array(); 116 | if (preg_match('/updateMetric(.*)/', $method, $matches)) { 117 | $metric = Str::snake($matches[1]); 118 | if (array_key_exists($metric, $this->metricWeights)) { 119 | return $metric; 120 | } 121 | } 122 | 123 | return false; 124 | } 125 | 126 | /** 127 | * Updates the rank attribute against the metric weight. 128 | * 129 | * @param string $metric 130 | * @param integer $value 131 | * @return null 132 | */ 133 | protected function updateMetric($metric, $value = 1) 134 | { 135 | $this->attributes['rank'] += $value * $this->metricWeights[$metric]; 136 | $this->save(); 137 | } 138 | 139 | /** 140 | * Handle dynamic static method calls into the method. 141 | * 142 | * @param string $method 143 | * @param array $parameters 144 | * @return mixed 145 | */ 146 | public static function __callStatic($method, $parameters) 147 | { 148 | 149 | $instance = new static; 150 | $matches = array(); 151 | 152 | if (preg_match('/ranked(.*)/', $method, $matches)) { 153 | 154 | $instance = $instance->orderBy('rank', 'DESC'); 155 | 156 | if ($matches[1] == 'All') { 157 | return call_user_func_array(array($instance, 'get'), $parameters); 158 | } else { 159 | return call_user_func_array(array($instance, $matches[1]), $parameters); 160 | } 161 | 162 | } 163 | 164 | return call_user_func_array(array($instance, $method), $parameters); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtablada/eloquentRankable/45c275faad10cd61ece36f9d926080b8d84fcf50/tests/.gitkeep --------------------------------------------------------------------------------