So i have a lot of different model types. Comments, posts, reviews, etc. And I want to join them into one integrated feed. Is there a CakePHP style of merging all this data for display, ordered by timestamp?
There are a lot of messy ways to do this but I wonder if there is some standard way which I am missing. Thanks!
Since the items are from different tables, it's difficult to retrieve them sorted together from the database in any case. Depending on how well organized your data is though, something as non-messy as this should do:
$posts = $this->Post->find(...);
$reviews = $this->Review->find(...);
$comments = $this->Comment->find(...);
$feed = array_merge($posts, $reviews, $comments);
usort($feed, function ($a, $b) {
$a = current($a);
$b = current($b);
return $strtotime($a['created']) - strtotime($b['created']);
});
philosophical? lol
No, I don't think there is one. Although you could write an afterSave() in app_model. Check for the data you're looking for, and if found, put it in Cache. It will probably be messy, but at least it's in one place, and doesn't affect the performance much.
There's definitely no way to merge the data automatically, but instead of firing separate queries for each relationship, you can grab it all at once using CakePHP's Containable behavior.
Related
I'm using eloquent relationships.
When I call $carcollection = $owner->cars()->get(); I have a collection to work with. So let's say that I have, for this particular owner, retrieved three cars. The collection is a collection of three arrays. Each array describes the car.
This is all working fine.
Now I want to add more attributes to the array, without breaking the collection. The additional attributes will come from a different source, in fact another model (e.g. servicehistory)
Either I retrieve the other model and then try merge() them, or I try manipulate the arrays within the collection without breaking the collection.
All this activity is taking place in my controller.
Is one way better than another, or is there a totally different approach I could use.... perhaps this logic belongs in the model themselves? Looking for some pointers :).
Just to be specific, if you do $owner->cars()->get(); you have a collection of Car Models, not array.
That have been said, you can totally load another relation on you Car model, using
$carcollection = $owner->cars()->with('servicehistory')->get();
$carcollection->first()->servicehistory;
You can try to use the transform method of the collection.
$cars = $owner->cars()->get();
$allServiceHistory = $this->getAllService();
$cars->transform(function($car) use($allServiceHistory) {
// you can do whatever you want here
$car->someAttribute = $allServiceHistory->find(...):
// or
$car->otherAttribute = ServiceHistoryModel::whereCarId($car->getKey())->get();
});
And this way, the $cars collection will be mutated to whatever you want.
Of course, it would be wiser to lazy load the data instead of falling into an n+1 queries situation.
I am learning Ruby, reading few books, tutorials, foruns and so one... so, I am brand new to this.
I am trying to develop a stock system so I can learn doing.
My questions are the following:
I created the following to store transactions: (just few parts of the code)
transactions.push type: "BUY", date: Date.strptime(date.to_s, '%d/%m/%Y'), quantity: quantity, price: price.to_money(:BRL), fees: fees.to_money(:BRL)
And one colleague here suggested to create a Transaction class to store this.
So, for the next storage information that I had, I did:
#dividends_from_stock << DividendsFromStock.new(row["Approved"], row["Value"], row["Type"], row["Last Day With"], row["Payment Day"])
Now, FIRST question: which way is better? Hash in Array or Object in Array? And why?
This #dividends_from_stock is returned by the method 'dividends'.
I want to find all the dividends that were paid above a specific date:
puts ciel3.dividends.find_all {|dividend| Date.parse(dividend.last_day_with) > Date.parse('12/05/2014')}
I get the following:
#<DividendsFromStock:0x2785e60>
#<DividendsFromStock:0x2785410>
#<DividendsFromStock:0x2784a68>
#<DividendsFromStock:0x27840c0>
#<DividendsFromStock:0x1ec91f8>
#<DividendsFromStock:0x2797ce0>
#<DividendsFromStock:0x2797338>
#<DividendsFromStock:0x2796990>
Ok with this I am able to spot (I think) all the objects that has date higher than the 12/05/2014. But (SECOND question) how can I get the information regarding the 'value' (or other information) stored inside the objects?
Generally it is always better to define classes. Classes have names. They will help you understand what is going on when your program gets big. You can always see the class of each variable like this: var.class. If you use hashes everywhere, you will be confused because these calls will always return Hash. But if you define classes for things, you will see your class names.
Define methods in your classes that return the information you need. If you define a method called to_s, Ruby will call it behind the scenes on the object when you print it or use it in an interpolation (puts "Some #{var} here").
You probably want a first-class model of some kind to represent the concept of a trade/transaction and a list of transactions that serves as a ledger.
I'd advise steering closer to a database for this instead of manipulating toy objects in memory. Sequel can be a pretty simple ORM if used minimally, but ActiveRecord is often a lot more beginner friendly and has fewer sharp edges.
Using naked hashes or arrays is good for prototyping and seeing if something works in principle. Beyond that it's important to give things proper classes so you can relate them properly and start to refine how these things fit together.
I'd even start with TransactionHistory being a class derived from Array where you get all that functionality for free, then can go and add on custom things as necessary.
For example, you have a pretty gnarly interface to DividendsFromStock which could be cleaned up by having that format of row be accepted to the initialize function as-is.
Don't forget to write a to_s or inspect method for any custom classes you want to be able to print or have a look at. These are usually super simple to write and come in very handy when debugging.
thank you!
I will answer my question, based on the information provided by tadman and Ilya Vassilevsky (and also B. Seven).
1- It is better to create a class, and the objects. It will help me organize my code, and debug. Localize who is who and doing what. Also seems better to use with DB.
2- I am a little bit shamed with my question after figure out the solution. It is far simpler than I was thinking. Just needed two steps:
willpay = ciel3.dividends.find_all {|dividend| Date.parse(dividend.last_day_with) > Date.parse('10/09/2015')}
willpay.each do |dividend|
puts "#{ciel3.code} has approved #{dividend.type} on #{dividend.approved} and will pay by #{dividend.payment_day} the value of #{dividend.value.format} per share, for those that had the asset on #{dividend.last_day_with}"
puts
end
I have some troubles with memcache and GAE DB operations.
if i update memcache rigth after DB operations, x.put(), for example, my memcache function often return old value. If i use sleep(), cache more often correct, but this is not right, in my opinion
sleep(0.2)
data = Picture.all().order('-created').fetch(300)
memcache.set('pictures_all', data)
What i need to do, to get correct memcache?
ANSWER:
Need to use parent with query, all Picture entities must have same parent, then you get strong consistant results
data = Picture.all().order('-created').ancestor(main_key()).fetch(300)
memcache.set('pictures_all', data)
If you have the data, just update one entry in the memcache, no need to retrieve all from memcache. Something like
data.put()
memcache.set(key, data)
You're on the right track that the problem is with eventual consistency.
Using STRONG_CONSISTENCY does solve the problem, but it'll give you scalability problems down the road - ones that will be difficult to resolve.
The solution for this is, annoyingly, more complex than it should be. I'm also not sure whether there's really a bulletproof solution given the eventual consistency behavior.
pseudocode should look something like this:
all_pictures = memcache.get('pictures_all')
if not all_pictures:
all_pictures = convert_to_list(Picture.all().order('-created').fetch(300))
if not newdata in all_pictures:
add_to_list_in_proper_order(all_pictures, newdata)
memcache.set('pictures_all', all_pictures)
config = db.create_config(deadline=10, read_policy=db.STRONG_CONSISTENCY)
data = Picture.all().order('-created').fetch(300, config=config)
memcache.set('pictures_all', data)
I guess, this is solution.
EDIT: No, this is dont work
Great.
I had the same problem and the solution was exactly what asker gave: the use of ancestors
To read:
data = Picture.all().order('-created').ancestor(main_key()).fetch(300)
To save:
pic = Picture(parent=main_key(), ...)
pic.put()
I am trying to get rid of the redundant model names in the results array returned by the find method in CakePHP. As it is now, if I were to do something like $results = $this->Model->find('all'), I would have to access a result field by $results[Model][fieldName] instead of $results[fieldName].
I understand that having the model name in the array has benefits but I am trying to build an api so I need to json encode the array. With the model name included I get something hideous like:
[{"Model":{"field":"blah","field":"blah"}},{"Model":{"field":"blah","field":"blah"}}]
I want something more elegant like:
[{"field":"blah","field":"blah"},{"field":"blah","field":"blah"}]
Any ideas?
In your controller, instead of serializing the results of the find, serialise a level deeper.
Assuming CakePHP 2:
$things = $this->Thing->find('all');
$things = Set::extract('/Thing/.', $things);
Now your results should be free of the extra level in your JSON.
The alternative, lengthy way of doing it is to for loop over the results:
foreach ($things as $k => &$v) {
$v = $v['Thing']
}
After that, your $things will have removed the extra level of keys.
For later versions of Cake, use $things = Set::extract($things, '{n}.Thing');
I need to use Inflector::slug() over all results fetched from my database, which are, of course, retrieved in an array. Is it possible somehow, or I'll need to loop each result and slugify it?
Thanks!
PHP's array_map() function might do what you need (although it assumes a simple indexed array).
array_map( 'Inflector::slug', $your_result )
If you're looking at something more complex, CakePHP's Set utility class may be helpful in a multi-step implementation.
I haven't tried this in a CakePHP context (i.e. mapping through a CakePHP class method), but I can't think of any reason it wouldn't work off the top of my head. Maybe it'll at least get you started.
Depending on the array you can use array_walk or array_walk_recursive.
Something like this should work.
This is for 5.3+;
array_walk_recursive($posts, function(&$value) {
$value = Inflector::slug($value);
});
If you wanted to limit it to a certain field you could also do something like this:
array_walk_recursive($posts, function(&$value, $key) {
if ($key == 'title') {
$value = Inflector::slug($value);
}
});
I haven't used Cake in a while but like Rob Wilkerson said, you might find that the Set class could make lighter work of this.