How can i call a requestAction method from my view, to specific controller, which returns me results on basis of conditions i mention?
Thanks !
Generally speaking, using requestAction is not very performant, because it begins a whole new dispatch process -- in essence your application is handling two requests for every one request made by a user. For this reason, you want to avoid using it as much as possible. That being said, requestAction has its uses, and you can mitigate the performance hit using view caching.
It's hard to gauge precisely what you want to do with requestAction, but the fundamental concept is you're asking CakePHP to process another request, so you can simply pass requestAction any URL your app would handle if it were typed into a browser's address bar (excluding the protocol and hostname). If you wanted to retrieve a collection of blogs managed by your application:
$blogs = $this->requestAction('/blogs/index');
You can also invoke requestAction by passing it an array of route components, in the same way as you might pass them into HtmlHelper::link. So you might retrieve the collection of blogs thus:
$blogs = $this->requestAction('controller'=>'blogs', 'action'=>'index');
Insofar as filtering the result-set returned by requestAction, it again is done by passing the conditions as part of the URL or route components:
$blogs = $this->requestAction('/blogs/index/author_id:1234');
// or
$blogs = $this->requestAction('controller'=>'blogs', 'action'=>'index', 'author_id' => 1234);
Note that if you want your requested action to return values, you need the action to handle requests differently than standard action requests. For the BlogsController::index action I've been referring to above, it might look something like this:
class BlogsController extends AppController{
function index(){
$conditions = array();
if ( isset($this->params['author_id'])){
$conditions['Blog.author_id'] = $this->params['author_id'];
}
$blogs = $this->Blog->find('all', array('conditions'=>$conditions);
if ( isset($this->params['requested']) && $this->params['requested'] == 1){
return $blogs;
}
else{
$this->set(compact('blogs'));
}
}
}
The if statement checking the presence and value of $this->params['requested'] is the key part. It checks whether the action was invoked by requestAction or not. If it was, it returns the collection of blogs returned by Blog::find; otherwise, it makes the collection available to the view, and allows the controller to continue onto rendering the view.
There are many nuances to using requestAction to arrive at the specific results you require, but the above should provide you with the basics. Check out the links posted by dogmatic69 for additional documentation, and of course there are numerous stacko questions on the subject.
Feel free to comment with any follow-ups! Hope this helped.
$comments = $this->requestAction('/comments/latest');
foreach($comments as $comment) {
echo $comment['Comment']['title'];
}
put this code in your ctp file
Related
The question I have is about how to store a collection of models to a RESTful back end.
I'm using Backbone.js, and I'm considering to either:
Use Async.js parallel method and post / put each model separately in a loop, after which a general callback method is triggered;
Send a collection of objects to the back end, and use a database transaction to make sure that all models are properly saved with a single commit;
The first method seems to cause a lot of overhead, because I have to make different calls to save the models.
But when considering the second approach, Laravel4 does not by default allows to perform a post / put on a collection.
What would be your preferred approach, and more importantly, why?
The second approach is definitely the way to go in my opinion, it reduces the latency and saves bandwidth if you use it with a large number of models since all the HTTP stuff (headers, etc) will be sent only once.
In your controller, use something like this (it may not work, I don't have access to any Laravel installation to test it) :
public function postCollection() {
$collection = Input::get("collection");
DB::transaction(function() {
foreach ($collection as $data) {
// In this example we assume it's a collection of users
// Of course in a real app you would also do input validation
$user = User::create(["name" => $data->name, "email" => $data->email]);
}
})
// Example success response, will be automatically serialized to JSON
return ["status" => "success"];
}
This loops over the collection element of your JSON input, which should be a list of models. Then it should obviously do validation and possibly other stuff. The whole loop is wrapped into a DB::transaction(), which will rollback everything if an exception occurs inside of it.
I have just started experimenting with backbone.js and have hit a roadblock while designing my very first front-end module with it. Here's what I have:
I followed the basic todos tutorial and created a commenting system upon my custom REST API. All good there.
I have a vote up or vote down button for each comment, just like SO. I want to call the REST API which is at
POST /api/comments/vote/:id
A parameter "direction" (up or down) is also passed.
In my comments view I have a function that calls another function it the comments model like this:
vote_up: function() {
this.model.voteUp();
return false;
}
the function in model:
voteUp: function() {
var up_votes = this.get('up_votes') - 0;
up_votes++;
this.save({up_votes: up_votes});
}
I am pretty sure I don't need this.save here which actually calls the comments API and the sends all the parameters as POST.
I am not sure how to do this in a backbonejs-way (I am looking for something standard).
You should do a manual $.ajax call to /api/comments/vote/:id and tell it if you have an upvote or downvote. Then /api/comments/vote/:id would send an atomic increment or decrement into the database, read out the new value, and send that back. Then the success handler for your $.ajax can do a set to update the client-side value.
Your voteUp implementation is subject to some obvious timing problems: what happens if you have 6 upvotes in your JavaScript, then 11 other people upvote, and your voteUp tries to tell the server that a new upvote has occurred so there should be 7 upvotes? You shouldn't try to maintain counters like this, not even in server-side code.
I guess I'm trying to say that there isn't really a Backboney way to do this, there is a correct way and all sorts of incorrect ways. Do it the correct way and declare, by fiat, that that is the Backboney way.
I think the standard way would be to take advantage of Backbone's urlRoot property.
var Vote = Backbone.Model.extend({urlRoot : '/api/comments/vote'});
// ....in comments view:
vote_up: function() {
// send HTTP Post '/api/comments/vote/' -- server will create ID
new Vote({direction: 1}).save()
return false;
}
If you need the Id right away, or to otherwise react to the successful insert, you can pass a success callback to save.
See: http://backbonejs.org/#Model-save
I'm posting a specific value through a POST form to my CakePHP app. Within the AppController I handle this specific POST data and afterwards I unset $this->request->data, e.g.
if(!empty($this->request->data['Keyboard'])) {
$this->Session->write('Keyboard.active', $this->request->data['Keyboard']['active']);
unset($this->request->data);
}
Afterwards I want ALL request data to be unset (hence the $this->request->data). Within my child controllers I call parent::beforefilter(); as the first line of code in its respective beforeFilter function, making sure the beforeFilter of my appController is initiated. However, in my childController the following line of code will still evaluate to true:
if($this-request->is('post'))
How do I 'unset' the is('post') call? I've also tried $this->request->is('post') = false in the if-statement above, with no success however.
The "problem" with $this->request->is($anything) (in your case at least) is that it is just a function to compare variables, not a setter.
Here's the API code in case you want to check it out.
So, basically, this code compares the variable env('REQUEST_METHOD') to POST (that's in the detectors part of the code).
There's no way a $this->request->is('post') = false is going to work. Something like
$_ENV["REQUEST_METHOD"] = null;
//or
unset($_ENV["REQUEST_METHOD"]);
after you unset the request data might work, but I don't guarantee anything because I haven't tried it.
I feel this approach is very dirty, I don't like to mess with those variables at all if there's no function already available for it, but I expect you to have considered all possible choices to avoid messing with the request already and see this as the only solution. Otherwise you should post another question addressing this subject and see if better approaches come up.
Hopes it helps in your case.
I have a project I'm developing which includes articles that can be commented on (comments stored in separate table of course). I want to perform pre logic on a field from each comment, wherever they are loaded through-out the app. The data logic I want to performed is from a custom written component.
The logical place to me that this could be achieved globally is from the comment model, but I could be wrong.
I'm not even 100% if I can use a component from a model, but I've been trying to do this logic using the afterFind() call-back function:
function afterFind($results) {
foreach ($results as $key => $val) {
if (isset($val['Comment']['created'])) {
$results[$key]['Comment']['created'] = $this->Dateconvert->howLongAgo($val['Comment']['created']);;
}
}
return $results;
}
I have tried echoing from inside this function and it doesn't actually seem to be getting called but searching hasn't revealed any functions that do, but I believe afterFind() is best to illustrate what I'm trying to achieve.
So I am looking for a solution where I can performed the post-load logic on articles comments, whether they are being loaded from other controllers with associations to comments or in the comments controller. Basically a global one hit solution :D
cakephp indicates that components are for controllers and behaviours for models and helpers for view...
knowing that first, you may also know that you can use any part of it wherever you want because cake still php, though is not recomended... if is a library of functions you may want to put it inside the libs folders and access it from there.
how, easy use App::import('component', 'nameComponent'); component can be lib, controller, etc..
Having said that, afterFind is a good place to do after load things, remember that this function is call ONLY when a find is used, if you use, any other like query or save or update it won't be called.
hope this helps you :)
I have a small calendar widget-type thing on many pages throughout my site. The goal is for it to retrieve events from X category that fall between Y and Z dates.
My assumption (I'm new to CakePHP) was that I should create a component, and have it do the query. Something like this:
class CalendarComponent extends Object {
var $uses = array('Event');
function getEvents($category = null, $date = null, $limit = null) {
$events = $this->Event->find('list', //conditions to get correct events
return $events;
}
}
BUT, according to this CakeBook page:
To access/use a model in a component
is not generally recommended
So - where would I store this logic / model call if not in a component? I've admittedly not used a component yet (or not created one anyway) due to lack of really understanding how I should use them - any snippet of advice on this is also VERY welcome.
Great question in my opinion and I imagine one that comes up quite often. I was actually dealing with a similar problem where I wanted some site-wide data gathering or functionality shoved into a component.
The first thing to keep in mind:
The book is a guideline.
These 'rules' aren't rules. If there's a good reason for breaking the rule and you understand why the rule is being broken then break the damn thing! Cake itself breaks this rule quite often.
Core components that require/use models:
Acl
Auth
Sessions (fairly positive you can save session data to a model)
So, clearly there are use cases where you need to use a model inside a component. How do you do it though?
Well, there's a couple different ways. What I wound up going with? Something like this:
<?php
ModelLoadingComponent extends Object {
public function startup($controller) {
$controller->loadModel('Model');
$this->Model = $controller->Model;
}
}
?>
That's it! Your component is now setup to use $this->Model...just like you would in a controller.
Edit:
Sorry, to clarify: no, you don't have to setup a new component to load models. I was showing an example for how you could load a component in any model. The startup function I used is a component-specific callback, there's a whole slew of them http://book.cakephp.org/view/998/MVC-Class-Access-Within-Components. These callback methods make components a lot easier to work with. I highly recommend looking at this part of the components tutorial if nothing else.
If you were working inside an AppController object you could call $this->loadModel() but we aren't working an AppController! We're working with a component, really an Object. There is no Object::loadModel() so we have to come up with a different way to get that model. This is where $controller comes into play in our startup callback.
When the startup method is invoked by Cake it will pass the $controller object it's working with on this dispatch as the first parameter. With this we're able to access controller methods...like loadModel().
Why do we do it this way?
Well, we could use ClassRegistry::init('Model') in each of our component methods that need to use the model. If you have 10 methods in your component and only 1 of them uses the model this might work. However, what if you have 10 methods in your component and all 10 of them use the model? Well, you'd be calling ClassRegistry::init('Model') 10 times! That's a lot of overhead when what you really want is just 1 model object. With this method the component is creating one model object. The one we create in startup.
I hope this clarifies your questions and provides some insight into why I use this method for models in components.
Edit: Added a code clarification after I did some experimenting.
I think writing a component is overkill in this case and it would be cleaner to put the getEvents method into the Event model.