julik live

Bad API design: a whirlwind tour of strong_parameters

Lately I have been doing time upgrading an application from Rails 3 to Rails 4. Obviously, one of the key parts of the operation in that case becomes a move from attribute protection (attr_accessible'/attr_protected') to strong parameters. Doing so, I have actually struggled a great deal. And after a number of hours spent on reflecting on the whole ordeal I came to the conclusion that it was not entirely my fault, and not even a problem with how the previous state of the application was when the migration had to be done.

The problem is strong_parameters itself. See, when you design an API, you usually assume a number of use cases, and then streamline the workflow for those specific use cases to the best of your ability. When situations arise where your API does not perform satisfactorily, you basically have two choices:

  • Press on and state that these situations are not covered by your API
  • Re-engineer the API to accomodate the new requirements

When designing strong_parameters, the Rails gang apparently went for the first approach. Except that since stating that "you are on your own" is not often encouraged as a message to developers-customers, it has been pretty much been swept under the rug. As a result, strong_parameters have been released (and codified as The solution to input validation) without (in my opinion) due thought process.

Since I was finally able to wrestle through it, behind all the frustrations I could actually see why strong_parameters did not work. It did not work because it is a badly designed API. And I would like to take some time to walk through it, in hopes that it can reveal what could have been done better, differently, or maybe even not at all.

So, let's run through it.

It is both a Builder and a Gatekeeper

By far the biggest issue with the API is this. It is both something we Rubyists tend to call a builder, and something we tend to call a gatekeeper - this is more of my personal moniker. Let's explain these two roles:

  • A Builder allows you to construct an arbitrarily-nested expression tree that is going to be used to perform some operation.
  • A Gatekeeper performs checks on values and creates early (and clear) failures when the input does not satisfy a condition.

For example, Arel is a good citizen of the Builders camp. A basic Arel operation is strictly separated into two phases - building the SQL expression out of chainable calls and converting the expression into raw SQL, or into a prepared query. Observe:

where(age: 19).where(gender: 'M').where(paying: true).to_sql

You can immediately see that the buildout part of the expression (the where() calls) and the execution (to_sql) are separated. The to_sql call is the last one in the chain, and we know that it won't get materialized before we have specified all the various conditions that the SQL statement has to contain. We can also let other methods collaborate on the creation of the SQL statement by chaining onto our last call and grabbing the return value of the chain.

XML Builder is another old friend from the Builders camp, probably the oldest of the pack. Here we can see the same pattern

b.payload do
  b.age 18
  b.name 'John Doe'
  b.bio 'Born and raised in Tennessee'
end
b._target # Returns the output

The obtaining of the result (the output of the Builder) is a definitely separate operation from the calls to the Builder proper. Even though calls to the Builder might have side effects when it outputs at-call-time, we know that it is not going to terminate early because the output object it uses is orthogonal to the Builder itself.

Strong parameters violate this convention brutally. The guides specify that you have to do this:

parameters.permit(:operation, :user => [:age, :name])

If you have strict strong parameters enabled - and this is the recommended approach since otherwise parameters you did not specify will simply get silently ditched - if you have even one single key besides :operation and :user at the top level, you will get an exception. If you supply a parameter that is not expected within :user - the same. The raise will occur at the very first call to permit or require. This means that the "Gatekeeper" function of strong_parameters is happening within the Builder function, and you do not really know where the Builder part will be aborted and the Gatekeeper part will take over.

Since we are so dead-set on validating the input outright, at the earliest possible opportunity, this mandates an API where you have to cram all of your permitted parameters into one method call. This produces monstrosities like thos:

params.require(:user).permit(
  :email,
  :password,
  :password_confirm,
  :full_name,
  :remember_me,
  :profile_image_setting,
  {
    paid_subscription_attributes: [
      {company_information_attributes: [:name, :street_and_number, :city, :zipcode, :country_code, :vat_number]},
      :terms_of_service,
      :coupon_code
    ]
  }
)

Those monstrosities are necessary because applications designed with use of nested attributes, and especially applications designed along the Rails Way of pre-rendered forms, will have complicated forms with nested values. And those forms are very hard to change, because they are usually surrounded by a whole mountain of hard-won CSS full of hacks, and have a very complex HTML structure to ensure they layout properly.

In practice, if we were to take the call above and "transform" it so that it becomes digestible, we would like to first specify all the various restrictions on the parameters, and then check for whether the input satisfies our constraint - divorce the "Builder" from the "Gatekeeper". For instance, like this:

user_params = params.require(:user)
user_params.permit(:email, :password, :password_confirm, :full_name, :remember_me, :profile_image_setting)

paid_subscription_params = user_params.within(:paid_subscription_attributes)
paid_subscription_params.permit(:terms_of_service, :coupon_code, :company_information_attributes)

company_params = paid_subscription_attributes.within(:company_information_attributes)
company_params.permit(:name, :street_and_number, :city, :zipcode, :country_code, :vat_number)

user_params.to_permitted_hash # The actual checks will run here

This way, if we do have a complex parameter structure, we can chain the calls to permit various attributes and do not have to cram al of them into one call.

More on this...

Suspects: Веб-стройка

On a small team, not being dicks sometimes trumps efficiency

There is an inherent difficulty to maintaining velocity. We always want things more streamlined, more efficient, more lean - sky is the limit, really, if the technical realm of a product is not micromanaged by the higher echelons of the company management, but is upgraded by grassroots effort. A good team of engineers worth their salt will, as wel know, improve and clean the product of must of technical debt - all a good manager has to do, really, is not to impede that process.

There is an inherent difficulty to maintaining velocity. We always want things more streamlined, more efficient, more lean - sky is the limit, really, if the technical realm of a product is not micromanaged by the higher echelons of the company management, but is upgraded by grassroots effort. A good team of engineers worth their salt will, as wel know, improve and clean the product of must of technical debt - all a good manager has to do, really, is not to impede that process.

There is an inherent concern however, which becomes especially important when the teams are small. Sometimes, velocity and efficiency need to be sacrificed a little if the team wants to preserve equilibrium in human relationships.

More on this...

Suspects: Веб-стройка

Why I am still using Jeweler

Rolling Ruby gems is an artistic affair. Fashions change. Sometime ago it was considered fine to edit your gemspec.rb by hand. Somewhat later a trend of using Hoe emerged. A little while later Bundler came into the picture, and it had a command as well to generate gems. Wind a few more years - and the fashion is now to roll with gem new.

The funny thing about it is that multiple people tend to jump on it and migrate their projects from one gem setup/maintenance system to another. It is just like with haute-couture or testing frameworks -- it is cool as long as you are using the latest one in fashion at the moment. Get distracted for just a couple of months - and you are no longer the fashion club, but a retrograde old bureaucrat stuck in his old ways of doing things.

However, not many people focus on what is way more important in the story of making Ruby gems and makine them actually shine - the stability. Let's go to an example. With my modest portfolio I clock over 20 gems in my decade of doing Ruby for fun and profit. Repeatability is way more important for me in this process than any fashions currently being flung around. In my view, for me as a maintainer of a whole salvo of gems, I need to have a few very simple conditions that are met by whatever tool I use for rolling gems:

  • There should be a command I can easily memorise to initialise a blank slate gem, and it should have the right setup in place.
  • All tests/specs for the gem should run with $bundle exec rake, no exceptions.
  • I should be able to do a release, including tagging and pushing it to the gem server, with $bundle exec rake release
  • I should not have to edit any files except of the changelog/version to roll such a release. Simple git history / gitignore is sufficient.
  • The gem set up with that tool of choice has to be runnable by Travis, and for most of my gems I still support Ruby 1.8.7

Having this process helps me by reducing the friction to release gems. When I want to release a library, absolutely the last thing I want to be worried about is how to streamline the workflow of doing those things. Simply because if I do, each new gem that I release or update is going to obtain a release process of it's own - it's just like building plugins for expensive post-production applications all over again. Every new version you roll, for any new version of it's dependencies, becomes a unique snowflake - one has a Gemfile, another one is managed by a manual gemspec.rb, yet another one assembles itself from git... and it goes on and on until you have to actually check what kind of release pipeline du jour has been in effect when you were last twiddling with a certain gem.

The longevity of many of my projects - tracksperanto is no exception with a running history of regular updates over it's 5 year existence - also owes to a stable release pipeline.

I've only went through the changes in the gem release pipeline twice. First switch was from manual gemspec.rb editing and a hodgepodge of Rake tasks to Hoe. The second was from Hoe to Jeweler, because I was unable to make Hoe function on Travis with the vast array of Ruby versions I wanted and because I got fed up with the manual MANIFEST file, where I always forgot to add a file or two.

So far Jeweler, with all of it's possible problems and limitations and an extra dependency, given me the most precious thing in the gem release/maintenance process - and that is that I don't have to think about that process. For any single gem that I maintain, I know that brake followed by brake release is going to do the update I am after - and I can concentrate on the code that my library is offering instead of the fashions of the build pipeline.

That is way more important, and way more precious to me than knowing that I am following the latest trend in a volatile universe of open-source. I am ready to pay the price of being called old-fashioned and having extra 10 lines in my Rakefile, along with a dependency practically none of my users will even download. By corollary, it means that your pull request to my projects proposing to remove Jeweler and go about some more bikeshedding is likely to be rejected. Not because I am a jerk, but because it supports a repeatable process I have developed muscle memory for, and changing that muscle memory is the last item on my priority list.

And I suggest you, dear reader, to do the same - pick a rubygem release/bootstrapping process that works for you, verify it, trust it and stick to it, instead of joining the bikeshedding fest - whatever that process might be. What your actual gem does is way more important than what you are using to roll it.

Suspects: Веб-стройка

Checking the real HTTP headers with curl

curl, the seminal swiss army knife of HTTP requests, is quite good at many things. Practically everybody knows that you can show headers using the -I flag:

$curl -I http://example.com/req

However, this is plain wrong in a subtle way. See, I sends a HEAD request, which is sometimes used to probe the server for the last modification date and such. However, most of the time you want to check a real GET as opposed to a HEAD. Also, not all web frameworks will automatically implement a HEAD responder for you in addition to a GET responder. It's also downright misleading because with quite a few proxies the headers you are going to be getting from the server will be different for a GET as opposed to a HEAD.

To perform a "real" GET, hit curl with a lowercase i as opposed to upprecase.

$curl -i http://logik-matchbook.org/shader/Colourmatrix.png

However, this will pollute your terminal with horrible binary-encoded strings (which is normal for a PNG after all)... There are ways to do a full GET and only show a header, the easiest being doing this:

$curl -s -D - http://logik-matchbook.org/shader/Colourmatrix.png -o /dev/null

Works a treat but is long and not memorizable. I put it in my .profile as headercheck:

alias headercheck='curl -s -D - $1 -o /dev/null'

So anytime you want to check headers with a real GET request, hit:

$headercheck http://url.that/you-want-to-check

and you are off to the races.

Suspects: Веб-стройка

For modern development Javascript indeed is a s̶h̶i̶t̶ dissapointing language

UPDATE: A much nicer JS bile from Armin Ronacher, where I subscribe to every word except for the Angular parts (since I skipped Angular entirely, because I am not smart enough to understand it).

I'm sorry, but the Crockford arguments do not cut it.

Javascript is so bad, on so many levels - it's not even funny. This is why I am so surprised everyone jumped on the node bandwagon with such excitement - yes, Node is faster than Ruby, but it's unfathomable to me that someone in his clear mind would want to rewrite his app in Node without being 100% focused on the evented model.

JS has inherent, deep issues. They are not solvable by making a new ECMA spec. They are not solvable by wrapping a better syntax around it like Coffeescript does. They are not solvable by standardizing on a require implementation or by introducing classes. There is an ECMA language with classes - it's called ActionScript and it's just as shitty as JS itself. These warts just are - and as long as the masses accept it as the status quo it's going to be exactly like the PHP framework landscape to this day: everyone and their mother will be spending man-years trying to create an infrastructure of tools around a shit language that will resist those efforts every single second.

Let me explain why I say that JS is awful. Of course, there are nice things in it - but the problem is that their utility is disputable. Prototypal inheritance, for example, is severly limited in utility - because all it offers you are function overrides. The "everything is a function" approach, while also gimmicky (look ma, I can also call this!), is also not particularly useful - because a function is not an object, not a datastructure that can carry data.

And then the real warts begin. Let's simply enumerate:

JS has callable attributes

This is a shitty design decision which most of the languages have made right at the start. In retrospect, it's difficult to blame the designers because they might have had performance issues - and, to boot, if you are not used to message-passing language the whole idea of "some attributes are callable and some are not" seems absolutely legal.

Hobjects are unusable for stable keys

The mixup between objects and hashes is also a very bad idea, because it defies the premise that objects can have metadata on them - which alllow you to establish a rudimentary type system or at least any kind of introspection.

Fobjects are unusable for type systems since an object does not carry any type information.

This one is a biggie. Even in the Ruby world, where everything is happily quacking like a duck, we often use the Object#class to get information about an object. A fairly standard procedure of styling an HTML element to a certain model object for example:

    <div class='<%= model.class %>' id='<%= [model.class, model.id].join %>' >…

is impossible in JS because the only types offered are 'Object', 'function' and primitives. It's awful in all the ways Java is awful, and then some.

Null everywhere

Trying to use a constant with a wrong name by mistake?

 MyApp.SYNC // should have been MyApp.SYNC_FETCH

Nothing will happen. Since ojects are hashes, and the language provides zero facilities for constants, our constant with the wrong key will be undefined, and will happily bleed into the callee. This makes stack traces huge.

Callback hell

JS lacks a decent facility for deferreds. It's created for evented execution, which is fundamentally not multithreaded. Your calls are interspersed with event callbacks - when your code is idling, callbacks are executed. However, JS lacks a simple facility for doing this:

 var res = await AjaxReq.fetch('/long-request')
 // because you are waiting for a result, here the runtime would
  // schedule event handling, DOM redraws and whatever else it can 
  // squeeze in while you await
 res.name // this will be only executed once res is available

Of course the JS community is doing exactly what the PHP community has been doing all along - they try to fix a bad language with even worser tooling. How? By using more callbacks and, on a good day, callback chains

when(<ERMAGHERD RIDICULOUSLY LONG CALLBACK>
 // 48 lines of code down
).then(<HOLYSHIT WHEN WILL THIS BE OVER>
// 23 lines down
).then(<GIVE ME SOME COFFEE ALREADY>)

In a normal situation this would have been fixed simply by adding a wait primitive to the language which would schedule the events when the result is still being fetched.

The proliferation of callbacks leads to the programming style where everything is async, but as a matter of fact 80 percent of the code you write has to be synchronuous. Simply because 80 percent of the programming is about doing one motherfucking thing with another motherfucking thing and you need both of them to complete the action.

Terrible exception handling

Exception handling in JS is terrible. It exists, but in a rudimentary form - you can see the call stack (that's going to consist of a dozen anonymous functions and one function that is named - that on a good day), and you can see an error message. Since I am not bashing on the DOM, I will only mention two most often encountered errors:

    undefined is not a function
    cannot call property 'xyz' of undefined

Both of these stem from the fact that fu(ck)bjects in JS have no defined methods - they only have properties. The JS runtime will never have a way to know whether your fubject is supposed to have a method that can be called, or a property of a certain name - it will just assume it being a missing hash key. It just doesn't know any better!

I remember people in the Ruby community complaining about Ruby's backtraces and error messages being not good enough - and Rubinius went to address this. You know where error messages are particularly fucked up? In fucking Javascript. Because the two absolutely basic, crucial exceptions that you want to get and see every single time - NameError (when you are adressing a class-ish or constant-ish something which is not defined) and 'NoMethodError' are simply impossible with the sloppy way the language is built.

And yes, functions are nice, and prototypes are nice and all that - but if you want to build a JS app having any kind of reasonable complexity, you will be bound to write code like this:

var cv = Marionette.CollectionView.extend({
  itemView: MyApp.Views.WidgetView;
});

What is the error that you will get if MyApp.Views.WidgetView is not defined yet? undefined is not a function of course! Where will you get it? When the CollectionView will try to instantiate your item view. Not when you define the variable cv, no no! It will explode later, and rest assured - you will be tearing your hair out for a couple of minutes until you see where the error came from.

And why? Simply because everything is a hash and the language is incapable of doing any kind of introspection.

It absolutely perplexes me that people who have used Ruby are moving to Node and calling it a good tool. Node might be great. The language running in it is shit though, and until this gets at least marginally better I will do without Node just fine, thank you.

I can understand that some people wanted to escape the MRI infrastructure by going Node, because - you know - learning Japanese is hard. If you don't speak Japanese, your chances of making a noticeable improvement in MRI approach zero - you will not be treated nicely.

JS is shit, and if we care at least a tiny bit we should do everything in our power to either sunset it, or to move it into the 'assembler for the web' realm where it would be a vehicle for decent languages that can actually get the job done without driving you up the wall. Being nice will not help it, and CoffeeScript is not radical enough. Support your local transpiler initiative today.

Update: nice to know that some people are considering alternatives

Suspects: Веб-стройка

Checklist for custom form controls in your wep app

Recently I had a privilege of reviewing a web app that is directly relevant to my work field. It is in fact an iteration on the system we are actively using at the company. After having a poke here and there, I was surprised to find that the custom controls epidemic was not over for some people.

All popup menus on the system (all select elements) were implemented as custom HTML controls with custom event handling. I hate this kind of thing not because it doesn't look like a native control - this is in fact secondary. Most of the apps I use daily (Flame, Smoke, Syntheyes, Nuke) do not use the native UI controls at all.

What I hate is a substantial reduction in useful behavior compared to a native control. There is a load of stuff humanity has put into menu implementations for the past 40 years. Every custom select implementation is bound to be reinventing the wheel, in a bad way.

Imagine you are all giggly and want to make a custom select element - a menu. Or your boss does not get UX and lives in the art-directorial LSD fuckfest of the late nineties, and absolutely requires a custom control. Ok then, roll up your sleeves.

More on this...

Suspects: Веб-стройка

Messages versus slots - two OOP paradigms

I've had this discussion with Oleg before, but this still keeps coming up again and again. See, alot of people are pissed at Ruby that you cannot do this

something = lambda do
  # execute things
end

something() # won't work

and also cannot do this

meth = some_object.method(:length)
meth(with_argument) # call the meth

However this comes from the fact that Ruby is a message-passing language as opposed to a slot language. What I tend to call a slot language is something that assumes objects are nothing more than glorified hash tables. The keys then become either instance variables or, if they store a function, "callables" (the way Python calls it).

So you might have an object Car that has:

 [ Car ] -> [weight, price, drive()]

all on the same level of the namespace. Languages that operate on the "slots" paradigm usually have the following properties:

  • It is very easy to rebind a method to another object - just copy the value of the slot
  • You can iterate over both ivars and methods in the same flow
  • Encapsulation is a decoration since everything in fact still is in the same table
  • You can call variables directly since local namespace is also a glorified hashmap with keys for variable names and concent that iscallable.

Due to various reasons I dislike this approach. First of all, I tend to look at objects as actors that receive messages. That is, the number of internal variables stored within an object should not be visible from the outside, at least not in a formal way. Imagine a way to figure out the length of a string in a slot language.

str.len # is it a magic variable?
str.len() # is it a method?
# or do we need a shitty workaround which wil call some kind of __length__?
len(str) # WTF does that do??

There is ambiguity whether the value is callable or not, and this ambiguity cannot be resolved automatically because this expression is not specific in a slot language:

# will it be a function ref?
# or will it be the length itself?
m = str.len

and therefore languages like Python will require explicit calling all the time. This might be entering parens, or doing

m.do_stuff.call()

Most OOP (or semi-OOP) systems known to us in the classic sence (even CLOS as far as I know) are slot systems - IO, Python, Javascript are all slot-based. This gives one substantial advantage: not having to specify that you want to move a block of code as a value explicitly. So you can do, for example in JS

 // boom! we transplanted a method
 someVar.smartMethod = another.otherMethod;

All of the rest are actually inelegant kludges. First of all, since a method can be used like a value for moving code from place to place, you always need explicit calling. Second, your slots in the object do not distinguish between values and methods so you have to additionally question every value in the slot table whether it is a method or a variable. Also, in slot languages you need to specify that an instance variable is private or not (since normally everything is in one big hashtable anyway).

On the other side we have message-passing languages, like Smalltalk and Ruby. In there anything you retrieve from an object passes through a method call whether you want it or not, because there are two namespaces - one for ivars and the other for methods.

You know that ivars from the outside of the objects are off-limits, and you know that everything exposed to the outside world iscallable, by definition. You also get the following benefits:

  • everything only ever goes through getters and setters, so you just don't have to think about them all the time
  • you tend to avoid using objects as glorified hashmaps
  • you get alot of smart syntax shortcuts
  • refactoring a property into a getter/setter pair is a non-issue to the consumer code

Message-passing languages also adhere to the UAP.

When implementing your next programming language, first make it a message-passing system, and if you need performance improvements make internal opaque shortcuts to bypass the getter/setter infrastructure when direct properties are accessed. You will spare alot of people alot of useless guessing and typing.

Suspects: Веб-стройка

Sequencing AJAX requests on "send-last basis"

So imagine you have this autocomplete thingo. Or a dynamic search field. Anything that updates something via AJAX when it's value changes.

Now, it's tempting to do this (I am speaking prototype.js here, jQuery would be approximately the same.

    function updateResults(field) {
        new Ajax.Request('/s', {
            method:'get',
            parameters: {"term": field.value},
            onSuccess: function(transport){
                var archiveList = transport.responseText.evalJSON();
                displayResults(archiveList); // This updated the DOM
            }
        });
    }

    new Form.Element.Observer(searchField, 0.3, function(form, value){
        updateResults(form);
    });

However, we got a problem here. Imagine that the user types something, and the observer fires when the field contains some. Now, logically some would find more entries than something and probably the search (the request) will take longer. So if you visualise the call timeline here's what you will see (pardon my ASCII):

            |                    |                           ^                    ^
            GET /s?term=so       |                           |             Update DOM for "so"
            |                GET /s?term=some           Update DOM for "some"     |
            |                    |===========================|                    |
            |                                                                     |
            |=====================================================================|

So the more specific request will complete faster and will update the DOM first. However, after some seconds when the previous request completes the previous request will overwrite the DOM again with less specific results.

What you need to do to prevent that is to discard the requests that have been sent before the last one. To do this, use a recorded sequence number for the request per window, and discard the results that come too late. Of course this will not stop the client from pounding on the server (ultimately you will need clever tricks like caching responses, increasing observe intervals and such) but it will at least ensure that the user is looking at the most specific result.

    function updateResults(field) {

        // First init our counter
        if (!window._reqSeq)  window._reqSeq = 0;

        // Increment request counter
        window._reqSeq +=1;

        // Store the sequence number of this request
        var thisRequestOrderNo = window._reqSeq;

        new Ajax.Request('/s', {
            method:'get',
            parameters: {"term": field.value},
            onCreate: showSpinner,
            onSuccess: function(transport){
                // ...and if this request is not the last one sent
                // discard it's payload.
                if(thisRequestOrderNo < window._reqSeq) {
                    console.debug("Request was stale, skipping");
                    return;
                }
                var archiveList = transport.responseText.evalJSON();
                updateDocument(archiveList);
            },
        });
    }

This works since now the callgraph will look like this:

            |                    |                           ^                    
            GET /s?term=so       |                           |             
            |                GET /s?term=some           Update DOM for "some"     
            |                    |=============(seq:3)=======|                    X
            |                                                         onSuccess returns early
            |==================================(seq:2)=============================|

Hat tip to Orbling for his SO answer on this.

Suspects: Веб-стройка

Migrating a typical roll-your-own comment system to Disqus

So, apparently disqus rocks. When I've applied it to this site I could reach a substantial reduction in code size, since most of the code (especially public-facing code) was dealing with

  • comment spam handling
  • banning of commenters
  • OpenID authentication
  • comment formatting

and so on. All of these are a burden and disqus does all of these better than I ever could. However, if you have a homebrew blog system (as I do in case of Inkpot) you will not have a simple plugin provided to flush all of the existing comments to Disqus. However, you can use this gist as a template - all it takes is a little Builder and some lap-dancing to output namespaces properly.

Run this to a file and you will get a Wordpress export that you can import into Disqus. If you store emails properly all of the comments will be also wired into their respective accounts there, automatically.

Suspects: Веб-стройка

Tracing a Ruby IO object for reads

So Tracksperanto has a progress bar. Actually both the commandline and the web version have one, which helps to improve the user experience a great deal (some files uploaded on the site were converting for more than a few minutes). However, we need to display this progress bar somehow and to do it we need to quantify the progress made.

For exporting this has been simple.

tracker_weight_in_percent = trackers.length / 50.0
trackers.each_with_index do | t, i |
   export_tracker(t)
   report_progress( (i + 1) * tracker_weight_in_percent )
end

However, for importing stuff the pattern is not so evident. Most parsers in Tracksperanto work like this:

  p = Parser.new
  trackers = p.parse(file_handle_or_io)

It's completely opaque for the caller how the Parser can quantify it's work and report on it. Parsers do send status messages that we can use for status line of the progress bar, but no percentages (they also mostly consider the IO unquantifiable and just read it until it eof?s. Also, there are many parsers and introducing quantification into each one of them would be kinda sucky.

So I looked for a different approach. And then an idea came to me: we are reading an IO (every parser does). Parsers mostly progress linearly, that is - they make trackers as they go along the IO (with a few exceptions), so the offset at which the IO is can be a good quantifier of where the import is. What if we feed the parsers an IO handle that can report on itself?

More on this...

Suspects: Веб-стройка

Architecture behind tracksperanto-web

So it's been quite a while since tracksperanto-web went production, with more than 300 files converted so far with great results. I hope some sleepless nights for matchmovers worldwide have already been preserved and nothing could be better.

I would like to bring a little breakdown on how Tracksperanto on the web is put together (interesting case for background processing). The problem with this kind of applications (converters to be specific) is that the operation to convert something might take quite some time. To mitigate that, I decided to write the web UI for Tracksperanto in such a way that it would not be blocking for processing, so extensive use of multitasking is made.

Basically, the workers on the web app are fork-and-forget. Very simple - the worker gets spun off directly from the main application, it writes it's status into Redis and at the end of the job to the database. Tracksperanto jobs use alot of processor time and alot of memory, and with Ruby nobody can guarantee that jobs won't leak memory. fork() is tops here since after the job has been completed the forked worker will just die off, releasing any memory that has been consumed in the process.

When processing is taking place, the worker process writes the status into redis which is perfect for this kind of a message bus application. Tracksperanto is designed so that every component can report it's own status and a simple progress bar can be constructed to display the current state. So basically we constantly (many times per second) write a status of the job into memcached - percent complete and the last status message. To let the user see how processing is going, I've made an action in Sinatra that quickly polls memcached for the status and returns it to the polling Javascript as a JSON hash.

This scheme has the following benefits:

  1. Status reporting does not load the database (not needed and the information is hardly crucial).
  2. Zero memory leakage
  3. No Ruby daemon processes
  4. Start/stop control is tied into the webserver.

Note: lots of stuff removed from this post since it's no longer relevant.

Suspects: Веб-стройка

_path routes considered harmful

There's been some talks lately on relative versus absolute routes and where they should be used.

Personally, I consider the point moot. If you have such a powerful tool at your disposal (Rails) there is one thing you certainly should do, once and for all - make all URLs both absolute and canonical. Here is the reasoning for that:

  1. Canonical URLs work in standalone documents (like PDF and downloaded pages). Anywhere you transition from displayable HTML to something else, you have to go canonical
  2. Atom and RSS feeds are busted when you use relative URLs. Granted, there is the xml:base but some feed readers just don't give a shit (making all your links dead). Nothing is more frustrating than having unclickable links. NetNewsWire, last time I checked, did not honor xml:base. This situation obliges you to rewrite all URLs that go out to feeds
  3. The same for sent email - there is no mechanism for specifying a URL base for hyperlinks in mail messages.

Also, if you promote web service consumption off your app (REST or otherwise) you oblige the consumer to do path-munging for every redirect, every cross-reference and every parse just to drill down your site structure (all these ugly url.path = something things come from that).

So if you ask me, I'd just hardwire *_path to raise or play through to *_url. These are processor cycles (and wire bytes) well spent, because they simplify the consumption of your website outside of anything except the browser, and free you from deciding every time which one you need. Additionally, modern times never cease to amaze us with regards to ways our content can bleed out of the browser.

At some point I had to write a plugin to enforce canoncal/absolute URLs in text segments, and for Inkpot (the engine running this site) I use canonical URLs for the absolute most of the links and cross-references. The exception is the stuff going through the Camping router, but my gut feeling tells me that the use of Camping will be abolished here anyway.

Just my 2 cents.

Suspects: Веб-стройка

Finally integrated

Thanks to the advice by Jeremy Lightsmith I've got CC.rb running on the very same machine I've checked it out to. Works fine with both git and subversion now. While this might not have been true in the past, git integration indeed works perfectly (it's polling instead of notifiers, though - which are in fact no more than pimped post-commit hooks and a maintenance chore).

Suspects: Случайное Веб-стройка

Just 7 years ago

A tad more than 7 years ago.

just-then.png

I'm glad we got out of this alive.

Suspects: Веб-стройка Mac

XMLRPC and Builder, a marriage made in heaven

This one is an example of why if you do XML you better do it right. I've written this blog with an aim for spotless MetaWeblog API support (I hate Web UI's and I own both MarsEdit and Ecto licenses). I also don't have an aversion to Dave Winer, and Atompub is not there yet IMO.

So I've implemented a small MetaWeblog responder based on Ruby's XML-RPC module. The module is somewhat antiquated and the docs are not always telling all that you might need, but there is a little problem which is actually very serious - it uses a bozo XML writer.

Recently I've posted an entry about caches. When I've posted it and retreived it via MarsEdit, everything went well. But then an update to MarsEdit came along, and lo and behold - the updated MarsEdit was not able to retreive the very entry the old one has made.

Error message

Hmm... let's see up close. I copied the XML response into a standalone XML document and ran it through xmllint.

invalid CDATA

Even more interesting. The character in question is ASCII 005, the ENQ character. Don't know how MacOS X managed to type it into the entry box in MarsEdit, but it certainly was there!

enq_char.png

This is an ASCII "control" non-printable character, and putting it into XML is anything but responsible. Even though it might be present in the blog entry itself, it should never bleed through into the XML representation for an RPC call! So I went out to investigate what writes XML for Ruby's XML RPC. I wish I didn't - to spare you a search, the file you need is create.rb.

Let's put it that way - the only sanitization done is replacing the obligatory entities. The rest.. well the rest is expected to somehow happen when you pass your results to XML RPC. The XML writer in Ruby's RPC works based on text concatenation.

More on this...

Suspects: Веб-стройка Юникод

A prophecy cast in stone

This was so fantastically spot-on.

Sure, they’re all curious and nice at first, but once they get their tentacles in, it’s nothing but “this isn’t good programming practice” and “that should be declaratively configured elsewhere.” You’ll see.

Suspects: Веб-стройка

Brutal fragment cache

Recently I've hit that painful mark, y'know... A page on a Rails site I've been developing crossed the dreaded "1 second" request time. This can't be happening, thought I - it's just a list of objects! One SQL request, bonafide, no associations fetched, all indexes are in place... and the most aggravating was - the SQL request itself was a measly 0,1 seconds long.

Something is wrong. After a short investigation the problem turned out to be of the following nature:

 <% @models_that_can_be_thumbnailed.each do | model | %>
    <%= render_a_beautiful_thumbnail_with_other_widgets_for(model) %>
 <% end %>

I've hit the "repeating render" (or "slow helpers") problem - one of the most stupid Rails aggravations in the performance department. See tha' slide about the app spending 30 percent of time in textilize? That's about us. The beautiful thumbnail render entails not just doing ERB though - it's doing captures, calculating a hashed URL via signed_params and doing a bunch of other stuff. Done 100 times in a row it was most enough to escalate a simple image listing into a performance disaster. So, caching to the rescue - and, more specifically, the Rails fragment cache. After all, I've taken care to render the thumbnails in most the same way almost everywhere (with the same recognizable widgets and sizes), but which ones - that varies per page, search result and so forth.

So, we read about the Rails caching. As it turns out, it recommends us to cache stuff by key, but... the key has to be a String! Or, more specifically, it can be a String or a hash of options (which most likely comes from url_for. So I'm encouraged to specify from which controller I am caching the fragment, from which action, and also it's probably expected that I will introduce some value that will help me identify what exactly I am caching. Like a model primary key value, for instance.

<% @models_that_can_be_thumbnailed.each do | model | %>
  <% cache :type => 'thumbnail', :model => model.class.to_s,
       :lotso_other_things_that_can_vary... do %>
    <%= render_a_beautiful_thumbnail_with_other_widgets_for(model) %>
  <% end %>
<% end %>

Meh. Such a hassle. Let's examine the problem here: I have an object (an Image model). I know that there were no changes to this Image as long as any of it's attribute values did not change. There are no associations to track on it (this can vary, but still). As long as the Image stays the same, the thumbnail will be the same as well - and the cache fragment too! Why should I bother to pass the key of the Image to the cache key method if I know that the state of all the fields in the model can tell me about the freshness of the cache? But bear with me here. Let's say we do this:

 @users_images = User.images

We know that a @users_images variable also holds a certain state - it's an Array with Image objects in it. If the images change, then @users_images will change too, right? It will just contain different objects.

So, let's see again - we are actually not limited to some arbitary string key for an object's cache key, we can use the object itself as state indicator. Tobi Lütke said that the most useful thing is to be specific on what you want to get from the cache. So a simple realisation dawned on me:

If we know what we want to cache and based on what the cache key can change, we can transform our objects themselves into cache keys.

More on this...

Suspects: Веб-стройка

Organized thread collisions

We don't like when things collide.

Absolutely no head on collisions

Photo by szlea

But what if we need them to? What if we want to ensure that should a collision between threads happen, things will not go out of hand?

This is remarkably easy to do. Let's pretend we want $somevar to be thread-safe:

require 'test/unit'

class Collision < Test::Unit::TestCase
  def test_that_fails
    a = Thread.new do
      5.times do | t |
        $somevar = true
        sleep(rand(10)/200.0)
        assert_equal true, $somevar,
          "Somevar should keep the state true"
      end
    end

    b = Thread.new do
      5.times do | t |
        $somevar = false
        sleep(rand(10)/200.0)
        assert_equal false, $somevar, 
          "Somevar should keep the state false"
      end
    end

    ensure
     a.join; b.join 
  end
end

Fail. Global variables are never thread-safe, neither are class variables and module attributes.

The .times call is somewhat cargo cult programming, because this fails for me on first iteration. Replace the $somevar with your getter and setter and you should see negative results immediately.

How does it work? Simple - we are spinning off two threads, they set the variable and wait in Kernel#sleep for the other thread to arrive and do it's own assignment. It ensures that between the variable assignment and the assertion there's just enough time for another thread to set the new value for the variable.

Suspects: Веб-стройка

SSL for noobs

Having spent a day investigating SSL I can finally document the findings in one place. This won't be of much use to crypto junkies but might come handy if you want to SSL-enable your little app somewhere on the network, or just protect your blog using SSL without paying for something and most importantly with clear realization of what you are actually doing.

This entry is quite lengthy. so make yourself some coffee and jump aboard.

More on this...

Suspects: Веб-стройка

Labeled

Amazingly enough, the iPhone Safari does not support changing checkboxes by tapping their labels.

Geometrically speaking, a checkbox with maximum magnification in iPhone is about 3-4 millimeters wide. They shoudda'given me a laser guidance system.

In the spirit of world peace and collaboration, the kludge:

   /* http://chriscassell.net/log/2004/12/19/add_label_click.html */
   function flipBox(e) {
     if (navigator.userAgent.indexOf("iPhone") > 0 && navigator.userAgent.indexOf("Safari") > 0)  {
       var cb = e.target.getElementsByTagName('input');
       if(cb.length == 0) return true;
       for(var i = 0; i < cb.length; i++) {
         if(cb[i].getAttribute("type") == "checkbox") {
           cb[i].checked = !cb[i].checked;
         }
       }
     } else {
       return true;
     }
   }

To maximize the excitement make the label 80% of the total page width, whereby it will become a huge stateful button (you can also style it as such).

Suspects: Веб-стройка

Tooled

Amazing but true - "Sita sings the blues", the winner of the feature prize at the Annecy Festival is a film made in Flash.

The more amazing fact is that the artist had one, and only one way to automatically export a rendered Flash animation into raster - embed it in Shockwave Director and then read the rasterized images per pixel. With all the oomph and aamph today it's still the only way to framelock Flash, iPhone or not.

Mad props to Nina Paley! A grand foei to Macrodobe for the workflow (and Flash frame precision in general), which is a FAIL.

Suspects: Веб-стройка Mac

Правильно вписанный мувик

Оч. прекрасно (as seen on airrebels)

movie_int.jpg

Suspects: Веб-стройка Дизайн

Дорогой lazyweb

Дорогой любимый lazyweb! Не знаешь ли ты, где мне найти собранный бинарник darcs для MacOS X, которому не пять лет (который не RC). Потому что я мало того что устал собирать GHC, GHCI, Perl, PerlGCI and their mother and granddauther - которые мне либо нафиг не нужны, либо есть в комплекте системы – так они еще и либо не собираются либо не устанавливаются.

Заранее большое спа.

UPD. Всем спасибо, война окончена. Выяснилось что можно скачать бинарник GHC и уже им собрать сам darcs. Собирался, гад такой, полчаса. Зато функционально и академично. Это кстати уже второй раз когда я крайне разочарован в MacPorts – в первый раз они намеренно исключили из комплекта Python 2.5 функцию md5. Это была очень тонкая шутка с их стороны.

Suspects: Веб-стройка

Дело Ленина живет, дубль два

Ура pythy – теперь и питонисты могут до достижения высокой нирваны транслитерировать и датировать свои веб-страницы, в том числе в Джанго.

Другое дело что любой порт RuTils, не напрягшийся на реализацию Typografica, я все-таки считаю идеологически неверным (неверным делу Кукуца, that is). Кстати, схему distance of time in words из pytils я может быть и портирую обратно – благо у всего есть тесты.

Сделайте кто-нибудь хорошую первоапрельскую шутку и портируйте RuTils обратно на PHP – будет весело, а? (даты можете оставить потому что PHPсты слава богу об этом подумали).

P.S. В других новостях - все обновившиеся на rutils 0.2.2 немедленно обновляются до 0.2.3, поскольку в связи с изменением в rubygems в релиз 0.2.2 не попали очень важные файлы. Простите за засаду.

Suspects: Веб-стройка

ISOбражая умных

Мало того что официальный список стран ISO сделан в кодировке Latin-1. Мало того что в нем еще Windows line breaks. Главное (и некорректируемое тривиально) – все страны набраны В ВЕРХНЕМ РЕГИСТРЕ.

Просто таки приглашение чтобы сделать красивое такое меню выбора стран автоматически.

Если международная организация еще и предлагает приобрести Microsoft® Access 2000 database products, говорить и вовсе не о чем. Куда катится мир, я вас спрашиваю?

Suspects: Случайное Веб-стройка Юникод

Scalable text area

Маленькое полезное - штука, которую я впервые увидел в TaskPro. Делает рядом с textarea две кнопки, которые позволяют делать ее больше или меньше. В качестве бонуса можете попробовать добавить динамический расчет размеров textarea в зависимости от уже вбитого в обьект количества символов.

Применяю во всех последних проектах/прожектах.

# Works just as the normal +text_area+ but also includes the buttons to extend or minimize the textarea to accomodate more text.
# Especially handy for long pieces of text
def scalable_text_area(*opts)
  unless opts.last.is_a?(Hash)
    opts[-1] = {} 
  end

  opts.last[:rows] = 10 unless opts.last[:rows];
  ident = "#{opts[0]}_#{opts[1]}"
  o = ''
  o << content_tag(:a, '[Smaller]', :href => '#', 
    :class => 'smallctr',
    :onclick => "if($('#{ident}').rows > 3 ) $('#{ident}').rows -= 2; return false;")

  o << content_tag(:a, '[Bigger]', :href => '#', 
    :class => 'smallctr',
    :onclick => "$('#{ident}').rows += 2; return false;")
  o << "<br />"
  # forward
  o + text_area(*opts)
end

class ActionView::Helpers::FormBuilder

  def scalable_text_area(method, options = {})
    @template.send(:scalable_text_area, @object_name, method, options.merge(:object => @object))
  end
end

Scalable text area

Suspects: Веб-стройка

Fuck you

Fuck, fuck, fuck, fuck, fuck you.

Какой вашу мать Flex? Какой такой Flex я вас спрашиваю?

Suspects: Веб-стройка

Back to the future

Презентация The Future Of Flash на домашнем G4 играет со скоростью 1 кадр в 20 секунд. Велкам, стало быть. Во фьючер-то. Машинка кстати умеет даже uncompressed video монтировать, ага.

P.S. Да, много-много-рентгенные лучи ненависти имплементорам FLV. И тот факт что Flash для линукса есть а QT нету - нифига никакой не excuse. Стандарты открытые.

Suspects: Веб-стройка

Феерично

Для справки - если Flash CS3 таки получил здоровую иньекцию прямых рук, то на Fireworks это не сказалось. Не дошел выпрямитель.

Рисуем прямоугольник. После шестнадцатого тыка мышкой Property Inspector таки @#$ показывает @#$ properties прямоугольника.

Это началось в Fireworks MX. Fireworks CS3 это спустя ТРИ версии после этого. В Fireworks 4 кстати все работало офигительно. Это было 7 лет назад.

Suspects: Веб-стройка Mac

ranges.rb

Навеяло постом маньяка...

Кстати по-моему очень хорошая идея - а в случае overflow сериализованного массива либо обнулять его (ибо информация не критичная) либо заменять на [1..последний_топик_в_форуме].

 module Enumerable
   def to_ranges
     compact.sort.uniq.inject([]) do | result, elem |
       result = [elem..elem] if result.length.zero?
       if [result[-1].end, result[-1].end.succ].include?(elem)
         result[-1] = result[-1].begin..elem
       else
         result.push elem..elem
       end
       result
     end
   end

   def member_includes?(item)
     find{|r| r.include?(item)} ? true : false
   end

   def implant(item)
    (map{|e| e.to_a }.flatten + Array(item)).to_ranges
   end

   def implant!(item)
     replace(implant(item))
   end
 end

implant конечно можно и поэкономнее сделать но было лень :-)

Suspects: Веб-стройка

Работа над ошибками

Решил попробовать cruisecontrol.rb, новый continuous integration tool от ThoughtWorks. С их тулингом у меня уже был не самый прекрасный опыт. Сравним:

Damage Control

  • пять зависимостей, среди них свои модули работы с SVN, CVS и прочими
  • чуть ли не поддержка электрифицированной теледилдоники в случае не сработавших билдов
  • тесты которые пытаются оную дилдонику завести и потому проваливаются
  • сетап из шести этапов
  • восемь страниц настроек на каждый проект
  • commit hooks

CruiseControl.rb

  • Сетап состоит из трех комманд - скачать дистрибутив, ./cruise add, ./cruise start
  • Ни одной зависимости
  • Не использует базу
  • Максимум правильных настроек по дефолту и веб-приложение из двух экранов
  • никаких commit hooks создавать не требуется
More on this...

Suspects: Веб-стройка

RuTils перезжает на subversion

Всем кто пользовался рутилями из CVS рекомендуется обновить линки, поскольку оный CVS скоро прекратит свое существование. Mash, это и тебя касается!

В числе прочего это значит что теперь можно использовать рутили как плагин в рельсах прямо в своем дереве контроля.

Suspects: Веб-стройка Юникод

Директория шаблонов в Edge Rails - спасаемся обещаниями

Недавно заботливые товарищи от Mephisto и Radiant закоммитили в рельсы смену template_root на view_paths, дабы можно было автоматом доливать пути к шаблонам в контроллеры.

Тем не менее они забыли о простом кейсе:

  1. У нас есть фирмы А, Б, В
  2. Все они пользуются одной админкой с похожим функционалом, но их сайты оформлены отдельно и у каждого сайта есть своя директория views
  3. При получении запроса мы вычисляем, на какой домен он пришел и используем темплейты фирм А, Б или В соответственно.

Дабы сэкономить читателям время, теперь это делается так:

More on this...

Suspects: Веб-стройка

Тезис

Markdown – TEX для творческой интеллигенции

Suspects: Веб-стройка

Как это можно не любить?

   <!--[if lt IE 5.5000]><style type="text/css">@import "/skins-1.5/monobook/IE50Fixes.css?59";</style><![endif]-->
   <!--[if IE 5.5000]><style type="text/css">@import "/skins-1.5/monobook/IE55Fixes.css?59";</style><![endif]-->
   <!--[if IE 6]><style type="text/css">@import "/skins-1.5/monobook/IE60Fixes.css?59";</style><![endif]-->
   <!--[if IE 7]><style type="text/css">@import "/skins-1.5/monobook/IE70Fixes.css?59";</style><![endif]-->
   <!--[if lt IE 7]><script type="text/javascript" src="/skins-1.5/common/IEFixes.js?59">

Suspects: Веб-стройка

А вот это и есть оно

Когда меня спрашивают, есть ли у Rails слабые места, я знаю что ответить но не знаю как. А именно:

http://theocacao.com/document.page/395

Главная информация внизу страницы. Вкратце - в Rails нету Object Context, а уж тем более нету Object Context который можно "подвесить" между загрузками страницы или несколькими запущенными приложениями.

Suspects: Веб-стройка

Flame user friendly web-GUI

Приятно, когда удобные контракты из чужого интерфейса можно безболезненно перетащить на веб.

 var oldConfirm = confirm;
 window.confirm = function(message) {
  if (window.event && window.event.altKey) {
    return true;
  } else {
    return(oldConfirm(message + 
     ' (hold Alt when clicking to bypass warnings)') ? true : false );
  }
 }

Suspects: Веб-стройка

Интересное применение блока

Как же я раньше не догадался:

run_long_something() # long_something длится пять часов

run_long_something do | progress |
  puts "#{progress}% готово" # гораздо лучше
end

Suspects: Веб-стройка

Firefox починен

После долгого копания в nightlies енота для Мака включен движок Cairo и сопутствующий ему рендеринг шрифтов. Это значит, что вот этого безобразия больше не будет.

Firefox-Proper-Rendering

Качайте свою firefox nightly с оф.сайта. Другое дело, что ни один экстеншен с ним не работает (ха!).

Билд firefox 2 с ATSUI font rendering кстати пока еще доступен здесь.

Suspects: Веб-стройка Mac

Теперь все Rails-разработчики Unicode-enabled

С внесением в код Rails набора изменений 5223 мы можем ответственно заявить, что теперь мы предоставляем более чем пристойную поддержку Unicode в Rails, примерно на уровне MediaWiki. С опциональным бакендом на С для ускорения самых емких операций, полностью в нативном для Ruby UTF-8.

More on this...

Suspects: Веб-стройка Юникод

Такой одинокий

Да, тем кто еще не пришел в наши сверкающие ряды - вот таким образом разрешается в Ruby Конундрум О Синглтоне (про мечту о том, чтобы у синглтона были только методы класса):

class Magician
  include Singleton

  def dove
    "*poof* A dove!"
  end

  def segway
    "*rides Segway around*"
  end
end


Magician.dove   #=> "*poof* A dove!"
Magician.segway #=> "*rides Segway around*"

Всческий plumbing само собой выполняется прозрачно, под капотом и с полным сохранением динамики. Neat shit.

Но это для пуристов. Если пуризм не критичен - у классов в Ruby тоже есть instance variables, с которыми соответственно так и можно работать:

class TallMagician
  class << self
    attr_accessor :hat

    def set(var)
      @var = var
    end

    def get
      @var
    end
  end
end

TallMagician.set(234) #=> 234
TallMagician.get # => 234

Suspects: Веб-стройка

Почему мак-юзеры ненавидят Firefox

Ребята, то что пишут про внешний вид кнопок у Firefox на Маке - совершеннейшая лабуда. Да, кнопки - образец уродства (более того, стилизация контролов веб-страницей считается у макюзеров моветоном - и rightfully so).

То есть конечно это погано:

Firefox Bad Widgets

чего и говорить.

More on this...

Suspects: Веб-стройка Mac Юникод

Rails fixtures и машина времени

Fixtures - это способ в удобном текстовом формате (CSV или YAML) собирать тестовые записи для тестирования Rails-приложения. У такого способа тестирования есть недостаток по скорости (поскольку в базу при каждом прогоне тестов данные загружаются заново), но есть и ряд преимуществ - можно тестировать реальные SQL-выборки.

Стандартный файл с fixtures выглядит так:

 first_comment:
   id: 1
   from: Joe User
   created_on: 2001-10-02

 second_comment:...

и так далее. Тем не менее, иногда полезно воспользоваться ERB - Embedded Ruby - для генерации данных, зависимых от среды, в которой проводятся тесты. Благодаря тому, что при каждом прогоне тестов шаблон рендерится заново, можно пользоваться полезными свойствами языка чтобы сделать fixture гораздо более понятной при чтении.

Например, предположим, что в базе есть пароли, закодированные как MD5:

  main_site:
    id: 1
    name: Blogue
    admin_password: 5ebe2294ecd0e0f08eab7690d2a6ee69
    admin_email: authors@blog.ru
    approves_comments: 0

При использовании ERB это легко можно заменить на пароль в "естественном виде":

  main_site:
    admin_password: <%= MD5.new('secret') %>
More on this...

Suspects: Веб-стройка

Негибкость

Читая восторженные отзывы Майка Клишина про всевозможный флешесерверы и флексоенвайронменты, хочется как-то отрезвляюще ответить. Давно причем хочется. Не потому что Все Плохо, а потому что Бангалор - а по восторженным отзывам этого не раскусишь.

Дисклеймер: я сам пользуюсь Flash и пробовал Flex. Делаю это потому, что очень многие вещи, которые Flash может, ни одна распространенная система пока не делает. И на нем можно делать потрясающие вещи (и для них он и нужен). Только запомните, дорогие читатели - делание чего-то приличного на Flash это всегда резьба против волокон, крутка веревок из травы и закат солнца вручную. Новые Макродобовские цацки решают 5 процентов проблем и как правило приносят еще 30 процентов новых.

More on this...

Suspects: Веб-стройка

Rails show and tell

Cмотреть в изобилии тут. Чтобы меня окончательно запозорить ребята из Fingertips разместили и аудиозапись, которую можно послушать.

Смекалистый слушатель заметит, что я делаю ряд обидных фактических ошибок, и постепенно теряюсь в англоязычных идиомах пытаясь их склеить на голландский манер. Кстати да - такой у меня в жизни противный голос ;-)

173107876 Ae172C774A

К моему радостному удивлению товарищи, с Юникодом дела не имевшие (или не отдававшие себе отчет в том, что Ruby с ним делает) мало того что по-настоящему захотели разобраться, что же за фигню их приложения могут творить, но и обратились за советами после презентации.

Что говорит о том что у Rails-приложений которые не печатают по-русски еще eсть шанс.

В числе прочего зарубежным товарищам было рекомендовано проспонсировать разработку ICU4R, которая рискует закончиться даже не начавшись.

А вот у других господ шанса нет:

CREATE TABLE `voydod`.`domains` (
  ...
) ENGINE=InnoDB DEFAULT CHARSET=cp1251;

Простите, это у меня такой глюк что в Узбекистане применяют (или по крайней мере раньше применяли) диакритику?

А дальше больше:

Так сложилось, что это единственная таблица с типом MyISAM. Первоначально поиск был реализован с помощью MySQL Full Text Search, а это возможно только с таблицами MyISAM.

И InnoDB, и UTF-8 полнотекстовым поиском в MySQL успешно поддерживаются, причем не два часа как. - схватили за нос, UTF8 поддерживается но не InnoDB

Suspects: Веб-стройка Юникод

Забота о правильном

Отсюда вот.

Погоня со стороны разработчиков за новыми технологиями. Все чаще и чаще разработчики используют новейшие, но, как правило, сырые и громоздкие технологии. В большинстве случаев, на наш взгляд, это неоправданно с точки зрения отсутствия специфических требований, но привносит значительную долю разнообразия в процесс разработки. Ведь поточная разработка большого количества ресурсов на одних и тех же технологиях не позволяет разработчику развиваться профессионально.

Орфография оригинала сохранена. Это видимо такой хитрый способ сказать, что они жалеют, что ни один из моих клиентов в ближайший год у них хоститься не будет, потому что мы, такие засранцы, используем сырые и громоздкие технологии. Отрадно знать, что они заботятся о моих клиентах.

А дальше больше:

Очень много маркетинговых предложений и технических решений, например в сфере хостинга, необосновано содержится только для обеспечения функционирования хаоса клиентских решений.

Да, мне нужен персистентный сервер и мне sure as hell не нужно чтобы его гасили ногами через три минуты, как это делал Некий Большой Хостер О Котором Мы Умолчим.

Дабы подтвердить важность темы - после известных событий (и выбора рельсов в качестве главной платформы для разработки) я вообще не допускаю выбор заказчиком хостинга. Именно чтобы не попасть в лапы таких-вот... заботливых...

UPD: Поступают правильные реакции с соседнего блога (то бишь с блога из соседней директории):

Vladislav Ukhov написал:
На следующем этапе дорожные рабочие должны начать учить нас ходить.

Suspects: Веб-стройка

Rails "show and tell meeting"

Если кто-то из местных местностей интересуется Ruby и Rails, все приглашаются в четверг на Rails Show&Tell Meeting в штаб-квартиру Greenpeace (а вы куда думали? в офис Большой И Богатой Java-корпорации? :-) на Ottho Heldringstraat 5.

Если вы придете, пожалуйста отметьтесь в комментариях на блоге Fingertips!

UPD: Программа мероприятия

Я буду в частности рассказывать про то как мы (не)используем Unicode в Rails и с чем его едят.

Если у вас плохо с голландским сразу скажу, что по слухам все презентации будут на инглише, именно потому что присутствуют не только голландцы.

Suspects: Веб-стройка Юникод

И я буду смеяться над этим

Потому что это позорище.

Just a message to the managers of the django website : I get an error when I try to access the "Recent Code Changes" RSS feed (on the page http://www.djangoproject.com/weblog/)

the link generating the error is :
http://code.djangoproject.com/timeline... the error is : Ticket changes event provider (TicketModule) failed: UnicodeDecodeError: 'utf8' codec can't decode byte 0xe5 in position 11: unexpected end of data

Best,

Vlad.

Ответ не менее прекрасен.

Hi Vladikio,

That's a known issue, and it's happening because a ticket, or wiki change, or changeset (we don't know) a couple of weeks ago had a funky character in it. See the bug report at http://code.djangoproject.com/ticket/1983 . If you can isolate that funky character, that would be a huge help in fixing the problem.

Adrian

Весь мир это funky characters, если кто еще не понял.

Это тем, кто считает что в Python хороший unicode support и что TRAC - это круто. Ordinal not in range.

Technorati Tags: ,

Suspects: Веб-стройка Юникод

Но с другой стороны...

Бывают и приятные открытия

$ ruby extconf.rb 
creating Makefile

$ make
...bla...
$ sudo make install
...bla...

$ irb -rmbstring -Ku
irb(main):003:0> t = "Некий странный длинный такой текстЪ"
=> "Некий странный длинный такой текстЪ"
irb(main):007:0> t.methods.grep /mb/
=> ["mbchop!", "member?", "mbchop", "mblength", "mbsubstr", "mbsize", "mbreplace", "each_mbchar"]
irb(main):008:0> t.length
=> 66
irb(main):009:0> t.mblength
=> 35 # ура товарищи!

Неважно, что README на японском - там все понятно. Чего же я раньше не заметил... Попутно Николай Луговой продолжает работу над чудесным icu4r - и только Матц спит. Сколько же можно то...

UPD: Знающие товарищи ретранслируют что

...Ruby 1.9.1, which will be released at Christmas 2007, with the Ruby 1.9.0 branch being developed. He will keep on maintaining Ruby 1.8.x as well. If he has to apply security patches, the forth version number (1.9.1.1, 1.9.1.x …) would be possible. Ruby 1.9.1 will include local variables, M17N and YARV inclusion2...

То что нас интересует Матц кличет M17N. Дожить бы.

Technorati Tags: , ,

Suspects: Веб-стройка

Иногда бывает и так

Если и есть за что не любить Ruby и Rails, так это за такое вот...

More on this...

Suspects: Веб-стройка

Cледи за собой

Поскольку новейшие технологии веб-разработки надо отслеживать, я подписан на несколько списков рассылки, не относящихся к Ruby и Rails - и трачу пару часов в неделю на их мониторинг (потому что надо знать, что происходит в смежных областях - как обстоят дела с манипуляторами в Django, как продолжаются continuations в Seaside и как компилируется бессмысленный Flex с кодом на haXe). Кстати и вам советую (если вы уже проснулись от PHP-сна).

Не далее как 4 дня назад в списке рассылки Django появился некто Illias Lazaridis - известный персонаж, в свое время извалявший половину списков рассылок по opensource-проектам в грязи - один из величайших флеймеров Сети, негодяй из негодяев. Может и не негодяй а просто неадекватный робот с Альфа Центавра, кто знает. Его бессмысленные вопли надолго оставили выжженные следы в ruby-lang (и пощечину на целом community - потому что приятнее и адекватнее сообщества найти сложно). Его забанили из массы форумов, а Хани посоветовал ему добровольную стерилизацию.

Результаты не замедлили себя ждать. Через два дня после его появления (он даже пытался стать dedicated contributor в Django) он был стремительно взят на карандаш. Более того, благодаря своему провалившемуся реваншу он теперь есть и в Wikipedia.

С момента, как в рассылке было заявлено, кто он такой, от него не слышно ни звука. Надеюсь так и будет дальше.

Мораль: веди себя прилично.

Suspects: Веб-стройка

Не стоит забывать

Не зря Joichi Ito в свое время купил SixApart на корню.

At 12:37 AM -0400 5/31/06, Timothy Appnel wrote: I've received at least as many shareware payments for RightFields from Japan as from the rest of the world combined, seemingly on the strength of a single blog entry by Tatsuhiko Miyagawa. Plus I've always gotten a steady flow of hits in my referrer logs from numerous Japanese blogs that are saying... well, something... about one or another of my plugins. And that's all without any of the plugins or docs being translated.

Это на тему "подумать" всем пишущим И Еще Один Свой Движок Блогов На PHP.

Suspects: Веб-стройка

Потерянные в плоскости имманентности

Долетело от Лени

Non-RIAs could satisfactorily accomplish most of the application’s requirements. However, we realized that building a rich internet application was the only way to reach the expected level of usability and user experience. The strategy to enhance usability and experience was twofold: improving feedback and response time, and contextually guiding users through tasks–perfect for RIAs.

Если при нашей работе нужно так изъясняться пора либо умерить денежные аппетиты либо переходить в теорию большого пшика и защитить на этом поле пару диссертаций/дипломов. Ну или грантов каких наполучать.

Поеду ка лучше вино пить.

Currently, Andres finds himself leading the user experience “wing” of idfive, LLC, a Baltimore based strategic communications design firm.

Ну ясное дело что тактика для него - слишком мелко плавать.

Suspects: Веб-стройка

Совсем на рельсах

Ruby Truly On Rails

Амстердам - Берлин, скорость - 120 км/ч, кресло жмет но работа идет nevertheless.

Suspects: Веб-стройка

Cеминар по Rails в Москве

Сочуствущим и интересующимся будет любопытно.

В пятницу, 21 апреля, в 18:30 в конференц-зале Института Философии РАН (Москва, ул. Волхонка 14, пятый этаж) состоится семинар на тему:

"Ruby on Rails: быстро и не сворачивая", посвящённый вопросам одноимённой технологии веб-программирования

Форма семинара - дискуссия, направляемая докладчиками. Докладчик: Максим Лапшин.

Провести такое мероприятие в ИФ РАН стоит тридцать у. е., так что будьте готовы внести небольшую лепту. Чем больше участников, тем лепта меньше! Спонсоры приветствуются :)

Подробная информация об этом мероприятии доступна по следующим ссылкам:

http://uneex.cs.msu.su/
http://uneex.cs.msu.su/uneex/SeminarRubyOnRails

Схема проезда: http://www.altlinux.ru/content/view/57/58/

Suspects: Веб-стройка

Рыдал

Отсюда

Просто win-1251 устоявшаяся кодировка для рунета. Работает без проблем. Включая и на любых хостингах. Со временем Юникод станет доминировать и в рунете, но только скорее всего уже не UTF-8, а полноценный UTF-16.

Зачем экономить? Давайте сразу программу "Каждому русскому блогу - UTF-32 до 2010 года". Ее правда придется совместить с программой "Умножь свою дисковую квоту на 4". Которую следует дополнить программой "каждому языку программирования по детектору BOM при чтении файлов, а каждому юзеру - конвертор шаблонов с добавлением BOM".

Нельзя позволять выбрать другую "рабочую" кодировку. Ни в каком софте. Никогда. Можно и нужно принимать и импортировать данные в любой кодировке. Ни один известный мне веб-софт не в состоянии корректно переключать рабочую кодировку с сохранением все зависящих от этого функций. Эта функциональность - B.A.D.

Если ваш хостер считает, что это не так - увольте его.

Suspects: Веб-стройка Юникод

Сайтов много - Мак один

Уже давно у меня была проблемка. В разработке постоянно много сайтов которые надо ссылать на локальную машину. Запускать сервера по очереди неудобно, вносить каждый сайт в списки - устанешь очень быстро.

В итоге около года назад родился скриптик (запускается только через sudo):

#!/usr/bin/env ruby
SitesDir = '/Sites'
DomainSuffix = `hostname`.gsub('.local', '').downcase.gsub(/\s/, '')

###############################################
`nidump hosts .`.split("\n").uniq.sort.each do | dir |
  next unless (dir.split('.').pop == DomainSuffix)
  host = dir.split("\t").pop
  cmd = `niutil -destroy . /machines/#{host}`
end

Dir.entries(SitesDir).each do | entry |
  next if (!File.stat("#{SitesDir}/#{entry}").directory? || entry == '.' || entry == '..')
  puts "> Adding website #{entry}.#{DomainSuffix}"
  `niutil -create . /machines/#{entry}.#{DomainSuffix}`
  `niutil -createprop . /machines/#{entry}.#{DomainSuffix} ip_address 127.0.0.1`
end

Что оно делает? Сканирует папку Sites, и создает для каждой подпапки ссылку в NetInfo (подобии /etc/hosts только удобнее и веселее) на локальную машину, где имя папки - домен второго уровня, а первого - имя машины. Допустим, одну мою машину зовут exile а другую turbine (да, только латиницей и без пробелов - иначе вам не сюда). При этом в папке Sites есть подпапки search, blog, live и client. Это автоматически создаст нам следующие домены ссылающиеся на локальную машину:

  • search.exile и search.turbine
  • blog.exile и blog.turbine
  • live.exile и live.turbine
  • client.exile и client.turbine

После этого пользуясь mass virtual hosting в Апаче можно просто подцеплять сайты автоматически, без всяких Денверов. Имя машины задается в системной панели Sharing.

P.S. С помощью NetInfo можно делать много других занятных штук - например, автоматически создавать маунты сетевых папок с общими шрифтами, музыкой и настройками программ. Подробнее тут. К слову, все это было (и работало по IP-сети) еще до того как вышел Windows for Workgroups. Один из компьютеров с NetInfo подарил нам первый в мире веб-сайт и веб-браузер, а потом другой компьютер с ним же был первым веб-сервером.

Suspects: Веб-стройка Mac

Радостные события

Очень приятно, когда случается что-то чего так долго ждешь. А главное - становится мучительно больно и стыдно что не сделал этого сам :=)

Наконец-то - первый русский макет в CSS Zen Garden. Снимаем шляпу перед Сашей Шабуневичем и быстро отмечаемся на карте. Мафия растет - не запалили бы.

Suspects: Веб-стройка

PDF-книги в стиле Pragmatic Programmers

Недавно возникла задача распространять PDF-файлы с "водяным знаком" - персонализированные версии журнала с отметкой, для кого они были подготовлены. Генерировать оные нужно из веб-приложения (при том что сами журналы сделаны в InDesign). Существует, как выяснилось, масса библиотек дабы PDF создать с нуля, но почти нету оных чтобы уже существующий PDF обработать (в особенности - не имея оригинальных шрифтов например).

Оказалось, что ни на Ruby ни на PHP внятного решения нет, зато есть совершенно магическое на Perl. Дабы никому не пришлось тратить на поиски солюшена много времени (среди кучи бессмысленного shareware для этой задачи под Винды отыскать то что нужно непросто), сразу код.

use PDF::Reuse;
use PDF::Reuse::Util;
use strict;

my ($name) = @ARGV;

prFile('book_for_someone.pdf');

my $sourcePdf = 'pdf_that_your_designer_made.pdf';
my $greeting = "This book is personalized for " . $name;

my $left = 1;
while ($left) {
   prFont('HO');
   prAdd("0 0 0 rg\n0 g\nf\n");

   prText( 38, 800, $greeting);
   $left = prSinglePage($sourcePdf);   
} 

prEnd;

И дальше из любого веб-приложения через командную строку поднимается как

perl make_pdf.pl "John Doe"

Работает по сути гениально - читает старый PDF и засовывает его как графику в генерируемый новый (по принципу матрешки). Если хотите быть оригинальным рекомендую впечатывать не один ватермарк, а два - один из них двухпунктовым белым шрифтом :-)

Suspects: Веб-стройка

Только не мой мозг...

После года Ruby от подобных сюжетов хочется рыдать в голос. Встреча с Flash (спасибо криворуким индусам - с версии 6 пользоваться им на Маке все равно что заниматься анальным онанизмом) заставляет плакать, особенно от того, как они весело вкручивают туда Java-функциональность (не пойму только зачем, кроме code complete):

public function pullXml(path:String):Boolean {
  portXml = new XML();
  portXml.ignoreWhite = true;
  var aRef = this; /* на месте Мартина Фаулера я бы начал рыдать отсюда */
  portXml.onLoad = function(status:Boolean) {
        aRef._xmlCallback(status);
  }
  portXml.load(path);

  return true;
}

Поясним пикантность сниппета.

  1. В загрузчике XML во Flash отсутствует синхронная загрузка. То есть вообще.
  2. Заметьте тонкую игру в Java с подсказками типов. При этом не забывайте, что все можно сделать просто Object и забыть об этом.
  3. Обратите внимание что все кривокобыльные костыли JavaScript остаются на месте - например aRef
  4. Поскольку это JavaScript (а никакая не Java) нельзя просто назначить callback присвоением собственного метода - это по факту копирует метод в XML-обьект.
  5. private или public при этом _xmlCallback - никого не волнует. Потому что это JavaScript сколько бы индусы не пытались доказать обратное.
  6. Угадайте, что возвращает typeof(this) во всех (совершенно всех) флешевых ООП-конструкциях? Правильно. Object. Всегда. Чтобы жизнь была интересна и полна.
  7. Проверка на undefined происходит только при компиляции. Потому что Javascript.

Такое вот у нас ООП. Скорее бы сдать эту дрянь. Констатирую что чудовищнее этого был только Lingo в Director. Как жаль что нету альтернативы этому кошмару.

Upd: А вот такой у них поиск по документации. Сраный Бангалор.

А теперь поищем слово "и"

Suspects: Веб-стройка

Этого не будет

Долго думал о fluxiom, даже советовал его Кирилке. Лелеял надежду что будет хороший тул. Тем не менее худшие подозрения оправдались - fluxiom явно опирается на очень большое количество дополнительных компонентов (не исключено что это компоненты MacOS X, потому как я не понимаю как на чистом опенсорсе можно оптимально залезать внутрь такого количества проприетарных документов) - и в итоге...

Fluxiom is not available for purchase to install on your own servers.

Закройте крышку и вынесите труп. Идея прекрасна, но hosted-сервисы хуже DRM. И хуже закрытых файловых форматов. В разы. Особенно учитывая какого размера среднестатистический слоеный полосный файл в CMYK.

Кстати, это очень большая дырка и проблема для Rails-приложений, потому что хочется дать его для установки на сервер пользователя, да не можется - исходники то открытые. Потому что несмотря на всю их открытость и веб-два-нольность ментальность все равно "между бухгалтерией и переговорной комнатой споткнулся".

Это кстати к тому что у меня несколько раз спрашивали - я принципиально не пользуюсь (и не буду пользоваться) Basecamp. If I use the software - I have it.

Suspects: Веб-стройка

О гибкости

Гибко!

Я предлагаю немедленно внедрить инновационное решение компании Аэрофлот в наши веб-проекты! На первом этапе ограничимся полуоткрытыми окнами. I wonder what they were smoking.

Suspects: Веб-стройка

Вопрос к начинающим (и не очень) Рубинистам

Предположим, что к вам в каминную трубу залез Дед Мороз и принес вам Ruby 2.0. Да, у вас нету каминной трубы (и тем более Деда Мороза, но предположим). И в Ruby 2.0 реализован нормальный Unicode, но - backwards compatible. Что вы предпочтете?

  1. Замедление всей обработки строк во всей программе в целом и "магическое" одарение методов String поддержкой Unicode, в стиле mb_func_overload в PHP:

    str = "Я ненавижу ASCII" str[0..4] #=> "Я нен"

  2. Вся обработка строк остается такой же как и была (происходит в байтовом выражении), но при необходимости обрабатывать именно буквы (если вы скажете что это одно и то же -- я вас побью) это нужно делать так:

    str = "Я ненавижу ASCII" str.characters[0..4] #=> "Я нен"

В варианте 2 естественно правильный софт (а.к.а. Rails) будет использовать правильный синтаксис при обработке строк. В обоих вариантах нужно компилировать расширение для Ruby. Применение новой адресации к строкам работает только при $KCODE == 'UTF8'

Пишите в комментариях какой вариант вам нравится больше.

Важное примечание. Я не Матц и я не буду править исходники Ruby на C. Я пытаюсь сделат meaningful-затычку для этой чудовищной дырки, и мне нужно знать как ее сделать правильнее.

Suspects: Веб-стройка

И это еще цветочки

Потому что все на самом деле еще занятнее. Если взять уже готовую модель Rails-приложения и подключить ее в Wee - получается совсем ракета. Если в Rails еще присутствует сакраментальное session(:value) = "foo", то в Wee все существенно проще. Локальные переменные вообще всего - это пользовательская сессия.

I've definitely been smoking PHP for too long.

Да, если все это вас действительно занимает - можете развлечься с этим - например.

P.S. Дважды welcome to the club, Clops

Suspects: Веб-стройка

Unicode и Ruby

Следуя за длинным рантом рассказываю о том, как я расправляюсь с юникодом в Ruby "своим путем". Этот подход успешно применяется, в том числе в RuTils и на всех rails-работах, которые я выполняю.

Technorati Tags: , ,

More on this...

Suspects: Веб-стройка Mac Юникод

Интересно, через сколько лет

...эти люди вырастут и научатся делать это а) сами б) сразу

Причем это паттерн.

The floggings will continue until morale improves

With ruby, though, it isn’t just a case of American programmers forgetting to think about the other 95% of the world. It’s far more interesting than that – the Perfect as the enemy of the Good. Ruby’s author, Yukihiro “Matz” Matsumoto, being a very smart Japanese programmer, is probably more aware of and better informed about internationalisation and character set issues than almost anybody. The problem is that being both very well-informed and Japanese, he is acutely aware of Unicode’s faults and failings and possibly shares the commonly held Japanese view of it as a grossly inadequate Eurocentric racist kludge.

Не говоря уже о том что иметь три вида строк я несколько не готов. Мне как-то для полного занятия мозга вполне хватает одного.

P.S. А мы с урбаншипом и леней - заслуженные члены Оси Зла. Я ношу титул с гордостью.

Upd: Проверил - в свежих Rails и PostgreSQL и MySQL поддерживают дополнительную опцию encoding с соответствующим функционалом. Как хорошо когда нету historical reasons.

Suspects: Веб-стройка

Ruby и Rails - откуда начать

Появились вопросы - откуда начать изучение Ruby и Rails. Поскольку тема занятная, пишу отдельным постом. В плане литературы - начать с PickAxe (русского перевода нет и не будет), продолжить на Ruby Hacking Guide и попытаться добыть The Ruby Way. После (и только после!) того как базовые идиомы Ruby освоены (заняло у меня недели две) можно приступать к Rails. Для этого есть Agile Web Development with Rails - книжка очень неплохая, но к сожалению (на мой взгляд) она не рассказывает об идеологии Rails (а без ее понимания многие решения будут поначалу казаться абсурдными). Завершить пир духа можно свежевышедшей Enterprise Integration With Rails.

Попутно - нужно освежить знания объектно-ориентированного программирования (особенно если опыта такового раньше не было) - потому что без него на Ruby у вас будет получаться плохая пародия на Perl. Ничего, если PHP вы знаете плохо - но если вам не ясны базовые понятия ООП или вы сомневаетесь в том что огромное количество народу их зачем-то придумывало, много пользы вам извлечь из занятий Ruby будет сложновато. Тем более если вы отвергаете ООП как практику в целом - это модно в русском PHP-сообществе (хотя зачем вам тогда Ruby - пишите сразу на ассемблере).

Technorati Tags: ,

More on this...

Suspects: Веб-стройка

Рельсовые войны

Как многие из посетителей Julik Live знают, последнее время я сильно углубился в Rails. Но записей тут не появлялось не поэтому. Все время с июля по октябрь было потрачено на поезда с 140 по 146, из которых я практически не вылезал. Во-вторых, огромное количество времени отнимает школа (на которую нужно пахать и пахать - и которая не предполагает ни блоггинга, ни вебдева как такового). Наконец-то нашлось полчаса, которые можно посвятить Rails, и это будет длинно и будет занимательно. Потому что просьбы товарищей надо выполнять. Дальше могут читать только люди причастные к веб-программированию (остальным будет неинтересно).

Technorati Tags: ,

More on this...

Suspects: Веб-стройка

TrackDecode совместим с МТ 3.2

После апгрейда до МТ 3.2 и проведенных тестов выяснилось, что TrackDecode работает с новой версией в полном объеме (включая логгинг). Утащить плагин, позволяющий вам не думать о кодировках при получении трекбека на ваш MovableType-блог (плагин работает для всех блогов на одном установленном МТ), можно здесь.

Кстати, похоже что скоро (сезон-другой) поддержка Trackback появится и в е2, что (когда оно случится), будет меня сильно радовать.

Upd: Hooray.

Suspects: Веб-стройка Юникод

Приветствуем соседа

Приветствуем соседа Леню Хачатурова Если мы с ним больно не подеремся (а надеюсь что этого не произойдет) то большая часть julik.nl будет перемещена на TextDrive к концу лета.

Отдельные kudos за то что он не побоялся об этом сказать - поскольку я персонаж одиозный, мало ли что ;-)

More on this...

Suspects: Веб-стройка

К слову

`begin

 site.deploy

rescue FuckedUpRussianHosting

 TextDrive.clients << me

ensure

 russian_hosting.destroy!

end`

Upd: сделано.

Suspects: Веб-стройка

Правила развертывания веб-приложений

15 правил развертывания веб-приложений на платформе UNIX, которые всегда нужно иметь в виду.

More on this...

Suspects: Веб-стройка

Сущностизм (или Yet Another Everything)

В последнее время прихожу к выводу, что свой путь - это даже не национальная русская черта, а гнусное, заразное заболевание - причем мы находимся на пике эпидемии.

Заразное потому, что оно стимулирует людей тратить свое свободное время на фигню. И стоит кому-нибудь заполучить в свое распоряжение хотя-бы полчаса свободного времени, он инвестирует их не в то, чтобы что-нибудь довести до логического конца, а в изобретение треугольного колеса, паравоза на дровах, самолета с винтом вместо крыльев и других полезнейших вещей новых сущностей.

Technorati Tags: ,

More on this...

Suspects: Веб-стройка Любимое Юникод

Нет, одной таблицы нам не хватит

В правильной рассылке проскочило.

Why not say, “I think that's actually not abstract enough; it still assumes a computer-based knowledge system. Better to store just two fields: The name of the object, and the cell phone of a guy who can describe it.”

Именно это (с добавлением мата по необходимости) рекомендуется повторять перед сном разработчикам CMS которые могут все.

Suspects: Веб-стройка

Рельсы - скепсис

Мда. Не прошло и года. Длинное эссе на тему следует.

Скепсис

Поскольку количество людей которые любят “по старому но чтобы работало” (как вариант - которым “ехать и без шашечек”) всегда больше, чем нас (курсив юл.), начнем с удовлетворениях их самых главных сомнений (чтобы никто не подумал напоминать мне об этом в комментариях). Расскажем, ничего не скроем.

Technorati Tags: , ,

More on this...

Suspects: Веб-стройка

Не могу не процитировать

...В первую очередь в области веб-дизайна до сих пор существует огромный водораздел между дисциплинами, ответственными за содержание, форму и технологию. Дизайнер, который небрежно кидает эскиз сайта, сделанный в Photoshop HTML-программисту, никогда не узнает, что небольшие решения формы могут иметь огромное влияние на конечный продукт. Крайне огорчительно, что в техническом задании редко находятся возможности для изменений, продиктованных всесторонним анализом создаваемого дизайна. Разработка интерфейса -- это процесс при котором все дисциплины одновременно и поочередно вносят свой вклад. И при котором дизайнер отнюдь не является (что до сих пор встречается слишком часто) всего лишь “декоратором” или человеком, который “делает нам красиво”.

Petr van Blokland, Koppelen en schakelen (over de interface), items, апрель 2005. Перевод юл.

Добавить нечего.

N.B. Брат Петра фан Блокланда - Эрик - один из соавторов стандарта CSS2. Интересное интервью с ним можно прочитать здесь

Suspects: Веб-стройка Дизайн

Unicode и PostgreSQL

Эта часть Unicode-эпопепеи будет довольно короткой.

More on this...

Suspects: Веб-стройка Юникод

Пока охотники заряжали ружья

Давно не брал я шашек в руки. Пора развлечься. Пока рунет ставит на это ссылки мы попробуем из этого построить домик.

Rails installed

Впечатления

От Ruby (два часа поигрался) - собирал нижнюю челюсть со стола щеточкой. От Rails (час поигрался) - “так не бывает”. До установки FastCGI - “сейчас мой компьютер умрет”. После - опять пришлось потянуться за щеточкой.

Посмотрим что из этого выйдет.

Желающие повторить эксперимент приглашаются проследовать. Кончайте ставить ссылки и попробуйте будущее на вкус.

P.S. - да, все вышеупомянутое на Apache 2.

Suspects: Веб-стройка

У RSS нету страницы 404

Господа, так нельзя.

RSS-это тоже URL. И он тоже не должен меняться, а если и меняется - то вменяемо. И за этим надо следить (причем внимательнее, чем за обычными страницами).

Давайте считать (с маркетинговой точки зрения например), что читатель сайта, который поставил на какую-то страницу закладку, вернется на ваш сайт (то есть он почти постоянный).

Читатель, который подписан на ваш фид - еще более постоянный, господа. В разы. Вполне возможно он зачитывает заголовки вашего сайта каждый день. И если вы оный фид переместили, произойдет то, к чему вы наверняка не привыкли (а пора бы).

Он не увидит 404-й страницы.

More on this...

Suspects: Веб-стройка

Unicode и PHP

Составлено с бесценной помощью Алексея 'huNTer' Колосова

В ближайшее время перекочует куда-нибудь в более подходящее для совместного редактирования место.

Если что-либо из указанного здесь не работает -- обязательно оставьте об этом сообщение в комментариях. Любые замечания и исправления приветствуются (не забудьте сообщить платформу и версию PHP). Все нижеописанное действует для PHP 4.

More on this...

Suspects: Веб-стройка Юникод

Нерусский трекбек

Трекбеки - очень полезная и нужная штука, позволяющая связать блоги (и в принципе сайты) друг с другом по принципу “статья-статья” получше вашего LiveJournal. Работает оная штука (вдруг кто не знает) так:

  • Владелец блога Х пишет статью, под названием “Y - полный идиот”. Хорошую и интересную. Среди прочего он ссылается на текст статьи Y, которую считает ну самым откровенным примером идиотизма Y в жизни.
  • Владелец блога Y немедленно получает оповещение о том, что X сослался на его статью и крушит ее в пух и прах. При этом под статьей Y появляется приписка “На сайте Х - есть статья, зовется - Y ничтожество!”
  • Владелец блога Y идет по линку, читает про себя статью, сию же секунду пишет ответный пасквиль ссылаясь на статью владельца блога X, о чем владелец блога X немедленно узнает тем же способом (стоит господину X поставить линк на господина Y).

А вот дальше наступает самое, пожалуй, интересное (правда, пока не реализованное в материале -- но скоро думаю будет). Господин Z заползает на сайты господина Y и начинает пролистывать фрагменты пасквилей господина X. После этого господин Z может (хитрым способом) подписаться на всю перестрелку пасквилями между господами X и Y а также (внимание!) на все записи других господ, которые ссылаются на пасквили господ X и Y.

Довольно толковое русское описание Trackback с точки зрения пользователя можно прочитать здесь.

Но не в рунете

Однако на подавляющем большинстве русских блогов трекбеки выключены. Почему? Блог, работающий в windows-1251, получая трекбек от блога в utf-8 его спокойненько берет и публикует as-is. Из этого, как вы догадываетесь, ровным счетом ничего хорошего не получается. Когда блог в windows-1251 сбирается ответить, получается еще хуже - win-1251 просто не видно (насколько я понял, это зависит от шрифта). Короче говоря, ничего хорошего. Будучи выпущено в RSS-фид (или любой другой “строгий” носитель) это ничего хорошего эффективно превращается в полный з[skip].

До сегодняшнего дня. Плагин для MT внутри (это кстати следует оценивать и как намек русским пользователем MT - включите прием trackback, время пришло иначе на что я потратил столько времени).

More on this...

Suspects: Веб-стройка Юникод

CSS на практике: Часть III - каскад

В прошлой серии нашей передачи шла речь о том, как наилучшим образом “получить доступ” к конкретному HTML-элементу с помощью CSS. Теперь настало время наилучшим образом этот элемент оформить.

Cascading Style Sheets - не просто название. Никогда не задавались вопросом, почему именно они cascading? Если не задавались (или до сих пор считаете, что CSS-громоздкий инструмент) - пройдите, есть разговор. Устраиваем маленькое шоу, участвуют CSS, каскады и JavaScript.

More on this...

Suspects: Веб-стройка CSS на практике

Опора на собственные силы

Тема уже затрагивалась -- но без упоминания деталей.

Есть многое на свете, как известно. При том что у нас и я блоки самые яблочные, и программисты самые программные, ни один (отечественный) движок блогов не поддерживает ни этого, ни этого, ни этого, ни этого, ни этого, ни этого, ни этого. Об этом даже речи нет. Видимо потому что “нафиг надо”. API для плагинов тоже ни у одного из них вроде нет (правда в последней версии e2 вроде бы появился один callback). Зато вместо всего вышеупомянутого теперь есть например BLIMP, utx, Typografica и масса других полезных и прекрасных, не известных никому и нигде в “большой сети” вещей. Да, еще механизм трекбеков у нас тоже будет свой.

Дадаисты и великий покойный корейский лидер нашли бы это занятным.

Suspects: Веб-стройка

Общими силами

Итак, в связи с полезными подвижками разыскиваются люди, которые помогут мне дописать эпическую повесть под названием “Практическое применение кодировок UTF на веб-страницах и связанных с ними системах и приложениях”.

More on this...

Suspects: Веб-стройка

Закат концепции

Предлагаю, следуя возникающей традиции, объявить сам факт пользования Adobe Photoshop пошлостью.

Давайте посмотрим. Он не предлагает никакой концепции (кроме того, что окончательный размер изображения измеряется в пикселях). Ровным счетом никакой!

А самое главное - посмотрите сколько людей им пользуется. Совершенно очевидно, что в скором времени программу Photoshop ждет неминуемый закат. Более того, немодной она уже стала.

More on this...

Suspects: Веб-стройка

Конвергенция - redux

Когда хорошие вещи кажутся плохими - вы смотрите на них не с той стороны.

More on this...

Suspects: Веб-стройка Дизайн

Конвергенция

Никита Вакорин недавно затронул очень интересную тему - он решил устроить небольшой наезд на русскую школу веб-дизайна (которой нет - прим. юл.). Подолью и я маслица в огонь.

Тупик веб-дизайна -- "разделение профессий", точнее говоря -- "идиотское подобие разделения профессий". Будучи идеалистом не могу не воспользоваться случаем в очередной раз назвать "реалии рынка" лишь Идиотизмом Непросвещенного Большинства.

Даю залп. Скажем честно -- на сегодняшний день большинство веб-дизайнеров не владеют материалом. Это в первую очередь относится к веб-дизайнерам русским, но во многом -- и к зарубежным.

More on this...

Suspects: Веб-стройка Дизайн

CSS на практике: Часть II - немного о селекторах

То, что отличает начинающих CSS-дизайнеров -- неумелое использование селекторов (и как следствие этого - идиотически перегруженный HTML).

В этой заметке я попробую кратко рассказать, какие бывают селекторы, и как пользоваться наследованием и каскадом для того, чтобы указателей класса/ID в документе было меньше, а CSS был гораздо более “всеобъемлющим”.

Итак, приступим.

More on this...

Suspects: Веб-стройка CSS на практике

Лучше сказать было нельзя

Через автора StyleMaster долетела ссылка на статью, удивительно точно описывающую вот эти мои сомнения. Вольный перевод мой.

More on this...

Suspects: Веб-стройка

Синдикат

Самураи веб-стандартов появились не на пустом месте.

Это заговор, я вам говорю.

More on this...

Suspects: Веб-стройка

Дурь неистребима

Напал на чумовой crash-course по CSS в Рунете.

Истекал слезами.

More on this...

Suspects: Веб-стройка

Movable Type с перцем

Сразу скажу - мне не нравятся новые условия лицензирования MT. Не нравятся потому, что радость от того, что погремушку дают даром -- исчезла, а новых (и нужных) функций взамен нет немного И это все. По этой причине, в частности, у меня до сих пор стоит MT 2.6 с чем-то (сам забыл), и вряд ли будет апдейт. Апдейт состоялся, причем вполне лицензионный (благодря членству в ProNet).

Тем не менее хочу сказать ряд лестных слов о MovableType и дать ряд советов, которые кому-то (когда-нибудь, где-нибудь) будут полезны, как всегда сдобрив это изрядной долей субьективности и радикализма.

Movable Type perk

More on this...

Suspects: Веб-стройка

Проклятие русского апача

Недавно я позволил себе очень резкий наезд на блоги с некорректными RSS-фидами.

N.B. - срочно научиться не наезжать на людей.

Блог Azzie попал недавно в мое поле зрения, и я решил выяснить, что же такое с ним происходит (автор блога вряд ли виноват в этой ситуации - во всяком случае и фид и страницы закодированы правильно). Естественно, ответ на этот вопрос мне дал фалидатор фидов. Ответ переводится на русский как “проклятие русского Апача” (на него ссылался еще Влад Головач в своей заметке про CityDesk).

Виноваты кривые руки. Уверение для естествоиспытателей -- mod_charset и кодировка UTF-8 вместе не живут.

Если вы пользуетесь отечественным хостингом, можете быть уверены, что бы вы там не написали, ваш замечательный хостер как всегда умнее вас, и помимо отключения php_info (чтобы вы всю жизнь, чувствуя себя идиотом, гадали, какие модули есть в вашем PHP а каких нет) шлет перед вашими документами указание, что они в кодировке windows-1251. В документации и хелпе по вашему хостингу об этом как правило нету даже строчки. А на самом деле -- чистый саботаж.

More on this...

Suspects: Веб-стройка Юникод

Turnkey-обструкция

Делать сайты на основе веб-стандартов несложно. Хороший текстовый редактор (с подсветкой синтаксиса, желательно), некоторое количество времени, нормальный браузер (не IE, без возражений), и вкусный кофе (с молоком, если есть желание).

Идут в ход нормальные решения и обходы, для традиционных проблем применяются традиционные же хаки, и все идет по обкатанной колее, пока не приходит он.

Форум (веб-магазин, онлайн-чат - добавить по вкусу).

More on this...

Suspects: Веб-стройка

Not sexy anymore - да ну!

Недавно в порядке необходимого зла (sic!) съездил на так называемые voorbeeld lessen в KMT.

В процессе чего заметил интересный факт, пересекающийся с этой статьей на *asterisk.

Дети хотят делать сайты

More on this...

Suspects: Веб-стройка Дизайн

Mac IE и UTF-8 - фикс найден

Итак, была известная проблема. При постинге комментариев из IE для Mac от браузера приходят данные формы в непонятной кодировке. Попытки решить проблему успехом не увенчались, пока не выяснилось следующее.

IE для Mac при отправке комментариев смотрит на шрифт, которым оформлена форма - если он не имеет русского ID, вместо русских букв отсылаются классические кракозябры. Поэтому я прошу всех делающих сайты в UTF (и желающих, чтобы текст от пользователей Mac IE прилетал в нормальном виде - некоторые из-за идиотов таки пользуются этим недобраузером, уверяю вас), добавить в свои стилевые таблицы следущее правило:

`* html input {

font-family: “Geneva CY”, “Helvetica CY”, “Priamoj Prop”, ваши шрифты от MS для форм, sans-serif;

}`

Вместо Geneva CY и пр. для Sans можно использовать Times CY для шрифта с засечками - но они по приоритету должны идти до шрифтов от MS.

Suspects: Веб-стройка Юникод

Ну наконец-то

А первым был LiveJournal...

More on this...

Suspects: Веб-стройка

JavaScript - прототипы

Одна из непопулярных (но при этом невероятно практичных) возможностей JS - изменение прототипов (иными словами, добавление методов и свойств ко всем объектам определенного класса. А возможность между прочим - удобнейшая( !). Пример использования такой возможности - удобное "прокручивание" массивов.

More on this...

Suspects: Веб-стройка

CSS на практике: Часть I - инструменты

Итак, страницы A List Apart, Stopdesign и mezzoblue зачитаны до дыр, на сайте лежит новый файл под названием my-styles.css и мы готовы начать войну со старым порядком веб-кодинга. Что дальше?

More on this...

Suspects: Веб-стройка Дизайн CSS на практике

PHP для настоящих негодяев

Don't try this at home

Правда предусматривается, что вы в состоянии собрать PHP самостоятелно поскольку вам понадобятся довольно обоюдоострые и нестандартные расширения. Форкаем и убиваем процессы, запускаем UNIX-демоны и получаем доступ в shared memory. Для настоящих негодяев.

Suspects: Веб-стройка

Зачем бороться за Unicode

и кому все это нужно

More on this...

Suspects: Случайное Веб-стройка

PHP - исходники в UTF-8...

Сегодня в очередной раз собрался “сломать орешек” - разобраться, почему не получалось у меня культурно записать RSS-feed с одного хорошего сайта

More on this...

Suspects: Веб-стройка Юникод

Почему я не люблю чужие скрипты

Чтобы запустить этот сайт, я находился в поиске blog engine некоего приложения для поддержания блогов в порядке. То, что мне открылось, превзошло всякие ожидания. Даже самый худшие.

More on this...

Suspects: Веб-стройка

Aspirine not included.