I am attempting to build a query where a product is updated via an API. The application is built in Angular2 wrapped in a NodeJS layer.
A product has a many-to-many relationship with files. What I want to happen is that when data is sent to the API to update a file, a Eloquent query checks whether this relationship already exists and if not it adds a relationship into the 'file_product' table. I have this functionality working fine with a category many-to-many relationship but for some reason this is not working on the files relationship. here is my code:
Product Controller update function:
public function update(Request $request, $id)
{
if($id && is_numeric($id)) {
try {
$requestProductVars = $request->get('product');
$product = Product::find($id);
$product->title = $requestProductVars['title'];
$product->description = $requestProductVars['description'];
$product->images = json_encode($requestProductVars['images']);
$product->SKU = $requestProductVars['SKU'];
$product->manufacturer_id = $requestProductVars['manufacturer_id'];
$product->active = (($requestProductVars['active'] === true) ? '1' : '0');
if($request->get('categories')) {
$categories = $request->get('categories');
foreach($categories as $categoryId) {
$category = Category::find($categoryId);
if(!$product->categories->contains($category)) {
$product->categories()->save($category);
}
}
}
if(count($requestProductVars['files'])) {
$files = $requestProductVars['files'];
foreach($files as $file) {
$fileId = $file['id'];
$fileRecord = File::find($fileId);
if(!$product->files->contains($fileRecord)) {
$product->files()->save($fileRecord);
}
}
}
$product->save();
As you can see, I check to see if there is a 'files' property on the request and if there is I loop through each file, get the id and check if the relationship exists using:
if(!$product->files->contains($fileRecord)) {
The files property contains an array of File objects.
My code seems to stop here and doesn't even seem to execute if. The category functionality works fine in this code though which is odd and I am doing exactly the same thing here.
I checked the models for Product and File and the many-to-many relationships are defined here fine:
Product.php
/**
* Returns all files associated with a product
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function files() {
return $this->belongsToMany('App\File')->withTimestamps();
}
File.php
/**
* Returns all products related to a file
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function products() {
return $this->belongsToMany('App\Product')->withTimestamps();
}
Can anyone see why my code doesn't seem to be working?
Thanks
Ok I discovered what the problem was.
Before I abstracted out the files to products functionality I had a cell in the products table called 'files'. I had forgotten to delete this cell and this was causing a conflict when I was trying to query the files relationship. Hope this helps someone in the future.
Related
I want to access the custom field added to the ps_orders table on order-details.tpl.
I have overridden Order file like below.
class Order extends OrderCore
{
/**
* #var int Round type method used for this order
*/
public $total_seat_price;
public function __construct($id = null, $id_lang = null)
{
parent::__construct($id, $id_lang);
self::$definition['fields']['total_seat_price'] = array('type' => self::TYPE_FLOAT);
parent::__construct($id, $id_lang);
}
}
Now. I want to show this price on the order details page. When I print the {$order.details} I can see the field in the object. But when I try to access {$order.details.total_seat_price}. It gives me blank value. Though I can access other fields from the object. Can anyone please guide me?
Developer's version of Acumatica 2020R1 is installed locally. Data for sample tenant MyTenant from training for I-300 were loaded, and WSDL connection established.
DefaultSoapClient is created fine.
However, attempts to export any data by using Getlist cause errors:
using (Default.DefaultSoapClient soapClient =
new Default.DefaultSoapClient())
{
//Sign in to Acumatica ERP
soapClient.Login
(
"Admin",
"*",
"MyTenant",
"Yogifon",
null
);
try
{
//Retrieving the list of customers with contacts
//InitialDataRetrieval.RetrieveListOfCustomers(soapClient);
//Retrieving the list of stock items modified within the past day
// RetrievalOfDelta.ExportStockItems(soapClient);
RetrievalOfDelta.ExportItemClass(soapClient);
}
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.All,
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
string lcItemType = "", lcValuationMethod = "";
int lnCustomFieldsCount;
using (StreamWriter file = new StreamWriter("ItemClass.csv"))
{
//Write the values for each item
foreach (ItemClass loItemClass in ItemClasses)
{
file.WriteLine(loItemClass.Note);
}
}
The Acumatica instance was modified by adding a custom field to Stock Items using DAC, and by adding several Attributes to Customer and Stock Items.
Interesting enough, this code used to work until something broke it.
What is wrong here?
Thank you.
Alexander
In the request you have the following line: ReturnBehavior = ReturnBehavior.All
That means that you try to retrieve all linked/detail entities of the object. Unfortunately, some object are not optimized enough to not affect query performance in GetList scenarios.
So, you have to options:
Replace ReturnBehavior=All by explicitly specifying linked/detail entities that you want to retrieve and not include Attributes into the list.
Retrieve StockItem with attributes one by one using Get operation instead of GetList.
P.S. The problem with attributes will most likely be fixed in the next version of API endpoint.
Edit:
Code sample for Get:
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.Default //retrieve only default fields (without attributes and other linked/detailed entities)
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
foreach(var entity in ItemClasses)
{
ItemClass itemClass= entity as ItemClass;
ItemClass.ReturnBehavior=ReturnBehavior.All;
// retrieve each ItemClass with all the details/linked entities individually
ItemClass retrievedItemCLass = soapClient.Get(itemClass);
}
I am trying to paginate some categories and this error popped up from nowhere and I don't know how to fix it :
Call to a member function render() on a non-object
This is my controller:
public function getFilmeByCateg()
{
$categorii = Categorii::all();
//$Movies = Movies::all();
$categorie = Request::segment(2);
$cat = Categorii::where('denumire', '=',$categorie)->first();
$cat2 = Categorii_filme::where('categorie_id', '=' ,$cat->categorie_id)->get();
$filme = array();
foreach($cat2 as $filmulet)
{
$film = Movies::where('movie_id','=',$filmulet->film_id)->paginate(12)->first();
$filme[] = $film;
}
return view('filme')->with('Movies',$filme)->with('categorii',$categorii);
}
And this is how I render the paginator in my layout:
{!!$Movies->render() !!}
This is in my routes:
Route::get('categorie/{categorie}','WelcomeController#getFilmeByCateg');
This is in my Movies.php:
class Movies extends Model {
protected $table = "movies";
}
Can someone tell me how can I manage to make this work ?
Since $filme is an array, you can access render() method whose object you wanted is $Movies which is array.
foreach($cat2 as $filmulet)
{
$film = Movies::where('movie_id','=',$filmulet->film_id)->paginate(12)->first();
$filme[] = $film;
}
Here $filme is an array.
return view('filme')->with('Movies',$filme)->with('categorii',$categorii);
Here you passed $filme as $Movie variable.
{!!$Movies->render() !!}
And here you wanted to access render() method of $filme which is not an object.
Change your controller to this:
public function getFilmeByCateg()
{
$categorii = Categorii::all();
//$Movies = Movies::all();
$categorie = Request::segment(2);
$cat = Categorii::where('denumire', '=',$categorie)->first();
$movie_ids = Categorii_filme::where('categorie_id', '=' ,$cat->categorie_id)->get()->lists('movie_id');
$filme = Movies::whereIn('movie_id','=',$movie_ids)->paginate(12);
return view('filme')->with('Movies',$filme)->with('categorii',$categorii);
}
OK First of all, you made it the wrong way.
Using an intermediate class for representing the pivot table is completely bad.
I'll make it work for you.
Your Category class
class Category extends \Model {
protected $table = 'categories';
public function movies()
{
return $this->belongsToMany('Your\Namespace\To\Movie');
}
}
Your Movie class
class Movie extends \Model {
protected $table = 'movies';
public function categories()
{
return $this->belongsToMany('Your\Namespace\To\Category');
}
}
Your route is still :
Route::get('categorie/{category_id}','WelcomeController#getFilmeByCateg');
So in your controller method you do the following :
public function getFilmeByCateg($category_id)
{
$category = Category::findOrFail($category_id);
return view('filme')->with('Movies',$category->movies()->paginate(12)->ge())->with('categorii',Category::all());
}
Now in your view $Movies->render() will work
If you wont paginate your data how would you render it ? Documents on the official laravel website states :
There are several ways to paginate items. The simplest is by using the paginate method on the query builder or an Eloquent model.
Paging database results
$users = DB::table('users')->paginate(15);
Note: Currently, pagination operations that use a groupBy statement cannot be executed efficiently by Laravel. If you need to use a groupBy with a paginated result set, it is recommended that you query the database and create a paginator manually.
Creating A Paginator Manually
Sometimes you may wish to create a pagination instance manually,
passing it an array of items. You may do so by creating either an
Illuminate\Pagination\Paginator or
Illuminate\Pagination\LengthAwarePaginator instance, depending on your
needs.
Now in your case you should first
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
and then
$paginator = new Paginator($items, $count, $limit, $page, [
'path' => $this->request->url(),
'query' => $this->request->query(),
]);
Please supply parameters accordingly in the Paginator function above.
I know it is very late but it will help someone who get in to same issue. Instead of ->get() use ->paginate(10) in query.
I am building my first sailsjs and nodejs application, and it great :)
My situation, I have about 100 tables with the same stucture, I would like to decide "on the fly" which table to load.
my first thought was use somehow a dynamic class names. But I dont know how to do this with nodejs, maybe some one have an idea.
So I would create 100 "modelName".js files in my models folder.
I can use this in browser
window["fileName"].find()....
But I don't have any window object in nodejs
Second idea was to pass the tableName to the model, the problem is, I have to reinit the model, don't know how.
Any solutions?
Found a solution
var modelName = req.param('p');
this[modelName].find()...
Own answer by author is correct, but I will add something just for people who will use it in the future - you can get modelName from req.options.model when you are using Blueprints.
Unfortunately you can't use this[modelName] as option is giving you model name starting with small letter, so first you have to upper case first letter with e.g. var modelName = req.options.model.charAt(0).toUpperCase() + req.options.model.slice(1);
and then you are free to use this[modelName].whateverYouNeed
I used it for generic policy to let user editing only his own group elements.
var modelName = req.options.model.charAt(0).toUpperCase() + req.options.model.slice(1)
var elementID = null
if (req.params.id) { // To handle DELETE, PUT
elementID = req.params.id
}
if (req.body.id) { // To handle POST
elementID = req.body.id
}
this[modelName].findOne({
id: elementID
}).exec(function(err, contextElement) {
if(err) {
return res.serverError(err)
}
if(contextElement.group=== req.user.group.id) {
sails.log('accessing own: ' + modelName)
return next()
}
else {
return res.forbidden('Tried to access not owned object')
}
})
An alternative:
sails.models[Model].findOne({...})
Make sure to have your "Model" name as string in lowercase. It works like accessing a property inside an object
Another option that worked for me:
var modelName = "User";
global[modelName].find()....
I have a table called "Users" that has a column called "deleted", a boolean indicating that the user is "Deleted" from the system (without actually deleting it, of course).
I also have a lot of tables that have a FK to the Users.user_id column. Subsonic generates (very nicely) the code for all the foreign keys in a similar manner:
public IQueryable<person> user
{
get
{
var repo=user.GetRepo();
return from items in repo.GetAll()
where items.user_id == _user_id
select items;
}
}
Whilst this is good and all, is there a way to generate the code in such a way to always filter out the "Deleted" users too?
In the office here, the only suggestion we can think of is to use a partial class and extend it. This is obviously a pain when there are lots and lots of classes using the User table, not to mention the fact that it's easy to inadvertently use the wrong property (User vs ActiveUser in this example):
public IQueryable<User> ActiveUser
{
get
{
var repo=User.GetRepo();
return from items in repo.GetAll()
where items.user_id == _user_id and items.deleted == 0
select items;
}
}
Any ideas?
You need to change following code in your ActiveRecord.tt file and regenerate your code:
Following is code is located under : #region ' Foreign Keys '
Update: I've updated code for your comment for checking if delete column is available then only apply delete condition.
HasLogicalDelete() - This function will return true if table has "deleted" or "isdeleted" column, false otherwise.
public IQueryable<<#=fk.OtherClass #>> <#=propName #>
{
get
{
var repo=<#=Namespace #>.<#=fk.OtherClass#>.GetRepo();
<#if(tbl.HasLogicalDelete()){#>
return from items in repo.GetAll()
where items.<#=CleanUp(fk.OtherColumn)#> == _<#=CleanUp(fk.ThisColumn)#> && items.deleted == 0
select items;
<#}else{#>
return from items in repo.GetAll()
where items.<#=CleanUp(fk.OtherColumn)#> == _<#=CleanUp(fk.ThisColumn)#>
select items;
<#}#>
}
}
Are you using Subsonic3? If so then you can actually edit the templates to modify the way the Data Access Layer classes are generated.