Laravel saving ordered list of eloquent models - laravel

I'm creating a food menu which the administrator can order/sort by dragging and dropping. This menu consists of multiple categories (ProductCategory) and products (Product).
I'm using HTML5Sortable on the client-side to allow nested d&d. The markup is pretty simple:
<div class="categories">
#foreach($categories as $category)
<div class="category">
#foreach($category->products as $product)
<div class="products">
<div class=""product" data=id="{{ $product->id }}">
{{ $product->name }}
</div>
</div><!-- /products !-->
#endforeach
</div><!-- /category !-->
#endforeach
</div>
And the corresponding javascript:
$('.categories').sortable({
items: '.category'
});
$('.products').sortable({
items: '.product'
});
// Will be called when the user is done repositioning the products and categories
function getOrderedList() {
var data = {};
$('.categories').find('.category').map(function(i) {
var category = $(this);
data[i] = {};
data[i].id = category.data('id');
data[i].products = category.find('.product').map(function() {
return $(this).data('id');
}).get();
});
data = JSON.stringify(data); // Send data to server
}
The function getOrderedList will send a JSON string back to Laravel, which contains the sorted category id's and product id's:
{"0":{"id":1,"products":[2,3,1,4,5,6,7,8,9,10]},"1":{"id":2,"products":[11,12,13,14]},"2":{"id":3,"products":[15,16,17,18]}}
How would I make this work on the back-end? I guess I must store this array somewhere in the database and later find and order the models by the id's?
In short: What is a clean and flexible solution for sorting (nested) models (within Laravel)?

A common convention is Weight, add a field called (Int)Weight on the products table, which is used to define the order of the items.
Once a change in the order occurs you only update the weight field.
When you retrieve the items, you sort them by Weight.
it becomes similar to an Array
Id Name Weight
01 'product 1' 2
02 'product 2' 0
03 'product 3' 1
when you order it by weight you get
product 2
product 3
product 1
it's similar to an array because
$products[0] = 'product 2'
$products[1] = 'product 3'
$products[2] = 'product 1'
Note that if you want to make it even more dynamic, you can create a polymorphic model that can satisfy multiple models.
Please refer to https://laravel.com/docs/5.1/eloquent-relationships#many-to-many-polymorphic-relations
Polymorphic Relations example
Create table Weights (migration example)
$table->increments('id');
$table->integer('value');
$table->integer('weightable_id')->unsigned();
$table->string('weightable_type');
Create model Weight
class Weight extends Eloquent
{
public function weightable()
{
return $this->morphTo();
}
}
now with any other model
class Products extends Eloquent
{
...
public function weight()
{
return $this->morphOne(Weight::class);
}
}
this way you can just add that method to any model you want then you can sort your model with it.
P.S. make sure any model that uses it, creates that relation immediately after creating the model
i do not recommend this method, it's much better if you explicitly define the weight field in the Products table, i understand how much you want your code to be dynamic, but everything comes at a cost
Performance goes down, it's not easy to visualize your code once you establish polymorphic relations, its more like starting to use Jumps instead of Functions

First, the JSON that you are producing shouldn't be an object where the keys are just array indices. Instead it should be an array of objects that looks like this:
[{"id":1,"products":[2,3,1,4,5,6,7,8,9,10]},{"id":2,"products":[11,12,13,14]},{"id":3,"products":[15,16,17,18]}]
Since the products table to product_categories table has an obvious many to one relationship, you'd just use the product_categories_id foreign key on the products table to represent the relationships laid out in your JSON.
In the nested objects of your JSON, every value in the products key array will have a foreign key that corresponds to the id key value in the same nested object (this is the product_category_id column on your products table).
Your API endpoint function would then look something like this:
public function myApiEndpoint(){
$input = Input::get('input');
$input = json_decode($input, true);
foreach($input as $category){
Product::whereIn('id', $category['products'])->update(array(
'product_category_id' => $category['id']
));
}
}
I am updating the model directly in the API controller here, but you should really do any model changes through a repository that's also implementing an interface.
The above will work if you only ever have one menu (with it's categories and products). If you want multiple menus, then you'll need a menus table along with a three way pivot table (with columns menu_id, product_id, and product_category_id).

I just implement this behavior using this library:
https://github.com/spatie/eloquent-sortable
It is very simple to implement, basically you need an extra column to keep the order and the library will do the rest, here is a part of the documentation:
Implement the Spatie\EloquentSortable\Sortable interface.
Use the trait Spatie\EloquentSortable\SortableTrait.
Optionally specify which column will be used as the order column. The default is order_column.
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
class MyModel extends Eloquent implements Sortable
{
use SortableTrait;
public $sortable = [
'order_column_name' => 'order_column',
'sort_when_creating' => true,
];
...
}

Related

ManytoMany relations in Laravel, retrieve data from related tables and display in blade

I have two tables related by many to many relation in Laravel Framework. I can display data from each table separately, but not through relation by taking one record from the 1st table and checking related records in the 2nd table. In tinker it accesses data fine.
Relations:
public function underperformances() {
return $this->belongsToMany(Underperformance::Class);
}
...
public function procedures() {
return $this->belongsToMany(Procedure::class);
}
My resource controller part:
...
use App\Underperformance;
use App\Procedure;
...
public function index()
{
$books = Underperformance::orderBy('id','desc')->paginate(9);
$procedures = Procedure::all();
return view('underpcon.underps', compact('books', 'procedures'));
}
Route:
Route::get('/underps', 'UnderpsController#index');
If I try to display data like this:
#foreach($procedures as $procedure)
<li>{{$procedure->underperformances}}</li>
#endforeach
I get such format to the browser:
[{"id":1,"title":"Spare part not taken before service","description":"tekstas","level":"1","costs":600 ...
This is correct data from related table, but I cannot select further the specific column from that table. For example this does not work:
#foreach($procedures as $procedure)
<li>{{$procedure->underperformances->id}}</li>
#endforeach
Nor this one:
#foreach ($procedures->underperformances as $underperformance)
<li>{{$underperformance->id}}</li>
#endforeach
How do I select records of the related table and display specific data from that table?
What would be a conventional way to do this?
#foreach($procedures as $procedure)
<li>{{$procedure->underperformances->id}}</li>
#endforeach
^ This right there $procedure->underperformances will return a collection, not a single item, so you need to treat it as array, you will not be able to access the id directly, you can either #foreach that, or use the pluck method in Laravel Collections.

How to retrieve data with composite primary key in Laravel

I am working with four tables in Laravel and trying to display data to a view. I believe I am stuck because one of the tables has a composite primary key.
I have the following in my controller:
public function show($id)
{
//Get application for drug
$application = PharmaApplication::where('ApplNo', $id)->first();
//Return all products for application
$drugs = $application->products;
return view('profiles.drug', compact('drugs'));
}
I have the following in my PharmaApplication model:
public function products()
{
return $this->hasMany('App\PharmaProduct', 'ApplNo', 'ApplNo');
}
I have the following in my view (which I cannot complete)
#foreach ($drugs as $drug)
<li>{{$drug->Strength}}</li>
<li>{{$drug->Form}}</li>
<li>{{$drug->Form}}</li>
#endforeach
I am trying to accomplish the following:
Get the application for a drug - this part of my code works
Return an array of objects for the products (i.e. 7 products that belong to one application). I can do this but get stuck going to the next part.
Next, I have to use the array of objects and search a table with the following columns: MarketingStatusID, ApplNo, ProductNo. I know how to query this table and get one row using DB Query, but then how do I get the proper result to that query within the for loop in my view?
Finally, I use the MarketingStatusID to retrieve the MarketingStatusDescription which I will know how to do.

is there a way to query and map datas with their correspondance in laravel

How to map data from one table to may other data from other table;
example we have three table region, market, categories, products using eloquent in laravel 5.1
i have tried this but only gives me the last
public function look(Request $request)
{
``$sector_id=$request->input('category');
$cells=Cell::where('sector_id',"=",$sector_id)->get();
foreach ($cells as $cell){
$a=$cell->id;
$markets=Market::where('cell_id',"=",$a)->get();
}
foreach ($markets as $market){
$b=$market->id;
$prices=Price::where('market_id',"=",$b)->get();
}
return view('reports.sector')->with('cells',$cells)->with('markets',$markets)-`>with('prices',$prices);
}
i need to display only the names in above table but what i need is that one elements in each table maps with their corresponding elements from other table how can i do that query.i need all of those data from database please help me i am stuck here.
If you mean three separate tables:
$cells = Cell::where('sector_id', $sector_id)->get();
$markets = Market::whereIn('cell_id', $cells->pluck('id'))->get();
$prices = Price::whereIn('market_id', $markets->pluck('id'))->get();
If you want to display the data in a tree-like structure, and if you have defined your model relations properly you can use eager loading
$cells = Cell::where('sector_id', $sector_id)->with('markets.prices')->get()
Pass cells to the view and you can use the following in your blade template to display the data
#foreach ($cells as $cell)
#foreach ($cell->markets as $market)
#foreach ($market->prices as $price)

Where should be functions of item generated from join query?

I have created block with function getRooms(). This function make joinLeft query for two tables and return items collection.
Actually It's not initiall room collection because It has group, sum and other countable field.
In the phtml template I render these collection and should check each item, call function.
For example:
<?php $rooms = $this->getRooms();
foreach ($rooms as $room) : ?>
<?php if ( $this->isAvailable($room) ) : ?>
<!-- some html here -->
isAvailable function work with $room values, thefore I feel It should be called like
$room->isAvailable();
instead of
$this->isAvailable($room)
In this case isAvailable() should be in Model. But what model? As I mentioned above my query is joining of two tables. So what model should be used? Maybe Magento has possibility to create model based on joining tables (instead of one table)?
The Collection object contains Models that you're using. All the joining work was done simply to add more data to your model (data that is required by your logic). To get the Model class simply use
echo get_class($room);

Storing product attributes with orders in Magento

I am looking for a solution to storing product attributes with each order. Essentially I have a need for storing a unique item lot number for each product that can then be searched on the front end to find out which orders contained products from a specific lot. My initial thought was to do this with product attributes and store the attribute with each product in an order.
Does anybody have a better solution or can point me in the right direction for implementing this solution?
Edit: Still looking for a solution to this
I agree the lot number should be a product attribute but you don't need to store it twice. Just join it with order table when you later need it - That way the information is kept up to date (unless you want to know what it was at the time of ordering, in which case this is all wrong).
Unfortunately the order tables are flattened, not EAV, so don't handle attributes so well. The collection's joinAttribute() method is a stub. You can get around that with this query patterns library (self promotion disclaimer; I'm using it here because I wrote the attribute functions and don't want to redo the work) and then extending it with a class specific to you.
class Knectar_Select_Product_Lot extends Knectar_Select_Product
{
public function __construct()
{
parent::__construct();
$this->joinAttribute('lots', 'catalog_product', 'lot_number',
'products.entity_id', 0, 'lot_number'
);
}
public static function enhance(Varien_Db_Select $select, $tableName, $condition, $columns = null, $type = self::LEFT_JOIN)
{
$select->_join(
$type,
array($tableName => new self()),
$condition,
$columns ? $columns : 'lot_number'
);
}
}
The enhance() function does the hard bit, you only need to call it and filter by it's column.
$orderItems = Mage::getResourceModel('sales/order_item_collection');
Knectar_Select_Product_Lot::enhance($orderItems->getSelect, 'lots', 'lots.product_id = main_table.product_id');
$orderItems->addFieldToFilter('lot_number', 'ABC123');
If you are using the collection in an admin grid then it's column filters will do addFieldToFilter() for you.

Resources