Proč je ActiveRow read only?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Maekoboss
Člen | 36
+
+3
-

Ahoj, proč je ve verzi Nette 2.1 třída ActiveRow pouze pro čtení? Někdy prostě potřebuju k datům z DB přihodit nějaké doplňující informace, které získám někde bokem ale vztahují se k datům z tabulky. Dřív jsem to dělal tak, že jsem si prostě k objektu ActiveRow přidal přes offsetSet() co bylo potřeba a všechny logická data jsem měl sdružená v jednom.

Nějak tohle omezení nechápu. Pokud by mi to někdo fundovanější vysvětlil a případně i navrhl alternativu k tomuto přístupu. Konvertovat objekt ActiveRow na array a pak případně zpět mi nepřijde jako dobrý řešení.

Díky

hrach
Člen | 1834
+
0
-

No proc, protoze to reprezenuje radek z db, a ne container na data. Mj. z jinych duvodu se nyni pouziva jen metoda update() na aktualizaci dat. Ale nevim, jestli ti to prijde jako dostatecne duvody :D

David Grudl
Nette Core | 8082
+
0
-

Alternativou by asi bylo si udělat obálku (lépe kompozicí než děděním), která by __set podporovala a __get měl fallback na původní ActiveRow.

koren
Člen | 58
+
0
-

Nemohl byste prosím někdo načrtnout tu alternativu? pracoval jsem s AR dosud tak jak píše Maekoboss a po přechodu na 2.1 celkem tápu :/

JDC
Člen | 19
+
0
-

koren napsal(a):

Nemohl byste prosím někdo načrtnout tu alternativu? pracoval jsem s AR dosud tak jak píše Maekoboss a po přechodu na 2.1 celkem tápu :/

Nevím, jestli lze už v Nette 2.1 u Selection změnit třídu pro ActiveRow, ale když jsem potřeboval vlastní třídu reprezentující řádek z db, napsal jsem si obálku nad Selection a pak nad ActiveRow a v modelu jsem pak v metodách pro získání dat vytvářel instance toho „RowCollection“ ala obálka Selection a do konstruktoru jí předával Selection. RowCollection mi pak při foreachu či přes metodu fetch() (nebo toArray()) „vytvářel“ instance mojí tříd zaobalující ActiveRow a opět v konstruktoru jí předáš normál ActiveRow, který ti vyplivne Selection.

Update pak máš jednoduchý. Jak už ti tu říkal David Grundl, přepíšeš si magic metodu __set, která ti ty data bude ukládat do nějakého arraye (nebo prostě někam, kam si je můžeš dočasně uložit) a metodou update v ActiveRowu mu to pole předáš jako parametr. (Myslím, že by to tak mělo v nette 2.1 fungovat, ale nezkoušel jsem to, vlastní obálku nad ActiveRow jsem používal jen pro čtení dat, ukládání jsem řešil v modelu přes repozitář) Pro ten update si vytvoříš vlastní metodu update, která ti „zaobalí“ metodu update ActiveRowu s tím, že mu automaticky předá parametry.

Pak by to už mělo stačit volat takhle:

$tvujrow->sloupec1 = "hodnota1"
$tvujrow->sloupec2 = "hodnota2"
...
$tvujrow->update(); //Zapíše do db

Pro ukázku nástřel obálky ActiveRowu (dána narychlo z hlavy):

class TvujRow extends Nette\Object {

	/** @var ActiveRow */
	protected $row;
	/** @var array */
	protected $toSave;

	public function __construct(ActiveRow $row) {
		$this->row = $row;
		$this->toSave = array(); //Je lépe ji takto inicializovat, aby to někde neřvalo, že přistupuješ k neexistujícímu objektu. Nebo si to můžeš udělat lazy v __set
	}

	public function update() {
		$this->update($this->toSave);
	}

	public function __set($column, $value) {
		//Dummy řešení
		$this->toSave[$column] = $value;
	}

	public function &__get($column) {
		//Asi nejednoduššeji
		return $this->row->$column;
	}
}

Snad ti to k něčemu bude, jako nástřel či jako inspirace ti to snad poslouží. Střílel jsem to narychlo z hlavy bez zkoušení, takže pokud se mi tam někam vloudila chybička, pak se omlouvám, ale snad si s tím nějak poradíš ;) Na obálku nad Selection si snad přijdeš, tam je to podobný, jen si tam pak frontenduješ ještě metody toho Selectionu, případně to můžeš použít jen jako kolekci pro ukládání těch ActiveRowů, to už je na tobě.

Editoval JDC (20. 1. 2014 14:13)

jik
Člen | 146
+
0
-

Mám stejný problém, jako Maekoboss. A vůbec mi nejde o to, nějak data upravovat k tomu, abych je ukládal, ale výhradně o to, abych data z tabulky doplnil dalšími (například počtem záznamů podřízené tabulky) a zobrazil. Zatím všechny funkční postupy jsou neohrabané a navíc mám podezření, že s tím souvisí i toto

Tomáš Kolinger
Člen | 136
+
0
-

Počet záznamů se dá v pohodě získat rovnou dotazem – sub-selectem/agregační funkcí. Takže ani nebudeš potřebovat nějak řádek dodatečně ovlivnit.

Pokud vážně potřebuješ přidat něco úplně mimo (což na druhou stranu tam nemá co dělat), tak budeš muset všechny data překopírovat do nějaké volné struktury. Třeba ArrayHash/stdObject…

Editoval Tomáš Kolinger (1. 4. 2014 10:05)

jik
Člen | 146
+
0
-

Tomáš Kolinger napsal(a):

Počet záznamů se dá v pohodě získat rovnou dotazem – sub-selectem/agregační funkcí. Takže ani nebudeš potřebovat nějak řádek dodatečně ovlivnit.

To je pravda, ale některé konstrukce jsou dost složité a tak bych modely zaplevelil hromadou složitých jednoúčelových dotazů, navíc leckdy s podmínkami. Zatím jsem se snažil držet modely jednoduché.

Pokud vážně potřebuješ přidat něco úplně mimo (což na druhou stranu tam nemá co dělat), tak budeš muset všechny data překopírovat do nějaké volné struktury. Třeba ArrayHash/stdObject…

Mám složitější výstupy, než jen vyjet jednu tabulku. Zatím jsem to také dělal přes array, nebo stdClass, ale jednak se mi to zdá komplikované, jednak mám podezření, že to zanáší další chyby. Proč vlastně nejde přeměnit objekt selection na data container a zapisovat rovnou do něj:

$select = $this->context->table->getTable();
$data = $select->fetchAll();
foreach $data as $dato {
	$dato->nove = 'neco';
}
$this->template->data = $data;
jandik.n
Člen | 41
+
0
-

Proč vlastně nejde přeměnit objekt selection na data container a zapisovat rovnou do něj:

$select = $this->context->table->getTable();
$data = $select->fetchAll();
foreach $data as $dato {
	$dato->nove = 'neco';
}
$this->template->data = $data;

Tohle by se mi přesně také hodilo. Byl jsem zvyklý to také řešit přes pole.

Můžete mi prosím poradit, jak v modelu, když si načtu údaje o zákazníkovi, přidat k výsledku záznam např. s jeho celkovým počtem zakázek? – mám tabulku zakázky, kde je uvedeno ID zákazníka.

Díky!

Tomáš Kolinger
Člen | 136
+
0
-

Faktem je, že to co chcete nedává moc smysl. Tabulka či pohled v databázi přece taky za chodu strukturu nemění. Z databáze získáš entitu, která má samozřejmě fixní atributy. Pokud chceš nějaké „přidat“, tak vytvoříš úplně jinou entitu. A to samo o sobě říká, že na jinou strukturu budeš potřebovat i jinou obálku. Chápu, že tohle se občas může hodit ale není to potřeba. Například – dneska se hojně používají ORM, kde je něco podobného naprosto nemyslitelné a prostě to nejde (bez hackování). A staví se na tom naprosto normální a plně funkční aplikace…

Takže pokud to chceš udělat, tak si nejdřív zeptej, zda to potřebuješ a zda to nemůže nahradit pohled či specializovaný dotaz. Když ani to nepomůže, tak si budeš muset pro-iterovat všechny entity co ti databázová vrstva vráti a přeskládat je do jiný struktury, jak už jsem psal výše.

@jandik.n
V tvém připadě ti stačí upravit dotaz či použít pohled, pokud to používáš častějí, kde použiješ sub-select.

SELECT c.id, c.name, (SELECT COUNT(o.id) FROM orders o WHERE o.client_id = c.id) AS orders_count FROM clients c

Editoval Tomáš Kolinger (14. 4. 2014 9:03)