"Call to a member function active_pest() on array" when using SyncWithoutDetaching() to update a pivot table - eloquent

I'm trying to using SyncWithoutDetaching() to update a pivot table (active_pest) but am getting a "Call to a member function active_pest() on array" error message.
The background here is that I have 8 tables: products, pests, actives (i.e active ingredients), crops, active_product, pest_product, crop_product, and active_pest. My form collects information about a selected (agrichemical) product - in that form, the user selects the pests, actives, and crops associated with that product. When submitted, my existing code is saving the expected information in the products table and, through a set of "belongsToMany" relationships, the active_product, pest_product, and crop_product pivot tables are also correctly updated.
For any given product there are typically 1 or 2 actives and 3-8 pests and it is those id values that I want to add to the active_pest pivot table.
My code is:
// ProductController
public function update(UpdateRequest $request)
{
$actives = $request->get('active');
$actives->active_pest()->SyncWithoutDetaching( $request->get('pest'));
...
}
// pest model
public function active_pest()
{
return $this->belongsToMany('App\Models\Pest', 'active_pest', 'active_id', 'pest_id');
}
Answers to other questions about this type of error message indicate that there is something wrong with the active_pest() relationship - but I got the same error message after making a typo there (active_pestr). Regardless, I don't understand what I'm doing wrong.
Insight appreciated. Thanks, Tom

$actives = $request->get('active');
Just return array, so you are trying something like this [1,2,3]->active_pest().
Which is exactly what it says. Call to a member function active_pest() on array.
You need to work with eloquent instance to perform ->active_pest()->SyncWithoutDetaching( $request->get('pest'));
So you can find you instances like this:
$actives = Active::whereIn('col_name', $request->get('active'))->get();
This will return you collection of instances, to save pest for each of them you need to iterate with foreach like this:
foreach($actives as $active){
$active->active_pest()->SyncWithoutDetaching( $request->get('pest'));
}

Related

Using "processDatamap_afterDatabaseOperations" with "tx_news_domain_model_news" and status "new" ends up without empty category

I'm using the "processDatamap_afterDatabaseOperations" hook within my extension to transfer content from a newly created News (tx_news_domain_model_news) to an API.
TYPO3 Version is 6.2.11 and if I var_dump or trying to access the category using $record->getCategories() it's empty. Same with related files, falmedia works. Here is my code:
public function processDatamap_afterDatabaseOperations($status, $table, $id, array $fieldArray, \TYPO3\CMS\Core\DataHandling\DataHandler &$pObj) {
if ($table == 'tx_news_domain_model_news' && $status == 'new') {
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
$news = $objectManager->get('GeorgRinger\News\Domain\Repository\NewsRepository');
$record = $news->findByUid($pObj->substNEWwithIDs[$id]);
Hope anybody knows what I'm doing wrong here. I've been trying this like forever and don't get it.
Thanks in advance for your help.
That's likely because "afterDatabaseOperations" is called for each record insertion/update in each table, and that the relation between the record and the categories has not been established yet.
Only after all insertions/updates have been done, the method processRemapStack is called by the DataHandler, that sets/fixes all the relations between the various records (e.g. wherever there was a "NEW.." relation in the datamap, the correct uids are set).
The only hook you can use, where alle records have the correct relations is the processDatamap_afterAllOperations hook, that you can find at the very end of the process_datamap in the DataHandler class.
That one only takes a single argument though (the DataHandler instance), so you probably have to try and get the inserted news records using the "datamap" property of the DataHandler reference.

Eloquent - load accessor along with model data

My model was eager loading a lot of things with accessors. I want to change it to specify accessors in each case. How do I include such accessors with the query, so that I get the basic model data plus the accessor data
Accessors would previously be eager loaded with:
protected $appends = [
'status',
]
But if I get rid of eager loading, and I want to include this acccessor:
public function getStatusAttribute() {
return self::STATUS_ACTIVE;
}
Then I can do this according to the documentation:
$prod = \App\Product::find(736)->status;
That works but I don't get the basic model data.
I can't do: return $prod = \App\Product::find(736)->with('status')->first()
It gives error: Call to undefined relationship [status] on model
So how do I add such accessors to be included with the model data?
Edit:
As Staudenmeir commented, i can do \App\Product::find(736)->append('status');
That solves it for single results. But how do I append data for many results?
Neither append or appends work:
This: \App\Product::whereIn([34, 55])->appends('status');
results in "Method appends does not exist.",
I saw that you can use "appends" on "->paginate()"
$products = \App\Product::whereIn([34, 55])
->paginate(12)
->appends('status');
But that appends it as a query string to the url. Very strange - I want to append it in the same way as for a single result in the json response.
You have not fetched the collection first, and then access the attribute of result.
$prod = \App\Product::find(736);
$status = $prod->status;

Adding elements to TableViewer from a collection?

So I'm trying to make a simple program that takes in some basic information about a loan, and turns it into an amortization table.
I am using WindowBuilder for this, and thus I have a Tableviewer that is supposed to have a column for Month, interest, payment, ect.
Unfortunately, I can't figure out how to get the table to actually display information. I have a Loan object that generates an ArrayList of month objects (each month being one row of the table), but from what I've read online, it's not as simple as telling the Tableviewer to populate each column with a variable from that arraylist.
I have set my Content Provider to be a Loan object, which contains an Arraylist of Month Objects, and each Month object has a getter for each column.
How do I go about displaying the text in each column?
Thanks.
EDIT:
So, here is the snippet of code from the example given to me below:
colFirstName.setLabelProvider(new ColumnLabelProvider() {
#Override
public String getText(Object element) {
Person p = (Person) element;
return p.getFirstName();
}});
I don't quite understand what is going on here. It's overriding the getText method, and casting the element to the type of element that has their data?
balanceCol.setLabelProvider(new ColumnLabelProvider(){
#Override
public String getText(Object element) {
Loan m = (Loan) element;
return m.getMonthObj().get(0).getMonth();
}
});
When I run this, it almost sorta works. It fills in about half the months, and then I get this error:
java.lang.ClassCastException: model.Month cannot be cast to model.Loan

Laravel: BadMethodCallException with message 'Method update does not exist.'

When I try to update a model with this code:
public function updateMixedtape($slug, Request $request)
{
$mix = Mix::where('slug', $slug)->get();
$mix->update($request->all());
return redirect('dashboard/mixes');
}
I get an error that method update doesn't exist. However if I modify my view to send a radio_show_id instead of slug and try to change the code to something like this:
public function updateMixedtape(Request $request)
{
$mix = Mix::findOrFail($request->radio_show_id);
$mix->update($request->all());
return redirect('dashboard/mixes');
}
The code executes without any errors.
What puzzles me is that if I do something like return $mix; before the line where I call the update method, I get similar data for both methods.
As shock_gone_wild has suggested in the comment section of my question $mix = Mix::where('slug', $slug)->get(); is returning a collection and not a model. This is because a Model::where() method can return zero, one or many records depending on whether or not there are records that meet the set condition.
As suggested I used $mix = Mix::where('slug', $slug)->first(); instead to get the first record that meets the condition.

ADO.NET Mapping From SQLDataReader to Domain Object?

I have a very simple mapping function called "BuildEntity" that does the usual boring "left/right" coding required to dump my reader data into my domain object. (shown below) My question is this - If I don't bring back every column in this mapping as is, I get the "System.IndexOutOfRangeException" exception and wanted to know if ado.net had anything to correct this so I don't need to bring back every column with each call into SQL ...
What I'm really looking for is something like "IsValidColumn" so I can keep this 1 mapping function throughout my DataAccess class with all the left/right mappings defined - and have it work even when a sproc doesn't return every column listed ...
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim product As Product
While reader.Read()
product = New Product()
product.ID = Convert.ToInt32(reader("ProductID"))
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
Also check out this extension method I wrote for use on data commands:
public static void Fill<T>(this IDbCommand cmd,
IList<T> list, Func<IDataReader, T> rowConverter)
{
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
list.Add(rowConverter(rdr));
}
}
}
You can use it like this:
cmd.Fill(products, r => r.GetProduct());
Where "products" is the IList<Product> you want to populate, and "GetProduct" contains the logic to create a Product instance from a data reader. It won't help with this specific problem of not having all the fields present, but if you're doing a lot of old-fashioned ADO.NET like this it can be quite handy.
Although connection.GetSchema("Tables") does return meta data about the tables in your database, it won't return everything in your sproc if you define any custom columns.
For example, if you throw in some random ad-hoc column like *SELECT ProductName,'Testing' As ProductTestName FROM dbo.Products" you won't see 'ProductTestName' as a column because it's not in the Schema of the Products table. To solve this, and ask for every column available in the returned data, leverage a method on the SqlDataReader object "GetSchemaTable()"
If I add this to the existing code sample you listed in your original question, you will notice just after the reader is declared I add a data table to capture the meta data from the reader itself. Next I loop through this meta data and add each column to another table that I use in the left-right code to check if each column exists.
Updated Source Code
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product While reader.Read()
product = New Product()
If Not colNames.Columns("ProductID") Is Nothing Then
product.ID = Convert.ToInt32(reader("ProductID"))
End If
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
This is a hack to be honest, as you should return every column to hydrate your object correctly. But I thought to include this reader method as it would actually grab all the columns, even if they are not defined in your table schema.
This approach to mapping your relational data into your domain model might cause some issues when you get into a lazy loading scenario.
Why not just have each sproc return complete column set, using null, -1, or acceptable values where you don't have the data. Avoids having to catch IndexOutOfRangeException or re-writing everything in LinqToSql.
Use the GetSchemaTable() method to retrieve the metadata of the DataReader. The DataTable that is returned can be used to check if a specific column is present or not.
Why don't you use LinqToSql - everything you need is done automatically. For the sake of being general you can use any other ORM tool for .NET
If you don't want to use an ORM you can also use reflection for things like this (though in this case because ProductID is not named the same on both sides, you couldn't do it in the simplistic fashion demonstrated here):
List Provider in C#
I would call reader.GetOrdinal for each field name before starting the while loop. Unfortunately GetOrdinal throws an IndexOutOfRangeException if the field doesn't exist, so it won't be very performant.
You could probably store the results in a Dictionary<string, int> and use its ContainsKey method to determine if the field was supplied.
I ended up writing my own, but this mapper is pretty good (and simple): https://code.google.com/p/dapper-dot-net/