What is the best way to store complex models in ZF? In the example below, should each attribute be a separate model entirely, or should the item be a multi dimensional array (as shown below)?
object(Application_Model_Item)#79 (4) {
["_id":protected] => int(45)
["_name":protected] => string(5) "Bolts"
["_description":protected] => NULL
["_attributes":protected] => array(2) {
[0] => array(2) {
["id"] => string(1) "3"
["name"] => string(4) "Size"
}
[1] => array(2) {
["id"] => string(1) "4"
["name"] => string(6) "Length"
}
}
Thanks in advance.
It all depends on your use case:
Indexing by ID or Position:
If you would like speed when accessing a particular attribute, then index the attributes by their IDs instead of their index position.
If you would like to keep an order, then order them by index position and a position offset amount.
Independent Table Vs Local Array:
If the attributes are duplicated in multiple items, then have them as their own table, and reference the attributes to that table.
If the attributes are not refenced and are unique to each item, then using them as serialise-able arrays (for storage) is adequate than needing them to be their own table.
In the case of _attributes i would use array of objects.
So it attributes would be an array of new model Attribute()
I make a class for every business model entity
["_attributes":protected] => array(2) {
[0] => Object(Model_Attribute) {}
[1] => Object(Model_Attribute) {}
}
class Model_Attribute {
protected $id;
public function getId();
public function setId($id);
.
.
.
}
I suggest you look at Doctrine ORM 2.0 since it can support the design from the above.
Look at this page, it may give you a clue:
http://www.doctrine-project.org/projects/orm/2.0/docs/reference/association-mapping/en
Related
Model class MyModel has following behavior
public function behaviors() {
return [
'CTimestampBehavior' => [
'class' => 'zii.behaviors.CTimestampBehavior',
'createAttribute' => null,
'updateAttribute' => 'update_time',
'setUpdateOnCreate' => true
]
];
}
In code, in controller I write something like
$model = new MyModel();
$dataReader = Yii::app()->db->createCommand()
->selectDistinct('some fields')
->from('MyModel')
->leftJoin('some table')
->where('some criteria')
->query();
while ($item = $dataReader->readObject('MyMode', $model->getAttributes())) {
**//!!! HERE item array is with model attributes, which are empty**
$items[] = $item;
}
disaster, it's not working, items is array, each of element holding empty list of attributes, like no data fetched from db
If I write
$dataReader = Yii::app()->db->createCommand()
->selectDistinct('some fields')
->from('MyModel')
->leftJoin('some table')
->where('some criteria')
->query();
while ($item = $dataReader->readObject('MyModel', MyModel::model()->getAttributes())) {
//!!! HERE item array is with model attributes, which hold correct data, taken from db
$items[] = $item;
}
it's working
If I get rid off CTimestamp behavior, both cases work.
If I debug first case, I realize that, after pdo fetchobject is done, it calls constructor with scenario="current_timestamp()". Question is why? And where I missstepped?
If you read readObject() documentation you will find that second argument is not list of fields, but list of constructor arguments. CActiveRecord has only one constructor argument - $scenario. $dataReader->readObject('MyMode', $model->getAttributes()) essentially assigns random value as scenario, since it will get first value from $model->getAttributes(). In your case you probably need:
$item = $dataReader->readObject('MyModel', []);
I'm doing a mutli-index query With Tire and rails 3 and I want to filter out Venues who have approved => false so I need some sort of combo filter.
Here is the query
query = params[:q]
from = params.delete(:from)
size = params[:size] || 25
Tire.search(
[Venue.index_name,
Performer.index_name, User.index_name], load: true) do |s|
s.query do
string(query, fields: [:_all, :name, :title], use_dis_max: true)
end
s.from from if from
s.size size if size
end.results.to_a
This line removes all Performers and Users because they don't have an :approved field.
s.filter(:term, :approved => true )
And this line obviously removes all non-venues which is no good.
s.filter(:term, { :approved => true, :index_name => 'venues'} )
Any ideas besides adding an approved: true field to all Users and Performers? I think something like this is what I want conceptually:
s.filter(:term, :approved => true, :if => {:index_name => 'venues'} )
EDIT Thanks to Mallox I was able to find the Should construct but I'm still struggling to implement it Tire. It seems like the below code should work but it return no results on any query. I also remove the "{:terms => { :index_name => ["performers", "users"]}}," to make sure it wasn't my use of index name or multiple lines of query that was the problem and still no luck. Can anybody shed some light on how to do this in Tire?
s.filter(:bool, :should => [
{:terms => { :index_name => ["performers", "users"]}},
{:term => { :approved => true}},
] )
So i have little knowledge about Ruby and Tire, but the ElasticSearch query that you want to build would be based on a bool filter, that contains some "should" entries (which would translate into inclusive OR).
So in your case something along the lines of:
"filter" : {
"bool" : {
"should" : [
{
"terms" : { "_type" : ["Performers","Users"] }
},
{
"term" : { "approved" : true }
}
]
}
}
Take a look at the documentation here, maybe that'll help:
:http://www.elasticsearch.org/guide/reference/query-dsl/bool-filter/
has just started out Yii web app and encountered this problem, any suggestions are welcome:)
What i am trying to achieve:
-To display a form with tabs, each tab content contains a list of checkboxes from the same model.
-so user can select some items from tab 1, some from tab 2, etc and then click submit button to process.
Problem:
But i couldn't think of anyway such that the last tab activecheckboxlist will not clobbered the previous one up.
I am trying to to something similar to this : [www.yiiframework.com/forum/index.php/topic/20388-2-checkboxlist-and-1-model]
but instead of fixing it at 2, mine is dynamic.
What i have done so far:
<?php
$tabArray = array();
foreach ((Product::model()->listParentChild(0)) as $productparent) {
array_push($tabArray, array(
'label' => $productparent['name'],
'content' => CHtml::activeCheckBoxList(
$model, 'products', CHtml::listData(Product::model()->listParentChild($productparent['id']), 'id', 'name'), array(
'labelOptions' => array('style' => 'display:inline'),
'template' => '<div class="check-option">{input} {label}</div>',
'separator' => '',
)
), 'active' => ($productparent['id'] == 1 ? true : false),
));
}
?>
<?php
$this->widget('bootstrap.widgets.TbTabs', array(
'type' => 'tabs', // 'tabs' or 'pills'
'placement' => 'left',
'tabs' => $tabArray,
));
?>
and in my product model:
public function listParentChild($parentid) {
$sql = "SELECT * FROM piki_product WHERE parentid=:parentid";
$productlist = Yii::app()->db->createCommand($sql);
$productlist->bindValue(":parentid", $parentid, PDO::PARAM_INT);
return $productlist->queryAll();
}
any suggestions will be appreciated.. :/
I could be wrong, but I don't think cliffbarnes is on the right track with his comments about dynamic nesting. As far as I can tell, you're only dealing with one level of child products; it's just that there could be multiple sets of these child products.
In that case, the link you sited actually offers the correct solution:
<?php echo CHtml::checkBoxList('array1', CHtml::listData(Atributos::model()-> findAllByAttributes(array('tipo'=>'talla')), 'id_atributo','valor'))?>
<?php echo CHtml::checkBoxList('array2', CHtml::listData(Atributos::model()-> findAllByAttributes(array('tipo'=>'talla')), 'id_atributo','valor'))?>
Each set of checkboxes is given a different name (array1, and array2), so that each field's selected values doesn't override the other. In your case, the solution is the same; you just need to make the field names dynamic. I.E.
foreach ((Product::model()->listParentChild(0)) as $productparent) {
$fieldname = 'product' . $productparent['id'];
echo CHtml::checkBoxList($fieldname, ... (etc)
Within your controller you would check to see whether there are results for each dynamic field name.
foreach ((Product::model()->listParentChild(0)) as $productparent) {
if (isset($_POST['product' . $productparent['id']]) {
// Add values to $model->product
}
}
An even better solution would be to output each checkbox individually, so you can create one array of results, indexed by child ID.
foreach ((Product::model()->listParentChild(0)) as $productparent) {
foreach (Product::model()->listParentChild($productparent['id']) as $child) {
CHtml::checkBox("product[{$child['id']}]", ... (etc)
Then in your controller, all you'd have to do is this:
if (isset($_POST['product']) && count($_POST['product']) > 0) {
$model->product = array_keys($_POST['product']);
}
This solution does not work with activeCheckBoxList(). It would work if you wanted to override the __get() and __set() magic methods to make these dynamic property names available to your model, but that's probably over kill.
Edit (as per request)
If you need to have default selections for your checkboxes, you can just pass them as the second argument of CHtml::checkBoxList(). http://www.yiiframework.com/doc/api/1.1/CHtml#checkBoxList-detail
But if you still want to use __get() and __set(), here's an example:
class YourModel extends CActiveRecord {
// I usually create a placeholder to contain the values of my virtual attribute
protected $_childValues = array();
public function __get($name) {
// The following regular expression finds attributes
// with the name product_{parent ID}
if (preg_match("/^product_\d+$/", $name)) {
// I put the underscore in the name so I could get
// parent ID easier.
list($junk, $id) = explode("_", $name);
if (!isset($this->_childValues[$id])) {
$this->_childValues[$id] = array();
}
return $this->_childValues[$id];
}
else {
// Make sure to still call the parent's __get() method
return parent::__get($name);
}
}
public function __set($name, $value) {
// Same regex as above
if (preg_match("/^product_\d+$/", $name)) {
list($junk, $id) = explode("_", $name);
$this->_childValues[$id] = $value;
}
else {
// Make sure to still call the parent's __set() method
parent::__set($name, $value);
}
}
}
$model = new YourModel;
// Any property in the format of product_{parent ID} is available
// through your model.
echo $model->product_1;
$model->product_300 = array();
You might also consider checking to see if the parent ID in a property name corresponds with a parent ID in the database, instead of just allowing any property in that format to pass through.
I am following an article here and I think I want to go to hash route but I am confused as to what if I have multiple taxi_car
<?php
$redis->hset("taxi_car", "brand", "Toyota");
$redis->hset("taxi_car", "model", "Yaris");
$redis->hset("taxi_car", "license number", "RO-01-PHP");
$redis->hset("taxi_car", "year of fabrication", 2010);
$redis->hset("taxi_car", "nr_starts", 0);
/*
$redis->hmset("taxi_car", array(
"brand" => "Toyota",
"model" => "Yaris",
"license number" => "RO-01-PHP",
"year of fabrication" => 2010,
"nr_stats" => 0)
);
*/
echo "License number: " .
$redis->hget("taxi_car", "license number") . "<br>";
// remove license number
$redis->hdel("taxi_car", "license number");
// increment number of starts
$redis->hincrby("taxi_car", "nr_starts", 1);
$taxi_car = $redis->hgetall("taxi_car");
echo "All info about taxi car";
echo "<pre>";
var_dump($taxi_car);
echo "</pre>";
how would I create a database that has all the data about taxi_cars in redis. Now I know there is no database but keys in redis, but I am using the relational lingo here to express myself. If I have 1000 taxi_cars I do not want to have 1000 initial keys. It has to be subset of something. I am not sure how to even explain this.
EDIT
so lets say that under brand I have Toyota, Honda, Suzuki
and then under those I have different styles like Tacoma, Accord etc
how would I go on inserting that data and leter on retrieving it.
thanks
You should not try to map relational model concepts to a NoSQL store like Redis, but rather think in term of data structures and access paths.
Here are a number of simple examples:
Porting from SQLite to Redis
Work with keys in redis
how to have relations many to many in redis
Here you want to store records of car models. You first need something to identify them (i.e. a primary key in the relational terminology). Then you can store these records in the top level dictionary of Redis.
You should not store:
taxi_car => hash{ brand" => "Toyota", "model" => "Yaris", etc ... }
but:
taxi_car:1 => hash{ brand" => "Toyota", "model" => "Yaris", etc ... }
taxi_car:2 => hash{ brand" => "Toyota", "model" => "Prius", etc ... }
taxi_car:3 => hash{ brand" => "Tesla", "model" => "Model_S", etc ... }
Now, you need to anticipate the access paths. For instance to retrieve the cars per brand and model, you need to add extra sets (to be used as indexes):
brand:Toyota => set{ 1 2 }
brand:Tesla => set{ 3 }
model:Yaris => set{ 1 }
model:Prius => set{ 2 }
model:Model_S => set{ 3 }
So you can retrieve:
# Per brand
SMEMBERS brand:Toyota
-> returns 1 2
HGETALL taxi_car:1
HGETALL taxi_car:2
# Per model
SMEMBERS model:Prius
-> returns 2
HGETALL taxi_car:2
# Per brand and per model (a bit useless here), plus associated data
# sort is used to get all the taxi_car data in one shot
sinterstore tmp brand:Toyota model:Prius
sort tmp by nosort get taxi_car:*->brand get taxi_car:*->model etc ...
I have this fluent mapping:
sealed class WorkPostClassMap : ClassMap<WorkPost>
{
public WorkPostClassMap()
{
Not.LazyLoad();
Id(post => post.Id).GeneratedBy.Identity().UnsavedValue(0);
Map(post => post.WorkDone);
References(post => post.Item).Column("workItemId").Not.Nullable();
References(Reveal.Property<WorkPost, WorkPostSheet>("Owner"), "sheetId").Not.Nullable();
}
parent class:
sealed class WorkPostSheetClassMap : ClassMap<WorkPostSheet>
{
public WorkPostSheetClassMap()
{
Id(sheet => sheet.Id).GeneratedBy.Identity().UnsavedValue(0);
Component(sheet => sheet.Period, period =>
{
period.Map(p => p.From, "PeriodFrom");
period.Map(p => p.To, "PeriodTo");
});
References(sheet => sheet.Owner, "userId").Not.Nullable();
HasMany(sheet => sheet.WorkPosts).KeyColumn("sheetId").AsList();
}
WorkItem class:
sealed class WorkItemClassMap : ClassMap<WorkItem>
{
public WorkItemClassMap()
{
Not.LazyLoad();
Id(wi => wi.Id).GeneratedBy.Assigned();
Map(wi => wi.Description).Length(500);
Version(wi => wi.LastChanged).UnsavedValue(new DateTime().ToString());
}
}
And when a collection of WorkPosts are lazy loaded from the owning work post sheet I get the following select statement:
SELECT workposts0_.sheetId as sheetId2_,
workposts0_.Id as Id2_,
workposts0_.idx as idx2_,
workposts0_.Id as Id2_1_,
workposts0_.WorkDone as WorkDone2_1_,
workposts0_.workItemId as workItemId2_1_,
workposts0_.sheetId as sheetId2_1_,
workitem1_.Id as Id1_0_,
workitem1_.LastChanged as LastChan2_1_0_,
workitem1_.Description as Descript3_1_0_
FROM "WorkPost" workposts0_
inner join "WorkItem" workitem1_ on workposts0_.workItemId=workitem1_.Id
WHERE workposts0_.sheetId=#p0;#p0 = 1
No, there are a couple of things here which doesn't make sence to me:
The workpost "Id" property occurs twice in the select statement
There is a select refering to a column named "idx" but that column is not a part of any fluent mapping.
Anyone who can help shed some light on this?
The idx column is in the select list because you have mapped Sheet.WorkPosts as an ordered list. The idx column is required to set the item order in the list. I'm not sure why the id property is in the select statement twice.
By the way, an unsaved value of 0 is the default for identity fields, so you can remove .UnsavedValue(0) if you want.