Laravel 4 and Eloquent: retrieving all records and all related records - orm

I have two classes: Artist and Instrument. Each Artist can play one or more Instruments. And each Instrument can be assigned to one or more Artists. So, I've set up the following Classes:
Artist.php
public function instruments() {
return $this->belongsToMany('App\Models\Instrument');
}
Instrument.php
public function artists() {
return $this->belongsToMany('\App\Models\Artist');
}
Then I have three database tables:
artists: id, firstname, lastname, (timestamps)
instruments: id, name
artist_instrument: id, artist_id, instrument_id
I'm able to successfully retrieve a one artist and their associated instruments like so:
ArtistController.php
$artist = Artist::find($artist_id);
$instruments = $artist->instruments()->get();
return \View::make('artists')->with('artists', $artists)->with('instruments', $instruments);
I have 3 questions:
In my view, I can output the $artist like:
{{ $artist->firstname }}
and I can iterate through $instruments like:
#foreach ($instruments as $instrument)
<h2>{{ $instrument->name }}</h2>
#endforeach
but is it possible to iterate over $artist (I know there's only one — see #2) and for each $artist iterate over their $instruments?
In my controller, how would I get all artists and for each of those their associated instruments with the end goal of iterating through them in my view as described in #1.
Is it possible to only retrieve specific columns in the above example of ArtistController.php? I've tried this:
$artist = Artist::where('id', $artist_id)->get('firstname');
$instruments = $artist->instruments()->get();
return \View::make('artists')->with('artists', $artists)->with('instruments', $instruments);
but I get an error saying Collection::instruments() is undefined.
I'm assuming there's something not right in my model relationships. I've also tried defining my relationship in Artist.php with a hasMany (I think it makes more sense to say "Each Artist hasMany Instruments", but that gives me an error because it's expecting a table named artists_instruments and it's also trying to retrieve columns that wouldn't exists in that table (like name).

Your model relationships are fine.
Controller:
$artists = Artist::with('instruments')->get();
return \View::make('artists')->withArtists($artists);
View:
#foreach ($artists as $artist)
<h1>{{ $artist->firstname }}</h1>
#foreach ($artist->instruments as $instrument)
<h2>{{ $instrument->name }}</h2>
#endforeach
#endforeach

Related

ASP.NET Core MVC Group by query with LINQ

I am trying to query the country column from one of my table and trying to group them so that one country name appears only once.
The controller code is
ViewBag.Countries = (from pT in _context.InfoProducts
group pT by new { pT.Country } into g
select new
{
Country = g.Key.Country
}).ToList();
In my view I am trying to show this list of countries in a dropdown like this
<div class=" form-group">
#Html.Label("Country")
<div class="col-md-12 ">
#Html.DropDownList("Country", new SelectList(ViewBag.Countries, "Country"), "Select Country", new { #class = "col-form-label col-md-12 label-align" })
</div>
</div>
The problem I am facing is though it is returning the expected names of the countries, the result is generating as a key, value pair something like
{Country = USA}
{Country = Canada}
The HTML generated copied from inspect element is given below
<select class="col-form-label col-md-12 label-align" id="Country" name="Country">
<option value="">Select Country</option>
<option>{ Country = USA}</option>
<option>{ Country = Canada}</option>
</select>
How can I get only the country name in the dropdown instead of the result I am currently getting.
Rather than using group by, it sounds like distinct would be a better fit:
_context.InfoProducts.Select(x => x.Country).Distinct().ToList();
This will give you back a list of strings to use, which won't give you the key-value pair problem you're currently having.
One more thing, when you use new SelectList(ViewBag.Countries, "Country") this overload of SelectList's constructor will try to set the value of Country as the selected item, but this doesn't exist in your list, so Select Country will always appear as selected. If you want that to select an actual country, then it would have to look something like:
new SelectList(ViewBag.Countries, "Canada")
where you would fetch Canada from the data for the current product.

Laravel 5.5, Display news data from two tables

I am new to laravel and I experience some trouble. I try to obtain data stored in two different tables and display them:
News.php (model)
public static function Data($category) {
$perPage = config('var.news.perPage');
if ($category) {
$news = News::orderBy('id', 'desc')->where('category', $category)->SimplePaginate($perPage);
} else {
$news = News::orderBy('id', 'desc')->SimplePaginate($perPage);
}
return $news;
}
This is how I grab all data from News table which struct is:
id, title, body, created_at updated_at, created_by, updated_by, category
The category column contains values separated by comma, e.g. 1,2,3,4
Now, I have another table, News_Cat which has id, name columns.
In another method I try to grab the filters names against values stored in category column of News table
public static function getFilterNames($id) {
$filters = DB::table('News_Cat')
->select('News_Cat.name as name')
->leftJoin('News', DB::raw('CAST(News_Cat.id as nvarchar)'), DB::raw('ANY(SELECT(News.category))'))
->where('News.id', $id)
->get();
return $filters;
}
However, it completely does not work. What I try to achieve is to display filter name in view.blade as 'name' value for specified filter from News_Cat
#if($news->count())
#foreach($news as $article)
<a href="{{ route('news.show', $article->id) }}" class="item angled-bg" data-filters="{{ $filters }}">
<div class="row">
So as result I would get e.g. data-filters="news, update, hot, latest"> instead data-filters="1,2,3,4">
Thank you
You should use eloquent!
In your News Model
public function getFiltersAttribute(){
$categories = explode(',', $this->category);
return implode(', ', NewsCat::find($categories)->pluck('name')->toArray());
}
then in your view :
{{ $article->filters }}
will output news, update, hot, latest
BUT
You should use a pivot table between your categories and your news, it would be much easier.
This method can't allow you to eager load the relationship and make a request for each news
If you can't change your database structure, I can propose you this:
In the boot method of your AppServiceProvider:
Config::set('tags', NewsCat::all());
THEN
public function getFiltersAttribute(){
$categories = explode(',' $this->category);
return implode(', ', config('tags')->whereIn('id', $categories)->pluck('name')->toArray());
}
MANY TO MANY METHOD
I am using laravel naming convention for the table :
news, categories_news (the pivot), and categories
You will have 2 models : New and Category
In your New Model
public function categories(){
return $this->belongsToMany(Category::class)
}
in your Category Model :
public function news(){
return $this->belongsToMany(New::class);
}
if you are not using laravel naming conventions, you will have to customize these raltionship like this : https://laravel.com/docs/5.5/eloquent-relationships#many-to-many

Laravel Display a record details while grouped by year or month

I just moved to the laravel framework and am starting to migrate some legacy sites and I have hit a problem with SQL or blade - dunno which.
I have to display a load of rows 'sports classes' which are grouped by year and then month. each needs to show attendance etc.
I am unsure which way to proceed.
I am able to display all rows and sort by date - easy squeezy
I am able to groupBy year AND month - fiddly but sorted it.
These are all displayed in an accordian.
Click the month - the individual rows drop down - you get the idea
I can get a number of rows per month/year
What I am unable to figure out is how to actually display the rows.
The groupBy is this:
$LinkClasses = DB::table('classes_lists')
->select('id, class, teacher, size')
->select(DB::raw('YEAR(date) AS year, MONTH(date) AS month, MONTHNAME(date) AS month_name, COUNT(*) post_count'))
->groupBy('year')
->groupBy('month')
->orderBy('year', 'desc')
->orderBy('month', 'desc')
->orderBy('id', 'desc')
If the code you provided is within your controller, then you can append ->get() after your last ->orderBy(). This will return a Collection. You can then do whatever you want with the Collection (http://laravel.com/api/master/Illuminate/Support/Collection.html), including conversion to an array using ->toArray(), but I think it would be best to utilize the Eloquent ORM if possible.
Anyway, once you have it in the format you want, just pass it to the view like so:
return view('your.view', compact('LinkClasses'));
Then, inside the your.view blade template, you can access this by using the following:
#foreach ($LinkClasses as $currentRow)
<tr>
<td>{{ $currentRow['id'] }}</td>
<td>{{ $currentRow['class'] }}</td>
<td> ... </td>
</tr>
#endforeach
Best guess I can offer without seeing the blade template to get a better idea of what you're doing. Hope that helps!
UPDATE BASED ON OP FEEDBACK:
Since you are only receiving a single record, it seems as though the issue lies in your query. I suggest you simplify your query to fetch all records and then do your sorting within an array. Something like this in your controller:
$allClasses = DB::table('classes_lists')->all();
foreach ($allClasses as $currentClass) {
$yearMonth = date('Y-m', $currentClass['date']);
$classesByYearMonth[$yearMonth][] = $currentClass;
}
ksort($classesByYearMonth);
/* now you have an array of all classes sorted by year-month like this:
// $classesByYearMonth[2014-01] = array(
// [0] => array(1, 'class name', 'teacher name', 23),
// [1] => array(2, 'another class', 'different teacher', 25),
// ...
// );
//
// $classesByYearMonth[2014-02] = ...
*/
return view('your.view', compact('classesByYearMonth'));
Then, inside your blade template:
#foreach ($classesByYearMonth as $yearMonth => $classListArray)
Found {{ sizeof($classListArray) }} classes for {{ $yearMonth }}
#foreach ($classListArray as $currentClass)
<div>
<div>ID: {{ $currentClass['id'] }}</div>
<div>Class: {{ $currentClass['class'] }}</div>
<div>Teacher: {{ $currentClass['teacher'] }}</div>
<div>Size: {{ $currentClass['size'] }}</div>
</div>
#endforeach
#endforeach
I will leave it to you to fix the formatting to make your accordion work. But hopefully that will get you on the right path.
DNoe - thank you so much.
Your reply put me on exactly the right track.
I had to mod some bits due to laravel ambiguities and add the strtotime but the logic was all there.
foreach ($allClasses as $currentClass) {
$ym = $currentClass['date'];
$yearMonth = date("Y-m",strtotime($ym));
$classesByYearMonth[$yearMonth][] = $currentClass;
}
krsort($classesByYearMonth);
return View::make('classes.index', compact('classesByYearMonth'));
The css is simple from here.
I owe you some beers. And thanks for helping me take my head from my butt!
Send me a pm and i would be very very happy to forward beer donation :o
Great work and thank you again. :)
Also, part of the problem was that the results were throwing an stdObject rather than an array.
Being able to compare your code with my own has enabled me to create a dbquery with multiple joins from which meaningfull data is selected and then converted to an array.
$classes = DB::table('table2')
->join('table1', 'table2.id', '=', 'table1.id2' )
->join('table3', 'table1.id3', '=', 'table3.id' )
->orderBy('classes_lists.date','DESC')
->get(array('table1.id', 'teacher', 'date', 'size', 'students', 'fname', 'classname', 'table1.notes'));
$cfr = count($classes);
foreach($classes as $object)
{
$arrays[] = (array) $object;
}
foreach ($arrays as $currentClass){
$ym = $currentClass['date'];
$yearMonth = date("Y-m",strtotime($ym));
$clazByYearMonth[$yearMonth][] = $currentClass;
}
krsort($clazByYearMonth);
This was the output into blade:
Not formatted :
#foreach ($clazByYearMonth as $yearMonth => $classListArray)
Found {{ sizeof($classListArray) }} classes for {{ $yearMonth }}
#foreach ($classListArray as $currentClass)
<div>
date: {{ $currentClass['date'] }} | class: {{ $currentClass['classname'] }} | Size: {{ $currentClass['size'] }} Teacher: {{ $currentClass['fname'] }} |
</div>
#endforeach
#endforeach

Laravel Eloquent - retrieving specific columns from related model

I'm new to Laravel and still getting my head around Eloquent. I have two models (A, B) with a many-to-many relationship (belongsToMany in both models) with the following fields:
A: id, name, text, etc..
B: id, name, description, info, etc...
A_B: id, A_id, B_id
When retrieving an object of 'A', I just want to retrieve the 'id' and 'name' of its related 'B', rather than the entire 'B' objects - as these could be large. Is a way to retrieve only specific columns from a related model, or would it be better to separate my tables so that 'id' and 'name' are on their own?
I'm not really sure if grabbing all of the columns from table 'B' would effect speed of the query too much, but you can try this:
Model class for table 'A_B'
class A_B extends Eloquent {
public function a(){
$this->hasMany('A', 'id', 'a_id');
}
public function b(){
$this->hasMany('B', 'id', 'b_id');
}
}
In your model for table 'B'
class B extends Eloquent {
//Hide fields from displaying in your query
protected $hidden = array('description', 'info');
}
In your controller:
//Get all records with A and B
$all = A_B::with('a')->with('b')->get();
return View::make('view')->with('all', $all);
In your view:
#if($all->count() > 0)
#foreach($all as $record)
{{ $record->id }}
{{ $record->name }}
#endforeach
#else
There are no records to be displayed
#endif

Filtering results based on a distinct column or field value

I have an MVC 3 application running against an MS SQL 2008 database with a table named Documents. Documents are broken down by paragraph in the database. The Documents table has a DocText column containing the text of each paragraph, a DocTitle column containing the document title. My MVC 3 app has a search function that can search for a word or phrase in the DocText column or in the DocTitle column. Everything works fine except that if a particular document has the search word appearing in multiple paragraphs, my List returns multiple instances of that document. For example, if the user searches the word "Budget" and one of THE documents has the word "budget" in four different paragraphs, my returned list has that document listed four times.
What I want to achieve is to list each document that has the searched word. I only want to list the document by Title once, regardless of the number of times the search word appears in that document. The only column that is truly unique is the RecordID column, a primary key.
My controller:
public class SearchController : Controller
{
private GUICEEntities4 db = new GUICEEntities4();
//
// GET: /Search/
public ActionResult Index(string Keyword)
{
#region Keyword Search
if (!String.IsNullOrEmpty(Keyword)) {
var SearchDoc = db.Documents.Where(r => r.DocText.ToUpper().Contains(Keyword.ToUpper()) || r.Title.ToUpper().Contains(Keyword.ToUpper()) || r.DocNum.ToUpper().Contains(Keyword.ToUpper()));
ViewBag.CurrentKeyword = String.IsNullOrEmpty(Keyword) ? "" : Keyword;
return View(SearchDoc.ToList());
}
else{
return View();
}
#endregion
}
}
My View has the following:
#foreach (var item in Model) {
<tr>
<td>
<strong>AFI #Html.DisplayFor(modelItem => item.DocNum): #Html.DisplayFor(modelItem => item.Title)</strong>
<br />
#Html.DisplayFor(modelItem => item.DocSummary)
<br />
<span class="complianceitems">Number of compliance items:</span> (TBD)
</td>
<td>
<a href="/Documents/Index/#(Html.DisplayFor(modelItem => item.DocNum))">Checklist
Generator</a><br />
<a href="/UploadedDocs/#Html.DisplayFor(modelItem => item.DocFileName)" target="_blank">
Download PDF</a>
</td>
Any suggestions on how I can achieve my goal?
ADDED: Each document can be identified by the DocNum column which has a unique document number for that particular document. I've tried to iterate through the List to pull out each unqiue DocNum and then try to make that DocNum not appear again in the loop...but I was not successful.
The following SQL statement gives me the results I need. The statement assumes that the search word is "budget". I don't know how to get the same results using EF. Any suggestions?
SELECT DISTINCT DocNum, Title FROM Documents
WHERE
DocText LIKE '%budget%'
OR
Documents.Title LIKE '%budget%'
OR
DocNum LIKE '%budget%'
The issue here is in your EF query and not anything related to MVC. It's been a while since I've actively used EF but the simplest way would probably be to return just the RecordIds first.
var recordIds= db.Documents
.Where(r =>
r.DocText.ToUpper().Contains(Keyword.ToUpper()) ||
r.Title.ToUpper().Contains(Keyword.ToUpper()) ||
r.DocNum.ToUpper().Contains(Keyword.ToUpper()))
.Select(d => d.RecordId)
.Distinct();
I'm not exactly sure what you will do with each individual record from then on as there isn't enough information in your question. But this should help.
Update:
var foundDocs = new List<YourType>();
recordIds.ToList().ForEach(r => foundDocs.Add(db.TblDocLists.Single(l => l.TheDocNum == r)));
//I must point out that I'm not familiar with the latest versions of EF so this might be overkill.