I am working on a project and I just wanted to get some help on how to approach this problem and help clean up the code and remove a lot of duplication.
I have an FAQ database table with "id, user_id, faq_question, faq_text, sort_id". Now every time a dynamic site is created, I have an insert query which creates the site and then also inserts into the FAQ table with the default questions and answers that I want to use.
$insertFAQ = array(
'user_id' => $user_id,
'faq_question' => 'Default Question',
'faq_text' => 'Default Answer',
'sort_id' => '1'
);
$Db->insert('faq', $insertFAQ);
The sort_id is there because they have the ability to drag the questions in a different order and it updates the database and shows on the website in the new order.
Now the problem is that I want to have 10 default FAQ questions and answers that will be created for every website. If we get hundreds of sites in the database, that will be a ton of records in the FAQ database table which is pretty much all duplicated.
I know there has to be an easier way to do it, the only thing that throws me off is the sort_id because they could all have the same questions and answers but sort them differently. And then of course they have the ability to add custom questions.
P.S. - How can I add multiple questions/answers in the above array that I posted?
AS for having multiple question in the array you posted in the question, you can just have a multi dimensional array:
$insertFAQ = array(
0 =>array(
'user_id' => $user_id,
'faq_question' => 'Default Question',
'faq_text' => 'Default Answer',
'sort_id' => '1'
),
1 =>array(
'user_id' => $user_id,
'faq_question' => 'Default Question',
'faq_text' => 'Default Answer',
'sort_id' => '1'
),
2 => array(
'user_id' => $user_id,
'faq_question' => 'Default Question',
'faq_text' => 'Default Answer',
'sort_id' => '1'
) );`
and so on and son on.
as for the sorting -
You can create a separate table that will just hold the sorting order, site id and question id.
id, faq_id, site_id, sort_order
Id a new site is created you just create a new entry for it in this table, and than the questions don't have to repeat themselves, your only specifying what order goes to what site.
One way would be to have a site_id field in the table. For global faqs the site_id could be null.
Then to get the faqs for a site:
SELECT * FROM faq WHERE site_id IS NULL OR site_id = $site_id
If I understand correctly your question... To me it seems you simply need to split the question/answer from the user record, and have instead a reference to a question/answer entry in the user record:
// insert if it doesn't exist
$insertFAQ = array(
'faq_id' => $faq_id,
'faq_question' => 'Default Question',
'faq_text' => 'Default Answer'
);
// get faq id of newly inserted or existing faq entry
...
// insert user
$insertUser = array(
'user_id' => $user_id,
'faq_id' => $faq_id,
'sort_id' => '1'
);
Related
Recently I've started using CakePHP by starting from using Bake to get simple CRUD.
I have one Model called "Contract" which is associated with "Product" and "Customer" as following.
public $belongsTo = array(
'Customer' => array(
'className' => 'Customer',
'foreignKey' => 'customer_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Now I wanted to customize the add view (add.ctp) created by Bake by not using select box as an input form for customer_id and product_id. Instead of using select box which shows all the possible name of product/customer that are associated with foreign key I want to use text field so that people can type in one of the field (for example product_code, customer_code) from Product / Customer table and convert it to foreign key to create a row for Contract table. In case there is a chance that no values be found from Customer / Product field I want to skip adding a row to Contract table.
I would like to know if this is possible.
Thank you,
The basic concept is this: Use a JS lib of your choice, guess it's jquery. Watch the text field for changes, on change make an ajax GET request with the value of the text field to a controller / action of your app that returns json. On click on one of the results set the id of the selected record to a hidden field. Done.
If you search Google or Stackoverflow you'll find plenty of examples.
I`m having a problem with the Containable behaviour.
I would like to know if there is any way to access the contained model attributes for operations like ordering.
For example, I have a model B which belongs to a Model A. I need to order objects of B using an attribute (integer) of A. It would be something like:
'contain' => array(
'A' => array(
'B' => array(
'order' => 'A.integer_attribute'
)
)
)
I know that there are easier ways to do this without Containable, but for reasons which are not worth being detailed here, I need to use it. This is an abstract example, in truth model A belongs to other models and this is just a small part of a deep containable tree.
I'd be very glad with any help!
EDIT
OK, I'll try my best to describe the situation without being unclear:
I have 4 models: User, Category, Field and UserField, and their relationships are as follows:
Category hasMany User
Category hasMany Field
User hasMany UserField
Field hasMany UserField
The opposite of these relations are all belongsTo. The purpose here is that the user belongs to a category, which has many fields that he needs to fill with his information (city, state etc). The information he fills is stored in the UserField table, as each information needs to have its field and the user who provided it.
That said, I need to build a screen which displays, for each category, a list of users and their information. So, I retrieve all the categories and its fields, so I can build a table for each category. Each field has an attribute "no_order", which is the number that indicates the order in which the field appears.
At the same time, I need all of each category's users to display them correctly in the tables. Finally, and there's the problem, I need to have UserField objects ordered by the "no_order" of their respective fields, for each user. So I ended up with something like:
$categories = $this->Category->find('all', array(
'order' => 'Category.name',
'contain' => array(
'Field',
'User' => array(
'UserField' => array(
'order' => 'Field.no_order'
)
)
)
));
But this doesn't work, since UserField cannot reach its respective Field's no_order from there.
I apologize if this wasn't clear enough, but for anyone who would spend a little while reading this, I would be VERY grateful for your help!
I am not shure for what do you want so, but I thing that should use joins of cakephp
$A = $this->A->find('all', array(
'joins' => array(
array(
'table' => 'B',
'alias' => 'B',
'type' => 'LEFT',
'conditions' => array(
'B.field_id = A.id'
)
)
),
'conditions' => array(
'B.field' => 'if_you_want_more_conditions'
),
'contain' => array(
'A' => array(
'B' => array(
'order' => 'A.integer_attribute'
)
)
),
'fields' => array('A.field','B.field'),
'recursive' => -1
));
I finally came up with a solution. I don't know how efficient it is, but there it goes:
'contain' => array(
'Field',
'User' => array(
'User.privilege = "Solicitante"'
'UserField' => array(
'order' => '(SELECT f.no_order FROM fields AS f WHERE UserField.field_id = f.id LIMIT 1)'
)
)
)
Having the raw query solved my problem. Hope it helps anybody who comes across a similar problem!
I'm trying to store my data in an empty database using Transactions. Now this doesn't work for me. This is my ERD: https://www.dropbox.com/s/pfakd7xplsvr7cc/db_erd.PNG
Notice that at first all three tables are empty. And I send the following array to the database:
array(
'Product' => array(
'slug' => 'c24b626d6701d3b07e30b233b989ff8811',
'product_name' => 'DAHLIA 5A',
'price_new' => '159.00',
'affiliate_url' => 'http://example.com/feed.xml',
'product_id_supplier' => 'c24b626d670133d3b07e30b2b989ff8811',
'supplier_id' => 'some_supplier',
'feedimport_id' => (int) 1
),
'Image' => array(
(int) 0 => array(
'image' => 'product_image.JPG'
)
),
'Term' => array(
'Term' => array(
(int) 0 => array(
'name' => 'Tommy Hilfiger'
)
)
)
)
Using $this->getDataSource(); . But it only fills the images and products table. If I look at the log file, it appears it tries to select from the products_terms table, after inserting the products and images table, and ofcourse it receives nothing back. And there it stops.... It doesn't try to insert in the terms table either.
Now if I look at tutorials like these: http://www.pabloleanomartinet.com/cakephp-2-x-saving-and-validating-a-habtm-relation-example/
It looks like I don't do anything wrong... But if I look closely it looks like the TAGS table in the tutorial is filled already.... So probably that's the problem? But I don't know how to get a Term ID first, before inserting the product and linking them together, using transactions....
Anyone who does?
Problem solved. It seems like the Terms table needs to be filled, else cakePHP doesn't know which term to connect to.
So the proper solution is to first check or the Term exists, else insert the Term in the Terms table, and then insert the products using HABTM (using the ID of the Term in the insert array), so the products_terms table will be filled also.
I'm building a site that works as a social network.
I have a profiles table and a Profile model.
Profile HABTM Interests
Profile HABTM Hobbies
etc...
There are a couple of pre-defined records in the hobbies and interests tables, which users can choose from to display on their profile. But they also have the option to add their own hobbies and interests if theirs isn't pre-defined.
A problem arises when the user chooses to add a hobby or interest which doesn't exist yet. Saving the pre-defined records to the join table is not a problem. But I can't seem to create new entries in the interests and hobbies tables during the same save call.
Below is a snippet of the data array I've tried to save (I've also tried other combinations, but this one seems the most logical):
array(
'Interests' => array(
'Interests' => array(
(int) 0 => '1',
(int) 1 => '2',
(int) 2 => '5'
),
'name' => 'Internet' //this is the user-created entry
),
'Hobbies' => array(
'Hobbies' => array(
(int) 0 => '1',
(int) 1 => '2',
(int) 2 => '5',
(int) 3 => '7',
(int) 4 => '8'
),
'name' => 'Reading' //this is also a user-created entry
)
)
The save function: $this->Profile->saveAll($this->request->data, array('deep' => true))
The problem is that the new entries (in this case "Reading" and "Internet") are being ignored during the save. There is no error and everything else is being saved just fine.
If I call $this->Profile->Hobby->saveAll($this->request->data) then it does work, but only for the Hobby model. The new interest is ignored of course, because the save call doesn't go through its model.
Is the data array not formatted correctly, or is this just not possible to achieve in one save call?
You might want to call saveAll using Profile, not Hobby.
$this->Profile->saveAll($this->request->data)
I'm using the Containable behavior to get a list of Comments (belongsTo Post, which belongs to Question; Question hasMany Post, and Post hasMany Comments; all of these belong to Users).
$data = $this->Question->find ( 'first',
array ('contain' =>
array ('User',
'Post' => array ('User', /* 'order' => 'User.created DESC'*/ )
)
)
);
It works, when I comment out the section in comments above. I suppose this is to be expected, but what I want is all of the Posts that are found, should be sorted in order of the 'created' field of the 'User' they belong to. How do I accomplish this deeper level sorting in CakePHP? I always get, "Warning (512): SQL Error: 1054: Unknown column 'User.created' in 'order clause'"
Thanks for your help!
Also, you might be trying to group on a related table from a find call that doesn't use joins.
Set your debug level to something greater than 1 so you can see the query log and make sure that Cake isn't doing two queries to fetch your data. If that is the case then the first query is not actually referencing the second table.
If you want to manually force a join in these situations you can use the Ad-Hoc joins method outlined by Nate at the following link.
http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find
I have found two ways to get around this.
The first is to define the second level associacion directly in the model.
Now you will have access to this data everywhere.
It should look something like this.....
var $belongsTo = array(
'Foo' => array(
'className' => 'Foo', //unique name of 1st level join ( Model Name )
'foreignKey' => 'foo_id', //key to use for join
'conditions' => '',
'fields' => '',
'order' => ''
),
'Bar' => array(
'className' => 'Bar', //name of 2nd level join ( Model Name )
'foreignKey' => false,
'conditions' => array(
'Bar.id = Foo.bar_id' //id of 2nd lvl table = associated column in 1st level join
),
'fields' => '',
'order' => ''
)
);
The problem with this method is that it could make general queries more complex than they need be.
You can thus also add the second level queries directly into te find or paginate statement as follows: (Note: I found that for some reason you can't use the $belongsTo associations in the second level joins and will need to redefine them if they are already defined. eg if 'Foo' is already defined in $belongsTo, you need to create a duplicate 'Foo1' to make the association work, like the example below.)
$options['joins'] = array(
array('table' => 'foos',
'alias' => 'Foo1',
'type' => 'inner',
'conditions' => array(
'CurrentModel.foo_id = Foo1.id'
)
),
array('table' => 'bars',
'alias' => 'Bar',
'type' => 'inner',
'foreignKey' => false,
'conditions' => array(
'Bar.id = Foo1.bar_id'
)
)
);
$options['conditions'] = array('Bar.column' => "value");
$this->paginate = $options;
$[modelname] = $this->paginate();
$this->set(compact('[modelname]'));
I hope this is clear enough to understand and that it helps someone.
Check your recursive value. If it's too limiting, it will ignore the containable links, IIRC. I remember bumping into this a few times. I'd try containing multiple models, but my recursive option was set to 0 and nothing would get pulled. For your example, I'd think that a value of 1 (the default) would suffice, but maybe you've explicitly set it to 0 somewhere?
You can add before your call to find() the following:
$this->Question->order = 'Question.created DESC';
Yeah, I couldn't work out how to sort based on the related/associated model, so ended up using the Set::sort() method. Checkout this article for a good explanation.
// This finds all FAQ articles sorted by:
// Category.sortorder, then Category.id, then Faq.displaying_order
$faqs = $this->Faq->find('all', array('order' => 'displaying_order'));
$faqs = Set::sort($faqs, '{n}.Category.id', 'ASC');
$faqs = Set::sort($faqs, '{n}.Category.sortorder', 'ASC');
...And yes, it should probably be a Category->find() but unfortunately the original developer didn't code it that way, and I didn't wanna rework the views.