Advertisement

Fancy JS flashes in Rails, and a w3c fail

  • Jun. 4th, 2009 at 10:23 PM
Unfortunately, Rails lacks a Prototype tie-in for Element.observe; it only knows about Form.Element.observer. So we have to add the JS a bit more directly.

Double unfortunately, W3C failed at setting a standard:
DOMSubtreeModified
This is a general event for notification of all changes to the document. It can be used instead of the more specific events listed below. It may be fired after a single modification to the document or, at the implementation's discretion, after multiple changes have occurred. The latter use should generally be used to accomodate multiple changes which occur either simultaneously or in rapid succession. The target of this event is the lowest common parent of the changes which have taken place. This event is dispatched after any other events caused by the mutation have fired.


(Bolding added.)

Guess what that means? Safari sends one event, Firefox sends multiple. Safari also sends DOMCharacterDataModified if a div's innerHTML gets changed, Firefox doesn't. I haven't tested with IE, so I don't know what it does, but I bet it's broken too. :-P

The result? In the code below, if you don't use the locking, Firefox - but not Safari - will fire an infinitely recursive series of events that crashes the browser.

The benefit? See the controller & view code below.

Basically, you get to do dead simple flashes without a lot of JS rendering on each event. Just replace the div's HTML and it flashes.

View partial (stick this in your layout w/ the rest of your flashes):

	<% [:info, :error, :warning].each do |type| 
		id = "js_flash_#{type}" -%>
		
		<%= javascript_tag <<-END
				var #{id}_lock = false;
				$('#{id}').observe('DOMSubtreeModified', function(event) { 
					// This lock is necessary because Firefox (but not Safari; dunno IE) will throw a SubtreeModified event when the following code is executed.
					// Which in turn invokes this observer. Which recurses and causes stack overflow, i.e. Firefox hang/crash. Boo.
					if(!#{id}_lock) {
						#{id}_lock = true;
						#{ update_page do |page|
						  page[id].appear
					      page.delay(5) do
					        page[id].fade
							# CRITICALLY IMPORTANT - The assignment MUST occur AFTER everything else is done.
							# NOTE: Prototype/Scriptaculous will execute all these statements in sequence, but NOT wait for them to finish (including delays)!
							# This means it must be done within the updater; if it's at the end of the observer, then it'll get hit before Scriptaculous is done.
							page.delay(2) do
								page.assign "#{id}_lock", false
							end
					      end
						end }
					} });
			END
		%>
	<% end -%>


Controller:

  def do_thingy
    render :partial => 'the_thingy'
  rescue ActiveRecord::RecordNotFound
    render :inline => "thingy not found", :status => 404
  end


Calling function:

<%= check_box_tag 'thingy?' %>
<%= observe_field 'thingy?', :url => thingy_url, :update => {:success => 'thingy_container', :failure => 'js_flash_error'} %>

Whee, attention

  • May. 30th, 2009 at 1:18 AM
My SuperDeploy Capistrano library got picked up by A Fresh Cup.

I learned about this when the Capistrano maintainer pinged me on GitHub; seems he has some ideas for how to make it even better.

Yay collaboration. :-)

TMail undefined method 'index' solution

  • May. 7th, 2009 at 2:18 PM
For the sake of Google, since I saw no solutions and had to just do a full stack trace...

If you're getting this:

NoMethodError: undefined method `index' for #
	from /opt/local/lib/ruby/gems/1.8/gems/tmail-1.2.3.1/lib/tmail/stringio.rb:128:in `gets'
	from /opt/local/lib/ruby/gems/1.8/gems/tmail-1.2.3.1/lib/tmail/mail.rb:392:in `parse_header'


it's because you did something like this:

class Mailman < ActionMailer::Base
  def receive mail
    mail = TMail::Mail.parse raw_mail
   ...
  end
end


Mailman methods get their stuff already preprocessed by ActionMailer into TMail objects. TMail doesn't like trying to parse an object; it's expecting a raw mail string / file.

So just delete that line, treat your arg as a TMail object, and it'll work.

CSS tooltips and flashes

  • May. 4th, 2009 at 10:39 PM
First off: I cobbled together this CSS from a few sources, to do tooltips.

Invoke using: Blah blah thingy info about thingy

It's clean, and sized correctly to the text of the tooltip... but requires explicit
s as it will not wordwrap.

CSS tooltip code )

However, I'm having difficulty getting CSS to work like I want it to for flashes (those little messages that show when you log in or do something or whatnot to confirm your action).

I want it to:
* live within any arbitrary div
* look like a centered box with text in it
* be only as big as needed to fit the text, or a max width if there's wrapping
* have centered or left-aligned (or combination) text, depending on the flash (e.g. errors are centered; longer how-to newbie info is left-aligned)
* play well with having multiple flashes on a page
* preferably consist of a single element w/ a class around the text, rather than text within an element within another element

Most of the CSS I've seen for this doesn't do one of those - e.g. most specify a fixed width, which means that either it gets wrapped poorly or it's got way too much padding.

I achieved this only partially with the tooltip above - it isn't capable of wrapping. (For some reason, unless nowrap or a width is specified, it defaults to being very small in width. I couldn't figure out why, or how to make it just wrap at a specific, wider width.)

Do any of you know how to do this? (Or: do any of you know why this can't be done?)
https://github.com/saizai/rails-authorization-plugin

Authorization is a great plugin. It (plus restful_authentication) is one of the big standard things that everyone uses. I like it.

However, I wanted to do three things in Kura (tracker github site) that it doesn't really support well:

1. permit only guests (not signed in users), e.g. for the signup page

2. permit only the user who has the identity relationship (e.g. only user 2, or an admin, is allowed to access Users#show_private_info 2)

3. permit anonymous people depending on per-record setting - e.g. some wiki pages can be anon-edited, some not (equivalent to anon_user.has_role 'editor', bar_wiki, except there is no actual user there)

Authorization has minimal support for guests as is; basically just a flag that lets it get to the parser if your user's not logged in. But the parser itself doesn't know how to handle it (other than e.g. "not something-else" - but then all your users would have to have some role).


Anyway, I hacked it.

My version (which hopefully will get merged into the main line) does the following:

1. Add an optional role name argument to roles_for:
user.roles_for Group, 'moderator' # moderator roles on any Groups

2. permit 'self of user'

Every user automatically has the 'self' role on themselves, for the purposes of permit expressions only. (has_role? 'self' will fail)

3. permit 'guest'

Allows *only* anonymous users (e.g. for the login page). Note that 'guest of foo' is not special, and is treated as a normal role name.

4. AnonUser.has_role 'editor of public_wiki_post'
AnonUser.has_role? 'editor' # true - a foo of a bar is also a foo generically
# Note: If you want to have these be different, make it a different name (e.g. 'uber_editor')
@banned_user.has_no_role 'editor of public_wiki_post' # Works, but they could just log out and be anonymous
permit 'editor of public_wiki_post' # Allows anon users, or anyone else explicitly assigned the role
Bar.accepts_role 'foo', AnonUser
... etc

AnonUser is a special class provided by Authorization. It acts just like the acts_as_authorized_user class, except that it's for anonymous users. It is not an actual User object, so don't try to treat it as one except for roles related stuff.


While I was at it, I cleaned up some of the code for it to be faster (previously, it did user.roles.select{conditions}; mine finds by the conditions, thus not handling in memory a potentially large number of roles (particularly if e.g. you have a wiki where the anon user has an editor role on most of your pages).

Ruby $0

  • May. 3rd, 2009 at 3:17 PM
What I'm actually running:

/opt/local/bin/ruby -e p(Process.pid.to_s) -e load(ARGV.shift) -I /Applications/Aptana Studio/plugins/org.rubypeople.rdt.launching_1.0.3.200807071913NGT/ruby/flush -rrdt_stream_sync.rb -- script/server webrick --port=3000 --environment=development --binding=127.0.0.1

What it thinks it is:

$0 #=> '-e'
$* #=> ['webrick', '--port=3000', '--environment=development', '--binding=127.0.0.1']

What it's supposed to be:
$0 the name of the ruby script file
$* the command line arguments

... kinda weird. I wonder if there's a better way for the ruby proc to figure out what its actual command line is.

Also weird:

$ ruby
puts 'x' [press enter then control-D]
xD [output]

Alex thinks (probably correctly) that this is because:

$ echo 'puts $0' | ruby
-

So the terminal itself? is outputting '-' (standard "read from stdin"). Then when I hit ctrl-d, it outputs '^D', on the same line, first. Then the output ('x') overwrites the '^'.

Silly terminal.

SuperDeploy - now in plugin form

  • May. 2nd, 2009 at 4:08 AM
http://github.com/saizai/superdeploy/tree/master

1. No complicated capistrano plugin framework or the like. Just install and it works.
2. A nicely set up deploy.rb.example file that'll get copied to your config/ on install
3. Live-and-aggregate tablified smart log scaper

Enjoy.
Several of you are professional or semi-professional photographers & artists.

So I'd like to get your opinion on this site: http://redbubble.com

I've recently started a discussion with them about a potential job for me as an IT lead.

But, well, I'm not a photographer or artist. I can evaluate a site pretty well from a tech and design standpoint - ease of use, loading speed, functionality, etc - but I'm curious what would make you actually use it, and switch from your existing services?

What do you want/need from such a site?

How to robustly identify users?

  • Jan. 25th, 2009 at 12:14 AM
For one of my projects, I would like to make it possible for users to voluntarily positively identify themselves.

This means I would need to know:
a) that they are the person they say they are, by actual legal name;
b) that that person is a qualified, registered voter; and
c) where that person lives (to county level minimum, city preferentially, mailing address optional).

I want to be able to do this with
a) minimal user difficulty,
b) minimal privacy exposure (beyond knowing that specific information),
c) minimal cost, and
d) maximal provability to skeptical third parties.

I've considered a few ways to do this, that have problems.

1. Get direct credit card info, make a tiny charge or deposit to it (e.g. $0.01), and have them verify that amount.

PRO: Gives reasonable assurance of their name, plus a mailing address.

This is the method used by banks (including PayPal) to authenticate someone as a user of an account and (in conjunction with an online contract) authorize them to deposit/withdraw on it as an account owner. Minus the charge/deposit, it's also used by Amazon for their RealName feature.

CON: Directly collecting CC info is a problematic issue. The security requirements (and audit) involved is an expensive pain; and if one does it via deposit, it would cost me at least a few cents per user (which is probably prohibitive for my purposes).

Privacy issues are significant, in that I have zero desire (or need) to store credit card info a priori - I use third party payment processors like PayPal or Amazon - and I don't want to have users have any question about my doing so.

Finally, it's not really good ID. One can get credit cards in other names (for example, under a DBA), use an address that isn't the same as one's residence, or 'borrow' someone else's card.


2. Use PayPal or Amazon as third-party identifiers - have them prove that they own an account, and show that that account has been authenticated by the third party as belonging to the name in question.

PRO: I don't have to store CC info. Otherwise identical to #1.

CON: I (and my auditors) have to trust the third party, whose ID methods may be insufficient. I have to create some way (custom to that third party) to authenticate a user as tied to an account that cannot be mimicked by the user alone. People could use someone else's account to falsely ID.


3. Get the user to submit a scan of a government issued ID, possibly in conjunction with a video or picture them doing something uniquely elicited (e.g. holding a sign that says "I am [name], and I [project slogan]").

PRO: Government ID is generally accepted everywhere. Combined with a photo or video of the person depicted, it's reasonably good ID. Plus, all government IDs have an address, so that is easy to verify.

CON: Privacy is problematic, again, though probably only minimally so. With government ID #, one can look up other information... but this same information can be obtained just from the name & location. However, there's a perception of real privacy associated with it; people would object to it being available to third parties.

More problematic is execution. Most people do not have access to a scanner and webcam or digital camera, and the whole rigamarole is a pain in the ass. Nice for some purposes (e.g. marketing photo montage).

Finally, government IDs can be faked - especially with a scanned or photographed copy. And there's no easy way to automatically process them to see if they match the text version submitted. (It'd take image processing yada yada, something I would rather not get into if unnecessary.)


4. Have user answer automated credit history questions - e.g. last address, mortgage company, etc - same as per what online banks typically require to open an account.

PRO: Pretty good ID (enough to open a new credit account, after all). Moderate extra privacy revelations.

CON: I don't know how one implements this; I presume you have to sign up for it through a (expensive) backend service that's only practical when used by large financial institutions.

Basically, it's probably too expensive. But if you know details, I'd like to learn more.


5. Have user simply give the info and electronically sign a statement swearing under oath that it is true.

PRO: Extremely easy to do. Zero tech involved, zero extra disclosures.

CON: Zero verification, also... with some exceptions.

a) Name and address combination can be checked against third party sources - e.g. government registries, credit card agencies, etc. (Probably for a cost, alas.) So at least, one could verify that that combination was legit (even if one doesn't know the user is that person).

b) This is the only level of verification that is actually required by law for what I want to do - name, address, signature affirming identity & understanding &c.

However, what I want is to be able to provide a *higher* level, for a number of reasons. It's very important to me to prevent sockpuppetry (I know multiple other techniques to fingerprint a particular *computer*, but that's subideal at best); to associate people to real names; and to know for certain that a particular user is an actual currently registered voter of a particular jurisdiction.


So... suggestions? More info? Other pros/cons I should consider?

Re-releasing SuperDeploy

  • Dec. 13th, 2008 at 10:57 PM
See http://github.com/saizai/superdeploy/tree/master

The short version:

SuperDeploy is a utility library for Capistrano.

One of its main features is the util:logs:smart task, which gives you a live, aggregated, tabled view of what's getting called, errors (with just the app-internal trace & # of errors), load times, unique IPs, hits, etc.

I posted about this earlier; I've only just gotten around to putting this up on GitHub.

Enjoy.

My experience with scaling Ruby on Rails

  • Dec. 13th, 2008 at 9:40 PM
Watching some of the RubyConf 2008 talks, and thinking of some of the false opinions* propogated at my former job, reminded me I wanted to briefly write up a bit on this.

The claim: Ruby on Rails is slow.

But let's be practical. What exactly are you trying to run? How many people do you need to serve? How fast? What are the real parameters?

To give the main number for my previous project that are openly available: up to 250k unique users per day. (That's on one app of several. The combined total was significantly more.)

That's with lots of pageviews, and very dynamic info on everything - we're not talking a simple blog app serving easily cached static pages.

Nearly all pages were served at <0.1s. IMNSHO, at that point further speed improvement is essentially irrelevant, because other things start to compete for affecting user-experienced page load time more.

This is with a small number of servers. And this is without using the more advanced techniques that I've used in other projects, like background tasks, push, etc, which make things that much faster. (The codebase was too big to be worth the refactoring time.)

We had room to scale another 10x without having to do more than maybe upgrade some server RAM.


So I have to ask: do you need faster than that? Do you need to serve more than, say, 10 million users per day?

Yes, PHP can be faster. But it's also hairier and slower to write. And I can't honestly say that I've ever seen PHP code that didn't make me cringe inside. (Maybe you're the exception and can make pretty PHP. But I doubt it.)


In my experience, the only things that made real speed impacts were stupid code. E.g. using ginormous arrays of objects; forgetting to use DB indices; writing inefficient DB calls (e.g. Foo.find(:all).size vs Foo.count); etc. All the standard stuff. I've refactored a lot of it.

Once those are eliminated, very few things take more than a few hundredths of a second at most. The best ones take a couple ms.

Eventually you get to the point of saying simply, how many requests per second can you handle? Is the server capable of handling slashdot?

If it's written well, then it probably is. Hopefully you knew how to use memcache. Maybe you have to split off slow vs fast pages and contain the swarm.

But for normal growth patterns, it's a slow curve. You realize that you're starting to go into swap. Response times slow down. Stuff like that.

That's the point at which you buy another server. It'll probably cost less than a couple days of coder-time.

In the meantime, your coder can work on making stuff better.


I could go on about specific techniques, but other people have already done that perfectly well. (If you want help scaling *your* code, I'm available for hire as a consultant.)

My point here is simply this:

I bet you're not running something bigger than I was running, and if you are, you're still not Amazon - add a server and do some code review, and you'll be fine.

Don't sacrifice code quality, maintainability, and sanity for a false idea of speed limitations.
Taken with slight modifications from Jeremy's comment here.


The problem:

1. Safari does not allow an iframe with a domain different from the domain of the main page to set cookies unless the user 'actively' navigates it, i.e. clicks a link.

2. Facebook apps using iframes are an instance of this - the main page is apps.facebook.com, and the iframe is some.developer.site.

The solution )

... and one more

  • Sep. 22nd, 2008 at 11:09 PM
So, I needed to actually render stuff from a Workling. Workling doesn't have ActionController (nor Jug's patches to it), and Ruby doesn't do multiple inheritance in a way that makes this easy.

However, this works pretty well:

class JugsWorker < Workling::Base
BG_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}-background.log")
BG_LOGGER.debug "#{Time.now.to_s}: Loading JugsWorker"
require 'action_controller/integration'
BG_LOGGER.debug "#{Time.now.to_s}: ... done"

def push_stuff options
BG_LOGGER.debug "#{Time.now.to_s}: pushing magic for #{options.inspect}"
@app_integration ||= ActionController::Integration::Session.new

ret = @app_integration.post '/magic/pixie_dust', options # Renders stuff!
end
end

Derived from http://errtheblog.com/posts/24-irb-mix-tape (yay Err) and some headbashing.

A lot easier than mucking with trying to get an ActionController & View properly initialized & templates correct etc etc etc. :)

I don't however know how well this'll hold up in production.

Hopefully this should just effectively load one, somewhat unusual, model. I keep the integration session around as an instance variable (Worklings are singletons), so it shouldn't have issues w/ loading it over & over.

There might be issues with slowness, however. For example, how will this deal with load?

May need to have Workling / Starling be multi-threaded etc.

Oh well, it works for now. And it only took the whole evening!
1. They lie about what the current versions are in a lot of places. And those old versions are b0rked.

You should have (as of this writing, w/ Rails 2.1):

Gems (all on GitHub, ignore the other ones of similar name anywhere else):
fiveruns-memcache-client (1.5.0.3)
jeremylightsmith-piston (1.9.3) # makes piston work with git, woohoo!
starling-starling (0.9.7.10)

Note that config.gem is broken for git gems so you have to do it special:
config.gem 'fiveruns-memcache-client', :version => '>= 1.5.0.3', :lib => 'memcache', :source => 'http://gems.github.com'
config.gem 'starling-starling', :version => '>= 0.9.7.10', :lib => 'starling', :source => 'http://gems.github.com'

System code (/usr/lib etc depending on your system):
libevent-1.4.8-stable
libmemcached-0.23
memcached-1.2.6

Plugin (I <3 Piston, you should too):
git://github.com/tra/spawn.git
git://github.com/purzelrakete/workling.git
git://github.com/defunkt/cache_fu.git # makes memcached usage much easier


2. Workers don't have access to your default logger.

This made me bash my head a while trying to figure out if the sample AnalyticsWorker.asynch_potential_invited(:potential_user_id => 1234) call was even doing anything. It wouldn't even react to raise "PleaseSeeMe".

I dropped in an easy Juggernaut.send_to_all("alert('yes this worker is alive')") call and it worked.

The logger however didn't.

Easy fix though:

class AnalyticsWorker < Workling::Base
BG_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}-background.log")

def potential_invited(options)
BG_LOGGER.info 'hit potential invited whee'

... etc


There's also a nice script in vendor/plugins/workling/script/status.rb - run it & it'll dump Workling's current status.

Note that, even when I had finally gotten it running, it still included:

Servers:
[<MemCache::Server: localhost:22122 [1] (NOT CONNECTED)>]

... soooo maybe that only shows as connected when there's heavier load and it actually has the socket to memcache open live when you query. Iunno. I kinda expected that it'd keep open a persistent socket...


3. The starling client's version of your code does not autoreload even if RAILS_ENV='development'. So you have to kill & restart it every time if you're debugging your workers. Oh well, but it'd be nice if it respected the initializer's config settings about such caching...

BTW: in my case, I'm using starling (in part) to async some tasks because otherwise they'd be self-blocking. So I can't just skip declaring a backgroundworker handler & have it default to sync behavior. :-P


4. cache_fu assumes your default network socket is eth0.

I'm running OSX in development, so that's not quite true.

Easy patch:

a) add a line to memcached.yml:
development:
sessions: false
fragments: false
servers: localhost:11211
eth: en0
# it's a mac, ain't got no eth0 ^
# note: .yml files are whitespace-sensitive; must use spaces @ same indent (no tabs)


b) edit line 18 of script/memcached_ctl to look for it:
self.ethernet_device = config[env]['eth'] || 'eth0'

... and move it to right after the config = YAML.load(stuff) line

Other than this little oversight, cache_fu rocks.

Capistrano SuperDeploy script v2.3

  • May. 11th, 2008 at 7:16 PM
http://pastie.caboo.se/195345

* Trimmed-down easy config file
* Lots of utility functions
* Smart log tail-er (cap util:logs:smart) that will show aggregate times, hits, errors, etc in pretty format!

Enjoy. :)

Edit 8:05pm: Added exponential weighted average for hits/min counter; fixed regex bug w/ multiple errors in one chunk

Rails query analyzer plugin

  • Mar. 19th, 2008 at 12:02 AM
Improved SQL profiling (w/ timings etc) to CSV spreadsheet plugin: http://pastie.caboo.se/167685
*stops banging head against desk in favor of dinner and sewing*


In other news, my apartment manager gave me a letter saying that to get my parking restored, I would have to submit an inspection report from an official Acura dealer oil change to prove that my car is drivable.

This is several MONTHS into things. Last time I talked to her she said nothing about it. And after I drove my car off the lot. And submitted requests 2-3 times 'cause she "lost" them.

What started it? I had to replace my battery. I fixed it by buying a battery at the local shop, and replacing it myself. And gave her a written notice that yes I had fixed it. She did not find this to be adequate proof that I had done so, nor was the fact that I could drive the car off the lot. (Which I did when they threatened to have it towed... for not being drivable.)

At this point I think she's just being intentionally obstructive.

At least I'm leaving soon.

Deploy!

  • Apr. 10th, 2007 at 12:17 AM
So today I deployed the huge set of changes I've been working on.

Took a mere 13 hours straight o.o but now it's mostly working. Mostly.

Some things are wonky and some are broken and some are working awesomely and I am too fucking tired and am going to bed.

Oy, if I see another bloody obscure bug having to do with little differences in production vs development mode that causes completely useless or outright false error messages that only crop up on the production server...

Oh, and the bossman wants to demo the NEW functionality - i.e. the stuff I've only barely started on - early next week.

o.o


Add to that a wide assortment of things going both wrong and right in other aspects of my life, and the fact that I'm pretty much out of time for moving so that needs to happen somewhere around NOW and that requires time too etc... mrr.

For some reason I'm not actually panicking, but I get the feeling that I would be if I were normaler.

Whee, total overhaul!

  • Mar. 21st, 2007 at 3:57 PM
Migrating to Rails 1.2, REST, ActiveScaffold, ... wheeeee.

A bit of a PITA but hopefully will make for a nice DRY Saibase that functions better (perhaps even more leanly... right now all the ruby instances are 30mb, which with a 100-200mb mem cap and multispawning ruby instances can get a bit ugly). :-)

And then I get to fix various bugs, and add stuff, and tweak it, and add the new email-based interface, and stuff! The email thing might actually be easier with REST; dunno if it could do responds_to or not. But it'd make it eas(ier) to do a crackberry/treo specific interface, which is what all of middle/upper management and almost all of sales are tied to (and guess who my clients are).

Huzzah work geekery anyhow.

But in the meantime I get to wait for gem update -y to finish. Looks like I'm fairly behind...

Hopefully most of the code I already wrote should port to the new version relatively easily (or perhaps even not be needed).

Edit: Yay, massive bugs on framework updates. Oy. Looks like I'll be redoing the login/authentication stuff too. Fun.

Profile

glyph
[info]saizai
Sai Emrys

Latest Month

December 2009
S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  

Tags

Syndicate

RSS Atom
Powered by LiveJournal.com
Designed by Lilia Ahner