Posted by oldmoe Monday, March 10, 2008 3/10/2008 12:22:00 PM
Objective what? Objective-C is a language that sits on top of
SmallTalk. Actually I shrug to the idea of writing code that looks like SmallTalk with C++ like constructs. Sounds like sweet and sour Chinese food
to me (it reminds me of the ugly "new" operator that is off place in Javascript). But Apple has done a great job with Cocoa (the MacOS X interface toolkit) and CocoaTouch (the one designed for touch interfaces, iPhone, iPodTouch and soon iTablet). The API is very elegant and clean (I still yearn for the BeOS API though, will always do).
OK, what does this have to do with Ruby? Well, a very interesting project popped up in the Ruby core list recently. Apparently, Apple is integrating the whole of Ruby1.9 into the Objective-C runtime and it is calling the package MacRuby. Ruby code will have access to all Cocoa interfaces and vice-versa. This project is an open source one but is being spearheaded by Apple. All those actively contributing right now are Apple engineers. They are trying to expand the interfaces to their APIs and thus cater for more developers.
Will we ever see an iPhone shipping with MacRuby? Will we be able to write Cocoa Touch interfaces in Ruby? Imagine that, I will no longer be ashamed that I don't know shoes!
Posted by oldmoe Friday, February 29, 2008 2/29/2008 06:27:00 PM
Labels: generator expressions , generators , javascript
One of those new features (an exciting one) is the introduction of generators. In layman's terms generators are: pause and resume for your methods. How is that? A generator is simply a normal method, but one that has the ability to yield back control to the caller while maintaining its state for future runs. This is not a very accurate description as it will not yield back to the method caller but actually to those who call next() on it. Confused already? let's use an example to make things clear.
//fibnacci example, stolen right from the mozilla docs
function fib() {
var i = 0, j = 1;
while (true) {
yield i;
var t = i;
i = j;
j += t;
}
}
var g = fib();
for (var i = 0; i < 10; i++) {
document.write(g.next() + " ");
}
which results in:
1 1 2 3 5 8 13 21 34 55
Before you get lost in the above code, here is a quick description of what happens:
- Javascript knows the above fib function is a generator (becuase it encloses the keyword yield)
- When you call a generator function, any parameters you send in the call are bound
- Rather than executing the method body it returns a generator iterator, one which you can call some of the iterator methods on (like next, send and close)
- The loop outside the function is run and g.next() gets called
- Whenever g.next() is called the fib function body gets executed, until it reaches the yield keword, at this point it returns control back to the caller of next() while its state remains intact.
- The result of the expression following the yield is returned to the caller of next (this is what is being generated by the generator)
- Subsequent calls to next() will cause the function to continue right after the yield keyword and yields control back again when it re-encounters it.
You can think of generators as a interruptable transformations. They are usually used to generate a transformation of some iteratable data while giving the callers control of when (or if) they are allowed to move forward with this generation.
Building on this, a new feature was introduced to make your life even easier. Generator expressions; Instead of having to write a generator functions it is possible to describe your transformation as a short hand in-place expression.
Consider the following generator function (also stolen from Mozilla but modified this time)
function square(obj) {
for each ( var i in obj )
yield i*i;
}
var someNumbers = {a:1,b:2,c:3,d:4,e:5,f:6};
var iterator = square(someNumbers);
try {
while (true) {
document.write(iterator.next() + " ");
}
} catch (error if error instanceof StopIteration) {
//we are done
}
this results in:
1 4 9 16 25 36This square function will iterate over the hash values (using the for each..in statement) and will generate the square of the current hash value and yield control back to the caller.
In this case the generator function is merely doing a very simple transformation. Thus we can easily replace it by a generator expression.
Like this example (for the third time, stolen and modified from mozilla.org):
var someNumbers = {a:1,b:2,c:3,d:4,e:5,f:6};
var iterator = (i * i for each (i in someNumbers));
try {
while (true) {
document.write(iterator.next() + " ");
}
} catch (error if error instanceof StopIteration) {
//we are done
}This line :
var iterator = (i * i for each (i in someNumbers));Is what we call generator expressions. This is exactly like the above generator function. It returns an iterator (the assignment) that when its next method is called it does a transformation (the expression i * i) in some sort of a loop (the for each..in statement) and returns control back to the caller after each iteration (implicitly yielding the expression result).
And there is more to generator expressions. They actually have a neat way of yielding only under some condition and the Javascript 1.8 developers (thanks Brendan et. al) came up with a cool Ruby like conditioning.
Say you only wanted to get the squares of the even numbers in the list, the above generator expression will be rewritten as:
var iterator =sweet!
(i * i for each (i in someNumbers) if (i%2==0));
Posted by oldmoe Sunday, February 17, 2008 2/17/2008 07:04:00 PM
Labels: AOP , javascript. Aspect oriented programming
Here's how to use them:
Dog.before('bark',function(){alert('going to bark')});
Dog.after('bark',function(){alert('done barking')});
User.prototype.before('login',function(){alert('logging in!')}The actual code written is very small (20 lines, less if you discount lines taken by braces)
Object.prototype.before = function(func, advice){
var oldfunc = this[func];
this[func] = function(){
advice.apply(this,arguments);
oldfunc.apply(this,arguments);
}
}
Object.prototype.around = function(func, advice){
var oldfunc = this[func];
this[func] = function(){
var myargs = [oldfunc];
for(var i=0; i < arguments.length;i++){
myargs.push(arguments[i])
}
advice.apply(this,myargs);
}
}
Object.prototype.after = function(func, advice){
var oldfunc = this[func];
this[func] = function(){
oldfunc.apply(this,arguments);
advice.apply(this,arguments);
}
}This way you can add any sorts of filters to your JavaScript methods. Enhancing on this is to be able to remove those filters once added and to add a filter to all functions of an object (recursively) at once.We also need some fail safety against users trying to advice non functions or even undefined properties.
Posted by oldmoe Saturday, February 09, 2008 2/09/2008 08:03:00 PM
You have your Java web app hot from the oven. Looking around you see this fat cat (we call it Tom, Tomcat) sitting around the corner. You hand it the app hoping that it will do a good job of serving it.
But how does Tomcat manage to serve pages from our Java web app? Simple, Tomcat listens on a certain port (default is 8080) and accepts requests, spawning a thread for each request to be handled when it reaches the maximum number of allowed threads (the maxthreadcount parameter and it defaults to 150) it queues the incoming requests until a thread is free so it can hand over a request to it. When threads get idle, Tomcat will start killing them till it reaches the max spare threads count (maxsparethreads, defaults to 75)
Sounds good. That means that a default Tomcat instance can handle up to 150 requests in parallel. Meaning it will spawn up to 150 threads. Which is a good thing. The more threads, the more parallel processing we can do.
WRONG! Because of limits imposed by your combination of hardware and software the above naive statements are not true. Manly due to:
Hardware: You can have only have n running threads, where n is the number of your cpu cores. Other threads are waiting until the scheduler preempts the current ones and permits them to run.
VM: JVM uses native threads (it used green threads in the past) which means that creating threads is not a very small process. In reality the cost associated with it is a bit high.
OS: Context switching is usually a heavy operation as well. When you have many thread more than your cores you will be dealing with much of those operations.
Here is a scenario: You use Tomcat with its default settings on a Quad core machine to serve your web applications. Your website is attacked and get sustained 150+ concurrent requests. Thus Tomcat spawns his max thread limit of threads (150) and attempts to serve all the coming requests.
Since you only have 4 cores. Only 4 threads can be active at a time, neglecting the Tomcat process and any other system processes then we have our 4 cores being fought for by 150 threads. Many threads will be waiting for I/O (hopefully hardware accelerated) most of the time. Thus a single core will be able to handle more than 1 thread depending on the speed of that core and the amount of time the threads are waiting.
I would say that a single core can cope with 5 to 10 threads (processing web requests) with negligible context switching penalty. Having more than that will result in too many context switches for threads congested on the core. With the default Tomcat settings, a cpu core will be handling 37 threads on average. This will lead to poor performance under heavy load and will slow down the application rather than help it run faster
So, what should we do with the maxthreads setting?
- Start from an informed position, knowing how much cores in your system you can just throw a suitable amount by following my (rather simplistic) approximation above of 5 to 10 (it is a guesstimate and it may turn out very bad for your specific case so don't say I didn't warn you) or ..
- Use a benchmarking tool, like Apache bench (from www.apache.org) and start testing your typical workload on your production machine with 1 thread per core setting. Record you requests/second and then redo the tests with more threads added. Stop adding threads when you can't get better performance. At this point, if you are not satisfied with your performance you can either:
- Get faster hardware
- Optimize your application and redo the benchmarking again
- Both of the above
Setting this for JavaScript and Image files causes your site to feel much faster on pages with many images and JavaScript files. But what do we do when any of those static resource change? For Images I usually change the filename with the new version and change the reference. For JavaScript files a common practice is to append some version information to the file name, usually a time stamp of the last modification date. This way when a file changes the reference to it changes as well and clients no longer use the old the cached resource and they will request the new one.
The simplistic time stamping approach works fine on a single server setup. When you add more servers you will find that you will need a more distributed safe way other than time stamping. One such way is to use your repository's revision file number. As long as you consistently deploy to all the machines you will have the same revision number on all the servers. In that case your files can look like this, application_235.js and common_42.js
Another issue arises with caching. If you are caching your entire responses (in memcached for example) and it references a Javascript file which happens to change its version then the response will keep asking for the older version rather than the new one. This can easily be solved by appending the application revision number to the cache key, i.e. "/users/1235/profile.html_1269". This way whenever the revision is upped your application will look for the latest ones in the caches and the older ones will auto expire (if you are using a cache store with auto expire capability like memcached)
Now, just relax and watch your web server serving static files blazingly fast while you are assured that everything is in sync.
Posted by oldmoe Friday, February 08, 2008 2/08/2008 02:19:00 AM
I was wondering where is the catch. Rereading the article I spotted it with little effort. Here's a ponder-this for those who read this blog (both of you), what did the guy do to screw up the performance figures that bad? If you get it, please add a comment with your answer.
Given his numbers, an appropriate Rails setup will make Rails suddenly faster (I don't mean any magic tricks, just fixing his fatal mistake). But the difference will not be that big anyway.
Update: seemingly no one discovered (or cared to discover?) the mistake, so here it goes. The lad used 10 Mongrels on a 1GB Ram machine that also ran mysql, OS X and whatever else he got running, he simply ran out of memory and started swapping. The numbers for the 10 Mongrel setup were including the disk swapping penalty. Couldn't he just listen to his drive or see a blinking led?
Posted by oldmoe Saturday, January 26, 2008 1/26/2008 11:43:00 PM
These are good news, though I will have to test if this will translate to noticeable performance increase in a full fledged Ruby on Rails application
I will redo my tests with this new setup and come back with more numbers
Stay tuned
I was using the nginx web server (authored by a Russian)
I tested against Mongrel application server (authored by an American) and Thin application server (authored by a Canadian)
I built the test application using the Rails application framework (authored by a Danish)
I was programming this app in the Ruby language (authored by a Japanese)
All work was done on Linux OS (originally authored by a Finnish)
I was doing all this work in Egypt for a service partner located in UAE
Not to mention the authors of the numerous tools I worked with in that particular day. I suddenly felt connected with all of them. I was thankful to live in a time were people from every where could contribute to a particular problem. It is amazing to see the culmination of efforts of many who are seemingly separated but in the end they seem all to be working in an unintended harmony. I felt that our little service is composed of bits and pieces from all over the globe. I only knitted them together with amazing results.
I need not mention that I didn't bother testing Apache as a proxy balancer instead of nginx, I am getting an earth shaking 7900 req/s for static requests using nginx on an application serving unit.
Our application serving units are xen virtual servers with a quad core processor each. It runs The nginx web server and the web application cluster (Mongrel currently, but may be Thin too). The cluster runs 10 Mongrels and 4 nginx workers. Any application can scale its front end by adding more of those application serving units.
While testing I was surprised that after a certain test Thin was giving me results that were slower than Mongrel. Repeating the tests or letting the system load cool down didn't help. What I found was that I hit the memory limit and the system started swapping. I shut down one Thin server and suddenly they started to outperform (albeit by a small margin) the Mongrel cluster again.
I tested against three of the very heavy pages. The results were the average of three runs for each page in the specified concurrency/connections pair
So here are the numbers (using Apache Bench):
(Concurrency/Connections)
100/1000 200/1000 200/10000
Thin 104.3req/s 115.7req/s 123.1req/s
Mongrel 100.6req/s 113.2req/s 121.6req/s
The figures speak for themselves; while Thin is constantly faster than Mongrel, the difference is negligible. I am assuming that this is due to the fact that most of the time is spent in processing the Rails stack and doing IO with memcached then sending the actual repsonse back. The raw differences between Thin and Mongrel are dwarfed by the time spent in Rails. You will see a good advantage for Thin when you are doing very small requests that do little processing and send small responses. While this is not the case for this particular test, it is typical in many Ajax intensive applications. And since this application is full of Ajax requests, I believe that we might opt for Thin at the end.
I have to say that I was very happy with the results so far. Mongrel and Thin are both robust and nginx is a true gem ;). The application is expected to generate lots of traffic and I am confident that scaling would only be a matter of adding more boxes. My next challenge is to get more performance out of those boxes. Which is a possibility since Evan is working on a much better memcached client for Ruby. Knowing that Evan is working on Mongrel too is reassuring me regarding its future.
Posted by oldmoe Monday, January 21, 2008 1/21/2008 12:23:00 PM
Thin is based on tried and true components (best in their class if you ask me). It's got its parser from Mongrel, IO management by Eventmachine and finally it connects to your favorite Ruby framework via Rack. It's amazing how one can achieve much just by blending the right components together.
For static page serving, I got a whopping ~2500 req/s on my 2GHZ Core 2 Duo machine. (vs ~900 req/s for the same machine running Mongrels). That's for 1000 concurrent users using Apache Bench
I also managed to achieve ~1200 req/s for a dynamic request in Rails that prints out 'Hello world!'. For the same 1000 concurrent users.
I will put it to real test in the coming days in more real world scenarios. I hope to be able to post the results here soon.
Posted by oldmoe Monday, January 07, 2008 1/07/2008 12:40:00 AM
Labels: pagination , rails , rest
One might come with a solution that overrides the cache key generation to incorporate the query string, which will work, but will result in very long and ugly hash keys.
Luckily there is a better approach, if you simply defined routs for pages (for the paginated resources) and name them page parameter with the same name you give it in the paginator then Rails will pick up the route when creating paginated links.
In your routes.rb
map.resources :users
map.paged_users '/users/pages/:page'
map.formatted_paged_users '/users/pages/:page.:format'
once the above routes are in place, all you need is to make sure your paginators are using 'page' as the page parameter name and you will see the pagination links created like this:
/users/pages/1
/users/pages/2
Don't forget the formatted route to support pagination with various formats so you can use routes like:
/users/pages/1.xml
These urls are very cache friendly and adhere to REST much more than the default parameters based ones.
Happy caching (with pagination)
Posted by oldmoe Thursday, September 27, 2007 9/27/2007 04:08:00 PM
When faced with a new web project these days you typically hear the clients listing AJAX as one of the must haves in their brand new web application. Pretty cool as you might be accustomed yourself to AJAX to the extent that you can hardly imagine returning back to the page reload per click days. But, if you are more sensible (or better yet, your clients are so) you would think twice before entirely abandoning the normal site browsing model for an AJAX based one.
Why? I hear you say. Many reasons, including the fact that we live in the early 21st century, where – get ready for this – not all Internet access devices are equipped with state of the art browsers that can consume your AJAX interfaces or whatever Javascript or CSS magic you throw at them. Many mobile phones (millions to say the least) can hardly parse plain old HTML, some can do CSS but not Javascript
Ok, you tell me. “I will have to do two versions, one that is full of AJAX effects and one old boring HTML only version.” STOP IT, I say, you can't be more wrong. Thank God there could be more elegant solutions to the problem than just writing another application around the same database. I present to you my humble take on the problem. Using the Ruby on Rails Framework (you can apply similar thoughts in other frameworks if you like, and many ideas can be copied easily as they only involve Javascript)
First off, the controllers. The controllers are responsible for receiving requests and sending responses. What we need to do is make them intelligent enough to understand different types of requests and respond accordingly. This is done using Rails magical method “respond_to”
class IssuesController < ApplicationController
def index
...
respond_to do |format|
format.html { # do something }
format.js { # do another thing }
format.json { # and another thing }
format.xml { # ok, enough }
end
end
...
end
In the above example we see that each format will have a different response. This is great for a start, that way we can implement slightly varying responses for the AJAX and the none AJAX calls. To make things easier on us we will implement a very simple case of AJAX. Each rhtml view is rendered in a DIV tag within an rhtml layout. In the none AJAX model, pages are rendered by rendering both the layout and the inner view. In the AJAX model, only the inner view is rendered and is sent back to the browser to replace whatever resides in the content DIV.
So, our controllers will work as follows:
class IssuesController < ApplicationController
def index
...
respond_to do |format|
format.html # will render index.rhtml
format.js { render :layout => false }
# the above line will render index.rhtml but without the layout
end
end
...
end
The above lines made our controller ready to respond to normal or AJAX requests (given that AJAX requests will have the .js format). In the former case it will return back the whole page but in the latter it will omit rendering the layout and only send the content.
Ok, but what we still need two views. I hear you, and fear not, you will have to change nothing. Actually it's only a trivia to adapt your views to this model. Let's see how this can be done.
...
<div id=”content”>
...
<ul>
<li><a href=”url1”>Link1</a></li>
<li><a href=”url2”>Link2</a></li>
<li><a href=”url3”>Link3</a></li>
</ul>
...
<form target=”url4”>
...
<input type=”submit”>
</form>
...
</div>
...
The above fragment shows a list of links and a form. All should behave in the normal way and reload the page when clicked. Now let's imagine that the user is using a Javascript capable browser. What effect could this coming fragment have on his experience?
<!-- Warning, this fragment requires prototype.js -->
<script>
function ajaxifyLinks(){
// check if there is AJAX support
if(!Ajax.getTransport())return false;
// loop on all links
$$('a').each(function(link){
// attach an event observer to each link's 'onclick' event
Event.observe(link, 'click', function(event){
// call the original url (with .js added) with AJAX
new Ajax.Updater('content',link.href+”.js”);
// stop the browser from following the link
return false;
});
});
// loop on all forms
$$('form').each(function(form){
// attach an event observer to each form's 'onsubmit' event
Event.observe(form, 'submit', function(event){
// send the form contents via AJAX
new Ajax.Updater('content',form.action+”.js”,
{params:Form.serialize(form),
method:'post'});
// stop the browser from submitting the form
return false;
});
});
}
</script>
The above code will transform EVERY link and form in the page to AJAX, that is, in case that the browser supports both Javascript and AJAX. Otherwise links and forms will remain untouched and they will behave as usual.
Of course this is a minimalistic example. We knowingly avoided touching on any special case but, in another installment of this article we will get more intimate with the subject and may be we can handle more aggressive ... techniques!
Posted by oldmoe Friday, May 25, 2007 5/25/2007 05:46:00 PM
If the above questions are all answered with a YES, then, my friend, you are desperately in need of caching. Caching will help you lessen the load on your servers by doing two main things:
- It eliminates lengthy trips to the (slow by nature) database to fetch the dynamic data
- It frees precious CPU cycles needed in processing this data and preparing it for presentation.
- It needs to be fast
- It needs to be shared across multiple servers
- Authentication is required for some actions
- Page presentation changes (slightly) based on logged in user
- Most pages are shared and only a few are private for each user
The cache store part is easy, memcached seems like the most sensible choice as it achieves points 1 & 2 and is orthogonal to the other 3 requirements. So it is memcached for now.
Now, which caching technique?. Rails has several caching methods, the most famous of those is Page, Action and Fragment Caching. Greg Pollack has a great writeup on these here and here. Model caching is also an option, but it can get a bit too complicated, so I'm leaving it out for now, it can be implemented later though (layering your caches is usually a good idea)
Page caching is the fastest, but we will use the ability to authenticate (unless we do so via HTTP authentication, which I would love to, but sadly is not the case). This leaves us with action and fragment caching. Since the page contains slightly different presentation based on the logged in user (like a hello message and may be a localized datetime string) fragment caching would sound to be the better choice, no? Well, I would love to be able to use action caching after all, this way I can server whole pages without invoking the renderer at all and really avoid doing lots of string processing by Ruby.
There is a solution, if you'd just wake up and smell the coffee, we are in Web 2.0 and we should think in Web 2.0 age solutions for Web 2.0 problems. What if add little JavaScript to the page that dynamically displays the desired content based on user role. And if the content is really little, why not store it in a session cookie? Max Dunn implements a similar solution for his wiki here and thus the page is served the same with dom manipulation kicking in to do the simple mods for this specific user. Rendering of those is done on the client so no load on the server, and since the mods are really small, the client is not hurt either, and it gets to get the page much faster, it's a win win situation. Life can't be better!
No, It can!. In a content driven website, many people check a hot topic frequently, and many reread the same data they read before. In those cases, the server is sending those a cached page yes, but it is resending the same bits which the browser has in it's cache. This is a waste of bandwidth, and your mongrel will be waiting for the page transfer to finish before it can consume another request.
A better solution is to utilize client caching. Tell the browser to use the version in its cache if it is not invalidated. Just send the new data in a cookie and and let the page dynamically modify itself to adapt to the logged in user. Relying on session cookies for dynamic parts will prevent the browser from displaying stale data between two different session. But the page itself will not be fetched over the wire more than once, even for different users on the same computer.
I am using the Action Cache Plugin by Tom Fakes to add client caching capabilities to my Action Caches. Basically things go in the following manner:
- A GET request is encountered and is intercepted
- Caching headers are checked, if none exists then proceed
else send (304 NOT MODIFIED) - Action Cache is checked if it is not there then proceed
else send the cached page (200 OK) - Action processed and page content is rendered
- Page added to cache, with last-modified header information
- Response sent back to browser (200 OK + all headers)
- We need to know the percentage of GET requests, which can be cached as opposed to POST, PUT and DELETE ones
- Of those GET requests, how many are repeated?
- Of those repeated GET requests, how many originate from the same client?
Happy caching
inversely, how many times you felt that joy when you made sure that this emerging technology you embraced or evangalized is actually gaining real momentum?
For me, my path in the technology sector has been a mix of both. Does that mean anything? does it say anything that every technology you adopt booms/busts?
In my silly 10 years of following the computer industry in general I had the following adoption failures:
- Cyrix, the little company that could! I was amazed by the capacity of their small team of engineers. But they just couldn't stand the tough fight. Bye Bye Cyrix.
- BeOS, a piece of engineering beauty. At least in the usability dept. I learnt my C++ by carefully studying the BeOS AP. I did almost all my low level coding attempts on BeOS. I even learnt bash on BeOS. I wasn't grasping why not every body on earth is using it! Silly people I thought (and still think :P). BeOS is no more, RIP BeOS (would be happy to see it ressurected one day though)
- Hibernate the ORM man, I knew it was a hit the day I saw their documentation, those guys new their stuff! I joined the ranks in the early version 2.0 days (Gavin's rewrite of the thing). You can still see me grin each time I see a developer using Hibernate at the place where I work.
- Javascript for semi-fat clients, it was the year 2000 and the use of Javascript for more than form validation was a taboo for many (browser compatability hell). Not for me, at the place where I work we fully embraced Javascript, and it (almost) never failed us!
- Ajax, we've already had such functionality, but since reading adaptive path's article, I really saw what I was missing by avoiding the XMLHTTPRequest object. In a week or so I had an ajaxified wroking protoytpe of our flagship application.
- Ruby on Rails, not really an early adopter (managed to use it for production in the pre 1.0 days). I still get this feeling of joy whenever I hear about another success (many of those these days). Rails has come out of age, that's for sure.
Now what about the products/technologies I'm looking at now?
- Ubuntu? Debian was already great. Ubuntu is the icing on the top of the cake. This one might boom.
- Offline web apps (sometimes connected apps, discussed here). These are just around the corner. If they manage to break the chasm before wireless technology covers the whole planet they will enjoy great success (for a while at least).
- The new wave of falling back to the forgotten REST API. I beleive we have a winner here. Specially when you see something like this coming out of it
- JSF, I believe one day people will realize that building interfaces is not like building a brick wall. That's the day JSF and the likes will be burnt for witchcraft!
- PHP, combine an ugly inconsistent sytanx with a terrible extension API and you've got yourself a PHP clone. Even though I managed to write decent apps in PHP but wouldn't like to live this experience again.
Every body will have his own pattern of failures/successes in following trends. Would be interesting to see what others think.
Posted by oldmoe Friday, July 07, 2006 7/07/2006 02:52:00 AM
- Our tools do that.!! The article was pointing to how Ruby follows the Unified Access Principle where you only have one interface to the class data members, whether it is a simple storage operation or a complex one. But, as Rob points out, having such support in the language is useless because current tools do that. I bet Bertrand Meyer wouldn't roll on his grave because Ruby is trying to offer UAP even when there are such tools around.
# initial class
class Plan
attr_accessor: owner
end
plan = Plan.new
plan.owner = rob
plan.owner # => rob
# we now need to upgrate to a full fledged setter
# rather than the one dynamically generated for us above
class Plan
attr_accessor: owner, assigned
def owner=(owner)
@owner = owner
@assigned = true
end
end
plan = Plan.new
plan.owner = rob
plan.owner # => rob
plan.assinged # => true - Long code is like short code is like medium code! Rob is picking at the author for mentioning that Ruby produces less lines of code. He's arguing that the tool is producing those verbose Java lines for us too!. As if we should accept garbage only and only if it is spit at us by our favourite tool! Why don't we all ditch the use of annotations for Hibernate mapping when our eclipse XML editor does autocompletion for the .hbm files? it is not about too many lines of code, it is about clutter and organization. I long for the day when i used VisualAge for Java, It was really anti clutter! (written in SmallTalk, no less!)
# neat example on short code
session.time_out = 48.hours.from_now - Rob rightfully accused the author to have poorly written the testing section and dynamism. The author was seemingly speaking about mock objects and how using them in a dynamically typed system is easier than a statically typed one. In a dynamic setting, identifying a mock object and using it is seamless. Also writing the mock object itself is seamless, no interface or contract of some sort, you only code the methods that you intend to handle and the others are handled by a common method. It's rather interesting to see that almost all the Java guys would praise AOP and tell stories about the wonders that they achieved using AOP (many are actually finding their way around the static nature of Java through AOP)
class Person
def can_run?
# some tedious operation
end
def can_jump?
# some other operation
end
end
# person mockup
# returns true whenever the method called has the character ?
class PersonMockup
def method_missing(method_id)
method_id.to_s["?"]
end
end
# or for some dynamic magic
class Roman
def roman_to_int(str)
# do conversion here
end
def method_missing(method_id)
roman_to_int(method_id.to_s)
end
end
roman = Roman.new
roman.V # => 5
roman.IV # => 4
roman.VII # => 7 - "ActiveRecord is simplistic". Here Rob doesn't justify why he thinks AR is rather simplistic? may be he is commenting on the simple example given by the article author? AR is rather a simple interface for a stunningly powerfull engine that provides you with a wide variety of DB constructs, the has_many and sisters, are actually defined in the AR module and when invoked they add methods and functionality to the invoking class at the class definition time. And due to Ruby's syntax flexibility they fit naturally in the class definition you don't even notice that they are function calls but rather some seamless annotation of some sort (which actually affects the class being declared and adds methods and attributes dynamically to it). Ruby provides you with the ability to add even more of those to AR. By excercising this feature you can build very complex relations between your domain models and still keep your code clean and clutter free.
class Person < ActiveRecord::Base
has_many :plans
has_many :tasks, :through => :plans
end
class Plan < ActiveRecord::Base
has_many :tasks
belongs_to :owner, :class => "Person"
end
class Task < ActiveRecord::Base
belongs_to :plan
acts_as_tree #defines parent/child relation ship among tasks
acts_as_taggable #for folksonomy aware objects (AR plugin)
end
#get all completed tasks for rob (involves one hit to the database)
completed_tasks = rob.tasks.select{|task| task.completed?}
#get all completed tasks that are parents for other tasks
completed_prent_tasks = completed_tasks.select{|taks|!task.children.empty?} - On DSLs. There are many ways one can solve a problem, but if you can shape your language around the domain you're attempting at that makes for much clearer code (wich is evident by looking at the above example) DSLs are abundant in Ruby code, mainly because the language has enough metaprogramming constructs and syntax flexibility that promotes such an approach to problems. Here are a few examples on how you can shape your code around your specific problem domain in a way that a domain expert will naturally understand the code.
# using a dsl suited for representing a workflow
workflow "default" do
step "scan"
step "ocr" do
when_error "manual"
end
step "cleanup"
end
workflow "manual" do
step "correction"
step "distribute"
end
#or a dsl for meal recipes
recipe "PBJ Sandwich"
ingredients "two slices of bread",
"one heaping tablespoon of peanut butter",
"one teaspoon of jam"
instructions "spread peanut butter...",
"spread jam...",
"place other slice..."
servings 1
prep_time "2 minutes"
#another dsl from rspec
target.should.equal 7
target.should.not.equal 5
target.should.be Fixnum
target.should.contain 'a'
target.should.be.empty
target.should_respond_to :quak #for the love of the duck!
#or from the poignant guide :D
class Dragon < Creature
life 1340 # tough scales
strength 451 # bristling veins
charisma 1020 # toothy smile
weapon 939 # fire breath
end - camelCaseVariablesLookPrettyNeatAndAreSoSweet , on the other hand underbar_variables_look_terribly_ugly_and_dull . That's what Rob thinks I suppose. I wont comment on Rob's taste, to each his own. I used to think like him, not any more.
testHasThreeClientsAndOneSupplierAndTwoStores() //pretty java
test_has_three_clients_and_one_supplier_and_two_stores #ugly ruby :)
To each his tools, to each his rules
oldmoe
"My opinion is right, though it could possibly be wrong. Your opinion is wrong, though it could possibly be right", Imam Shafey
read more | digg story
Currently the AATT is a real plugin that lives in /vendor/plugins in your rails app. The plugin enables you to do that to any of your models
class Person < ActiveRecord::Base
acts_as_taggable_tag
end
This simple invocation adds the following to your model class
#These methods are called to define the relations
has_many :tag_joins, :class_name => "Tagging", :as => :tagged_one
has_many :tagged_one_joins, :class_name => "Tagging", :as => :tag
#These instance methods are defined for your model
tags #returns a list of objects that tag yours
tagged_ones #returns a list of objects that are tagged by you
tag(tagged_one) #tag this object by yourself
remove_tag(tag) #remove this tag from you
clear_all_tags #delete the relations between you and your tags
clear_all_tagged_ones #delete the relations between you and objects tagged by you
If you look at the implementation of the above methods you'll notice how inefficient they are (a select call for each tag on a certain object for example). Performance is not my primary concern at this point in time, I am just trying to get the concept right.
A class is created for the dual polymorphic join model (name Tagging). Currently the name and the table mappings are not configurable (you have to use what I give you, period). The table structure is available in a migration format and can be invoked by:
rake import_aatt_schema
and it can be dropped from the database using:
rake drop_aatt_schema
I will be preparing a .zip file containing the plugin. To install it you only need to unzip it in the vendor/plugins directory. A great guide to using plugins can be found here
acts_as_taggable provides a very easy means for tagging various objects in your Rails application. By using this plugin you can now add a tag to every object and even look at those objects from the tag's point of view; thanks to :polymorphic => true.
I was entertaining the idea of using tagging in a system that I am intending to build. There would be a very generic framework that consists of a certain basic element that can be tagged by different types of tags. I then realized that this was actually the opposite of what acts_as_taggable does!. acts_as_taggable defines a single tag type that can be applied to any object. This lead me to thinking, why not join both ideas? And hence the acts_as_taggable_tag.
acts_as_taggable_tag is not yet a module, but I couldn't resist the name ;). What it does is that it simply enables any object to act as a tag for another object even if it was of the same class or even if it was tagging itself! Thus implementing dynamic many to many associations across all your persistent domain objects through tags
The caveat though is that has_many :through does not play nicely with polymorphic associations as explained here . I ended up using only the join table (taggings in my case) and adding methods for retrieving both the objects that act as tags for the current object and the objects that are tagged by the current object
This implementation is a bit lacking when it comes to performance. What would make it sweet though is to enable :polymorphic associations with a has_many :through, looks like the next thing to dig into :)
Now for the code:
Person Class
class Person < ActiveRecord::Base
has_many :tag_joins,
:class_name =>"Tagging",
:as => :tagged_one
has_many :tagged_one_joins,
:class_name => "Tagging",
:as => :tag
def tags
self.tag_joins.collect { |tj| tj.tag }
end
def tagged_ones
self.tagged_one_joins.collect { |tj| tj.tagged_one }
end
end
Message Class
class Message < ActiveRecord::Base
has_many :tag_joins,
:class_name =>"Tagging",
:as => :tagged_one
has_many :tagged_one_joins,
:class_name => "Tagging",
:as => :tag
def tags
self.tag_joins.collect { |tj| tj.tag }
end
def tagged_ones
self.tagged_one_joins.collect { |tj| tj.tagged_one }
end
end
Tagging Class
class Tagging < ActiveRecord::Base
belobgs_to :tag, :polymorphic => true
belongs_to :tagged_one, :polymorphic => true
end
The code in the Person and Message classes is identical, now each of them has a list of tags and a list of tagged_ones each containing whatever objects of whatever classes that happen to tag or be tagged by the current instance of Person or Message
The Tagging class represents the double polymorphic association between the tagging object and the tagged one regardless of the Class. This approach can be used to implement all sorts of hierarchies among your persistent objects through tagging. I am using it to build a multi process project management tool, which through tags can create different views of the same data like an Iteration/Story one for something like XPlanner or a TodoList one for the BaseCamp style.
Happy tagging :)
Posted by oldmoe Friday, April 14, 2006 4/14/2006 03:00:00 PM
For the past few months I was surrounded by more white collars than ever in my life. Mr. IT Manager this and Mr. IT Manager that, even Mr. Director of Technical and Non Techncial Economic Hyper Relations at PSGCD (their names happen to puzzle you somtimes, dont they?).
And guess what? meetings to these people are like water to a fish, they can hardly survive out of the meeting room. We need to change a label? let's throw an ultra high level management meeting for three companies and let everyone and his brother join in. Now after 3 hours of brain trashing everyone agrees that the label really needs changing, and a follow up meeting is set to decide on the actual label to be used (ofcourse that's an exaggeration so take it with a grain of salt,...or two!)
I just heard a comment saying the I should be doing more meetings and less programming! I beg your pardon, programming is MY WATER! I dont imagine myself laying back and moving things by pointing a stick at them. I'm not an ivory tower type of a manager either. And I dont like the constraints that conventional management and white collar cultures are putting around me.
The joke is, after all that, those white collars are COSTING US MONEY! you'd imagine that working with multinationals is like having a cash cow, rather it's like having a fake cow for display (and no milk at all). You only have to deal with complicated requirements (that dont even make sense sometimes), very eager expectations, very slow payment and very limited technical assistance (up to the level of requiring us to travel between cities to install files).
I bet you're asking now: "why are you putting up with all of this?", I just asked myself the same question, and hence that (seemingly whining!) post :)
I'm still pondering it all in my mind, where should we (or I?) be directed, how can we be happy? and I mean HAPPY!
I'd quote DHH here, "so be happy"
