diff --git a/Gemfile b/Gemfile index ffb1b27..c74d61d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,37 @@ source 'https://rubygems.org' gem 'nanoc' -gem 'adsf' -gem 'kramdown' -gem 'sass' + gem 'haml' -gem 'coffee-script' -gem 'typogruby' \ No newline at end of file +gem 'kramdown' + +gem 'coderay' + +gem 'sass' +gem 'compass' +gem 'bourbon' + +gem 'typogruby' +gem 'nanoc-cachebuster' +gem 'nanoc-javascript-concatenator' + +gem 'uglifier' +gem 'multi_json', '~> 1.3' # needed by uglifier + +# needed for tasks +gem 'rake' + +# needed to run nanoc autocompile +gem 'rack' +gem 'mime-types' +gem 'i18n' + +# needed to run nanoc view +gem 'adsf' + +# needed to run nanoc deploy +gem 'systemu' + +# needed to run validations +gem 'w3c_validators' +gem 'nokogiri' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 47dc75b..183be85 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,36 +3,72 @@ GEM specs: adsf (1.1.1) rack (>= 1.0.0) - coffee-script (2.2.0) - coffee-script-source - execjs - coffee-script-source (1.6.2) + bourbon (3.1.1) + sass (>= 3.2.0) + thor + chunky_png (1.2.7) + coderay (1.0.9) colored (1.2) + compass (0.12.2) + chunky_png (~> 1.2) + fssm (>= 0.2.7) + sass (~> 3.1) cri (2.3.0) colored (>= 1.2) execjs (1.4.0) multi_json (~> 1.0) + fssm (0.2.10) haml (4.0.1) tilt + i18n (0.6.4) + json (1.7.7) kramdown (0.14.2) + mime-types (1.21) multi_json (1.7.1) nanoc (3.6.1) cri (~> 2.3) + nanoc-cachebuster (0.3.1) + nanoc (>= 3.3.0) + nanoc-javascript-concatenator (0.0.2) + nanoc (>= 3.3.0) + nokogiri (1.5.9) rack (1.5.2) + rake (10.0.3) rubypants (0.2.0) sass (3.2.7) + systemu (2.5.2) + thor (0.17.0) tilt (1.3.6) typogruby (1.0.15) rubypants + uglifier (1.3.0) + execjs (>= 0.3.0) + multi_json (~> 1.0, >= 1.0.2) + w3c_validators (1.2) + json + nokogiri PLATFORMS ruby DEPENDENCIES adsf - coffee-script + bourbon + coderay + compass haml + i18n kramdown + mime-types + multi_json (~> 1.3) nanoc + nanoc-cachebuster + nanoc-javascript-concatenator + nokogiri + rack + rake sass + systemu typogruby + uglifier + w3c_validators diff --git a/Rules b/Rules index 18165ac..51a27ee 100644 --- a/Rules +++ b/Rules @@ -1,37 +1,76 @@ #!/usr/bin/env ruby SASS_LOAD_PATHS = [ - File.join(`bundle show bourbon`.chomp, *%w[app assets stylesheets]), - File.join(`bundle show rocks`.chomp, *%w[app assets stylesheets]) + File.join(`bundle show bourbon`.chomp, *%w[app assets stylesheets]) ] -compile '/stylesheet/' do - filter :sass, syntax: :scss, load_paths: SASS_LOAD_PATHS +BYPASS_FILES = %w(404.html crossdomain.xml humans.txt robots.txt) unless defined?(BYPASS_FILES) + +BYPASS_FILES.each do |file| + compile("/#{file.sub /\..+/, ''}/") do + # don't filter bypass files + end end -compile '/javascript/' do - filter :coffeescript +compile %r{/_.+/$} do + # don't filter partials +end + +compile '/css/*/' do + # filter :sass, syntax: :scss, load_paths: SASS_LOAD_PATHS + filter :sass, Compass.sass_engine_options.merge(load_paths: SASS_LOAD_PATHS) +end + +compile '/js/*/' do + filter :concat_js + filter :uglify_js end compile '/posts/*' do filter :kramdown, auto_ids: false, coderay_line_numbers: nil, coderay_tab_width: 2 filter :typogruby layout 'post' + layout 'default' + filter :cache_buster end compile '*' do unless item.binary? - filter :erb - layout 'default' + case item[:extension] + when 'md' + filter :erb + filter :kramdown + when 'haml' + filter :haml, format: :html5 + else + filter :erb + end + + if %w(html haml md).include?(item[:extension]) + layout 'default' + filter :cache_buster + end end end -route '/stylesheet/' do - '/style.css' +BYPASS_FILES.each do |file| + route("/#{file.sub /\..+/, ''}/") do + "/#{file}" # route bypass files as is + end end -route '/javascript/' do - '/script.js' +route %r{/_.+/$} do + nil # don't route partials +end + +route '/css/*/' do + fp = fingerprint(item[:filename]) + item.identifier.chop + fp + '.css' +end + +route '/js/*/' do + fp = fingerprint(item[:filename]) + item.identifier.chop + fp + '.js' end route '/posts/*' do @@ -49,4 +88,4 @@ route '*' do end end -layout '*', :erb +layout '*', :haml, :format => :html5 diff --git a/compass-config.rb b/compass-config.rb new file mode 100644 index 0000000..80946d6 --- /dev/null +++ b/compass-config.rb @@ -0,0 +1,10 @@ +project_path = File.dirname(File.dirname(__FILE__)) +http_path = '/' +output_style = :compressed +# output_style = :expanded +sass_dir = 'content/css' +css_dir = 'output/css' + +sass_options = { + :syntax => :scss +} \ No newline at end of file diff --git a/content/css/_main.scss b/content/css/_main.scss new file mode 100644 index 0000000..f39236c --- /dev/null +++ b/content/css/_main.scss @@ -0,0 +1,60 @@ +body { + @include background(linear-gradient(red, green) left repeat); + color: white; + font-weight: bold; +} + +h1, h2, h3 { + text-shadow: 1px 1px 2px #9af; +} + +#content > h1 { + overflow: auto; + img { + border: 1px solid #888; + margin-right: 0.5em; + float: left; + } +} + +p, li { + text-shadow: 1px 1px black; +} + +a, a:link, a:visited { + color: #9af; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +#content { + position: absolute; + + top: 40px; + left: 300px; + margin-right: 100px; + + border-left: 3px solid #888; + padding-left: 40px; +} + +#sidebar { + position: absolute; + + top: 40px; + left: 20px; + width: 260px; + + padding: 20px 20px 0 0; + + text-align: right; +} + +#sidebar ul { + list-style-type: none; + + margin: 20px 0; +} \ No newline at end of file diff --git a/content/css/_normalize.scss b/content/css/_normalize.scss new file mode 100644 index 0000000..4617600 --- /dev/null +++ b/content/css/_normalize.scss @@ -0,0 +1,504 @@ +/*! normalize.css v1.0.2 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/* + * Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. + */ + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +/* + * Prevents modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/* + * Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3, + * and Safari 4. + * Known issue: no IE 6 support. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/* + * 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using + * `em` units. + * 2. Prevents iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-size: 100%; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -ms-text-size-adjust: 100%; /* 2 */ +} + +/* + * Addresses `font-family` inconsistency between `textarea` and other form + * elements. + */ + +html, +button, +input, +select, +textarea { + font-family: sans-serif; +} + +/* + * Addresses margins handled incorrectly in IE 6/7. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/* + * Addresses `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/* + * Addresses font sizes and margins set differently in IE 6/7. + * Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5, + * and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +h2 { + font-size: 1.5em; + margin: 0.83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: 0.83em; + margin: 1.67em 0; +} + +h6 { + font-size: 0.67em; + margin: 2.33em 0; +} + +/* + * Addresses styling not present in IE 7/8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +/* + * Addresses styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE 6/7/8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/* + * Addresses margins set differently in IE 6/7. + */ + +p, +pre { + margin: 1em 0; +} + +/* + * Corrects font family set oddly in IE 6, Safari 4/5, and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +/* + * Improves readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* + * Addresses CSS quotes not supported in IE 6/7. + */ + +q { + quotes: none; +} + +/* + * Addresses `quotes` property not supported in Safari 4. + */ + +q:before, +q:after { + content: ''; + content: none; +} + +/* + * Addresses inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/* + * Prevents `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Lists + ========================================================================== */ + +/* + * Addresses margins set differently in IE 6/7. + */ + +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +/* + * Addresses paddings set differently in IE 6/7. + */ + +menu, +ol, +ul { + padding: 0 0 0 40px; +} + +/* + * Corrects list images handled incorrectly in IE 7. + */ + +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/* + * 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3. + * 2. Improves image quality when scaled in IE 7. + */ + +img { + border: 0; /* 1 */ + -ms-interpolation-mode: bicubic; /* 2 */ +} + +/* + * Corrects overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/* + * Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/* + * Corrects margin displayed oddly in IE 6/7. + */ + +form { + margin: 0; +} + +/* + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE 6/7/8/9. + * 2. Corrects text not wrapping in Firefox 3. + * 3. Corrects alignment displayed oddly in IE 6/7. + */ + +legend { + border: 0; /* 1 */ + padding: 0; + white-space: normal; /* 2 */ + *margin-left: -7px; /* 3 */ +} + +/* + * 1. Corrects font size not being inherited in all browsers. + * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5, + * and Chrome. + * 3. Improves appearance and consistency in all browsers. + */ + +button, +input, +select, +textarea { + font-size: 100%; /* 1 */ + margin: 0; /* 2 */ + vertical-align: baseline; /* 3 */ + *vertical-align: middle; /* 3 */ +} + +/* + * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/* + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Corrects inability to style clickable `input` types in iOS. + * 3. Improves usability and consistency of cursor style between image-type + * `input` and others. + * 4. Removes inner spacing in IE 7 without affecting normal text inputs. + * Known issue: inner spacing remains in IE 6. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ + *overflow: visible; /* 4 */ +} + +/* + * Re-set default cursor for disabled elements. + */ + +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to content-box in IE 8/9. + * 2. Removes excess padding in IE 8/9. + * 3. Removes excess padding in IE 7. + * Known issue: excess padding remains in IE 6. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + *height: 13px; /* 3 */ + *width: 13px; /* 3 */ +} + +/* + * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in Firefox 3+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE 6/7/8/9. + * 2. Improves readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/* + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/content/css/style.scss b/content/css/style.scss new file mode 100644 index 0000000..2979185 --- /dev/null +++ b/content/css/style.scss @@ -0,0 +1,3 @@ +@import "normalize"; +@import "bourbon"; +@import "main"; \ No newline at end of file diff --git a/content/index.haml b/content/index.haml new file mode 100644 index 0000000..9d73e2b --- /dev/null +++ b/content/index.haml @@ -0,0 +1,14 @@ +--- +title: Home +--- +%h1 ariejan.net + +#posts +- articles_by_year.each do |year, articles| + %h2= year + + %ol + - articles.each do |article| + %li + %span.date= article[:created_at] + = link_to article[:title], article.path diff --git a/content/index.md b/content/index.md deleted file mode 100644 index 7f8b4c5..0000000 --- a/content/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Home ---- - -# Nanoc - -An awesome site generator. \ No newline at end of file diff --git a/content/js/_main.js b/content/js/_main.js new file mode 100644 index 0000000..e69de29 diff --git a/content/js/_plugins.js b/content/js/_plugins.js new file mode 100644 index 0000000..ba113ac --- /dev/null +++ b/content/js/_plugins.js @@ -0,0 +1,22 @@ +// Avoid `console` errors in browsers that lack a console. +(function() { + var method; + var noop = function noop() {}; + var methods = [ + 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', + 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', + 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', + 'timeStamp', 'trace', 'warn' + ]; + var length = methods.length; + var console = (window.console = window.console || {}); + + while (length--) { + method = methods[length]; + + // Only stub undefined methods. + if (!console[method]) { + console[method] = noop; + } + } +}()); \ No newline at end of file diff --git a/content/js/application.js b/content/js/application.js new file mode 100644 index 0000000..898b960 --- /dev/null +++ b/content/js/application.js @@ -0,0 +1,2 @@ +//= require _main.js +//= require _plugins.js \ No newline at end of file diff --git a/content/js/vendor/jquery.js b/content/js/vendor/jquery.js new file mode 100644 index 0000000..3883779 --- /dev/null +++ b/content/js/vendor/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/content/js/vendor/modernizr.js b/content/js/vendor/modernizr.js new file mode 100644 index 0000000..abfe536 --- /dev/null +++ b/content/js/vendor/modernizr.js @@ -0,0 +1,4 @@ +/* Modernizr 2.6.2 (Custom Build) | MIT & BSD + * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load + */ +;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;fRuby on Rails, I gave a RoR weblog, TypoSphere, a try. I exported my entire Wordpress blog to typo and I was a happy man. Until disaster struck... + +After a while I noticed that typo's search functionality was not working. I recieved a MySQL error messages that the table containing my artilces did not exists! To make a long story short, all database files containing data were gone. Luckily I make regular backups every day automatically. + +Well, I make regular backups and keep the seven most recent backup files at hand. But, you guessed it, apparently the disaster happened more than a week ago. So, all my backup files contained the corrupted database files. Bugger! + +So, after some flirting with Dreamweaver, Drupal and Joomla, I'm back at Wordpress which really is the best, easy to use and customize publishing tool I know. + +Since all my previous tutorials are gone, I'm going to start with a clean slate here. Please comment on my articles if you like and feel free to link anything you find here. + +For now, I'll have to re-customize my site and then I'm ready to start posting useful things here. \ No newline at end of file diff --git a/content/posts/2006-10-10-easily-create-a-favicon-online.md b/content/posts/2006-10-10-easily-create-a-favicon-online.md new file mode 100644 index 0000000..1b79922 --- /dev/null +++ b/content/posts/2006-10-10-easily-create-a-favicon-online.md @@ -0,0 +1,24 @@ +--- +title: "Easily create a FavIcon online!" +kind: article +slug: easily-create-a-favicon-online +created_at: 2006-10-10 +tags: + - General + - Everything + - Blog + - Web Development + - Links + - Personal +--- + +You know those little icons you see in your browsers address bar? Yes, the ones in you bookmarks! Well, you want such an icon for your site? + +These icons are called FavIcons, short for Favourites Icon. Most modern browsers make use of them. + +To create such an icon you'll need some image first. Normally the process would be rather difficult involving several graphics manipulation programs and conversions. Don't fall for that! Use the followin FavIcon from Pics site! + +Just upload a gif, jpeg or png image (with transparency if you like) and get a FavIcon for free! There are also some other goodies included! + +Of course, there's information available on how to upload your FavIcon and how to let your browser know about it. + \ No newline at end of file diff --git a/content/posts/2006-10-10-generate-a-sqlite-based-rails-app.md b/content/posts/2006-10-10-generate-a-sqlite-based-rails-app.md new file mode 100644 index 0000000..a157316 --- /dev/null +++ b/content/posts/2006-10-10-generate-a-sqlite-based-rails-app.md @@ -0,0 +1,33 @@ +--- +title: "Generate a SQlite-based Rails app" +kind: article +slug: generate-a-sqlite-based-rails-app +created_at: 2006-10-10 +tags: + - General + - Everything + - RubyOnRails + - Databases + - SQlite + - Features +--- + +When you create a Rails application a database.yml files is included with some default configuration for your database. Unfortunately these are defaults for MySQL. If you want to use another database, like SQlite, you'd have to rewrite the entire configuration file. And that's not what you want! + +Luckily, rails is very adapative and we can make it do all the work for us. + +When you create a rails application, firstr check out the help message from rails itself. + +$ rails --help + +When you look closely you'll see that there's an option available called '--database'. It has the following message attached to it: + +
Preconfigure for selected database (options: mysql/oracle/postgresql/sqlite2/sqlite3).
+ +Basically, you can specify, when create your rails project, what database type you want to use, and Rails'll create a matching configuration file for you. When you leave this option out, it defaults to MySQL. + +As you can see there are several databases supported, including SQLite. In order to create a new rails project with SQlite3 configured by default, run this: + +$ rails --database=sqlite3 myproject + +Check out config/database.yml and you'll see that there are sensible defaults for you there. \ No newline at end of file diff --git a/content/posts/2006-10-10-hi-res-wallpapers-for-you-macbook.md b/content/posts/2006-10-10-hi-res-wallpapers-for-you-macbook.md new file mode 100644 index 0000000..46c9528 --- /dev/null +++ b/content/posts/2006-10-10-hi-res-wallpapers-for-you-macbook.md @@ -0,0 +1,21 @@ +--- +title: "Hi-Res Wallpapers for your MacBook!" +kind: article +slug: hi-res-wallpapers-for-you-macbook +created_at: 2006-10-10 +tags: + - General + - Everything + - Blog + - Links + - Eye Candy + - MacBook + - Professional +--- + +For all of you who have a flashy Apple MacBook (or any other device with a 1280x800 resolution) here's a nice site with lot's of hi-res wallpapers. Of course, they're all for free! + +Oh, they have other sizes as well. + +Go check out InterfaceLIFT. + \ No newline at end of file diff --git a/content/posts/2006-10-10-top-5-dvds-you-must-have.md b/content/posts/2006-10-10-top-5-dvds-you-must-have.md new file mode 100644 index 0000000..465de4f --- /dev/null +++ b/content/posts/2006-10-10-top-5-dvds-you-must-have.md @@ -0,0 +1,58 @@ +--- +title: "Top 5 DVDs you must have" +kind: article +slug: top-5-dvds-you-must-have +created_at: 2006-10-10 +tags: + - General + - Everything + - Blog + - Lists + - Movies + - DVD + - Coding +--- +Everybody at this point in time at least owns one or more DVD - Digital Versatile Disk - Videos. Some people try to collect as many DVD's as possible, others just buy the ones they like (or can afford). + +I'm not sure how many DVD's I have at this time, but there are quite a few DVD's that you just have to have in your collection. + +If you feel wronged because your favourite movie is not listed, feel free to add a comment stating your favourites. (Oh, let me know why it's such a great movie too!) + + +

5. Gladiator

+ +GladiatorGladiator, by Ridley Scott, is tells the story off a Roman general who became a slave and then a gladiator. It sounds pretty boring when you put it like that, but Scott really makes it into a very gripping movie. + +Gladiator is more than a few man in shorts fighting in a big bowl of sand. You should really check this one out and add it to your collection. + +

4. The Lord of the Rings Trilogy

+ +Lord of the RingsWell, The Lord of the Rings is not really a trilogy since it is one big story. Still, this movie is great. + +LOTR, by Peter Jackson, attracts old and young alike. It features humour, heroism, love, massive battles between good and evil and everything else needed to make a successful movie. + +Many complain that the Fellowship (part 1) does not have much action in it and that Return of the king (part 2) is just one big fat ending. Well, that's correct. In my opinion you should watch the three movies in sequence all at once. That will give you the full sensation the way Jackson intended it. + +Because this movie appeals to a very broad audience, it's placed at position 4. + +

3. Pulp Ficton

+ +Pulp FictionThe combining of several story lines into one movie with a lot of violence by Quentin Tarantino. The result is Pulp Fiction. + +Every story line has it's own characters and Tarantino was able to get a lot of big names attached to this film including Bruce Willis, John Travolta, Samuel L. Jackson and Uma Thurman. + +This movie is not just entertainment, it's art. If you don't have it, you should probably get is a.s.a.p. + +

2. Star Wars (all six of 'em)

+ +Star WarsStar Wars is the work of George Lucas. George has done many innovative things during the creation of his Star Wars movies. Not only was he the first to introduce (and claim the rights to) merchandising, he has also brought special effects to a new level. Of course, I don't have to remind you that George Lucas is also the genius behind the THX sound system. + +If I have to tell you what Star Wars is about, you should not be allowed to have a DVD collection at all. + +

1. The Godfather Trilogy

+ +The GodfatherI think everybody knows the Godfather. Directed by Francis Ford Coppola, this epic trilogy gives us an inside look in the Corleone family. + +Part one is really the best movie of all three. It's based on the original novel by Mario Puzo. However, parts two and three really complement the whole story and that's what makes this such a great trilogy. + +There are some very nice Trilogy DVD Boxes available. If you say you like movies, you cannot be taken seriously unless you have this trilogy in your collection. \ No newline at end of file diff --git a/content/posts/2006-10-13-google-ads-cant-click-em-but-you-want-to-know-more.md b/content/posts/2006-10-13-google-ads-cant-click-em-but-you-want-to-know-more.md new file mode 100644 index 0000000..5ed6540 --- /dev/null +++ b/content/posts/2006-10-13-google-ads-cant-click-em-but-you-want-to-know-more.md @@ -0,0 +1,18 @@ +--- +title: "Google Ads: Can’t click ‘em, but you want to know more!" +kind: article +slug: google-ads-cant-click-em-but-you-want-to-know-more +created_at: 2006-10-13 +tags: + - General + - Everything + - Blog + - Google + - AdSense + - Discussion +--- + +Do you have Google Ads on your site? You know you are not allowed to click 'em, because that's fraude. But what do you do when you see an ad on your site that really grabs your attention? Just google? Enter the URL manually? Or are you the kind of person that clicks that ad anyway. + +What would you do? Please let me know. + \ No newline at end of file diff --git a/content/posts/2006-10-13-having-fun-with-spam.md b/content/posts/2006-10-13-having-fun-with-spam.md new file mode 100644 index 0000000..57055d3 --- /dev/null +++ b/content/posts/2006-10-13-having-fun-with-spam.md @@ -0,0 +1,24 @@ +--- +title: "Having fun with SPAM!" +kind: article +slug: having-fun-with-spam +created_at: 2006-10-13 +tags: + - General + - Everything + - Blog + - Fun +--- + +I was just wading through my SPAM at Gmail to see if it flagged anything important. Then I came across this message: + +Is your website www.gmail.com offline, or why can't I find it on Yahoo? + +
Your website (www.gmail.com) on top positions on Google, Yahoo and MSN search! +We will get your website (www.gmail.com) to the top positions on all major search engines. + +Use our great value offer: +We will submit your website (www.gmail.com) to 890 Search Engines, including Google, Yahoo and MSN.
+ +Just keep an eye out for funny SPAM mails. Feel free to let me know if you ever read a funny or really stupid SPAM message. + \ No newline at end of file diff --git a/content/posts/2006-10-13-migrate-sqlite3-to-mysql-easily.md b/content/posts/2006-10-13-migrate-sqlite3-to-mysql-easily.md new file mode 100644 index 0000000..9433e50 --- /dev/null +++ b/content/posts/2006-10-13-migrate-sqlite3-to-mysql-easily.md @@ -0,0 +1,34 @@ +--- +title: "Migrate SQLite3 to MySQL easily" +kind: article +slug: migrate-sqlite3-to-mysql-easily +created_at: 2006-10-13 +tags: + - General + - Everything + - RubyOnRails + - Databases + - Features +--- +I've been using a simple Rails application locally with a SQlite 3 database for some time. Now I want to move to another host and use MySQL instead. But guess what? You can't just migrate your data! + +Here are some easy steps on how to migrate your data to MySQL. First of all you need to dump your SQLite3 database. This includes transaction statements and create commands. That's fine. Since we also migrate the schema information, our RoR app will not know any difference after we change config/database.yml. + +The biggest probem I encoutered was that the SQLite3 dump places table names in double quotes, which MySQL won't accept. +~ + +First, make sure you create your MySQL database and create a user to access that database. Then run the following command. (It's a long one, so where you see a \, just continue on the same line.) + + sqlite3 db/production.sqlite3 .dump | \ + grep -v "BEGIN TRANSACTION;" | \ + grep -v "COMMIT;" | \ + perl -pe 's/INSERT INTO \"(.*)\" VALUES/INSERT INTO `\1` VALUES/' | \ + mysql -u YOURUSERNAME -p YOURPROJECT_production[/source] + +This will take the SQLite 3 dump, remote the transaction commands. Next I use perl to replace all INSERT commands containing double quotes with something MySQL will understand. + +That's it. You MySQL database will be populated with your data. + +Don't forget to change your config/database.yml file after this! + +Note. You may also migrate your MySQL database using Rails. If you do this I recommend that you dump the SQLite3 database to a file first before you commit it directly to MySQL. You'll have to remove the CREATE TABLE statements as well as any reference to the schema_info table. diff --git a/content/posts/2006-10-13-tagging-in-ajax_scaffold.md b/content/posts/2006-10-13-tagging-in-ajax_scaffold.md new file mode 100644 index 0000000..d23d400 --- /dev/null +++ b/content/posts/2006-10-13-tagging-in-ajax_scaffold.md @@ -0,0 +1,70 @@ +--- +title: "Tagging in ajax_scaffold" +kind: article +slug: tagging-in-ajax_scaffold +created_at: 2006-10-13 +tags: + - General + - Everything + - Web Development + - RubyOnRails + - Features +--- +I've been using the Ajax Scaffold for quite some time now. It's a great piece of software by Mr. Richard White for Ruby on Rails. It seems that the plugin version of AS is getting quite a bit more attention than the generator. I started out with the generator but quickly reverted to the plugin since it's way more flexible and easier to use. + +Since I wanted to create a quick app to inventory my CD/DVD collection (which is now in a very sexy alu DJ case) I used Ajax Scaffold to get me started. In the spirit of Web 2.0 I wanted to add tags to every CD so it would be easier to find certain kinds of disks later on. So, I added acts_as_taggable. + +Acts_as_taggable basically allows you to tag any model in your app. So, I made my Disk model taggable. Great. Now I could do this: + + :::ruby + d = Disk.new(:number => 1, :name => "Mac OS X 10.4.6 Install DVD 1") + d.tag_with("macoxs apple macbook install") + d.save + +The real problem was, how to get this functionality easily integerated in Ajax Scaffold? +~ + +First of all I had to show a column in AS that included the tags attached to a given disk. I specify all rows manually in the Manager controller. Manager is scaffolded using AS. Here's what my Manager controller looks like: + + :::ruby + class ManagerController < ApplicationController + ajax_scaffold :disk + + @@scaffold_columns = [ + AjaxScaffold::ScaffoldColumn.new(Disk, { :name => "number" }), + AjaxScaffold::ScaffoldColumn.new(Disk, { :name => "name" }), + AjaxScaffold::ScaffoldColumn.new(Disk, { :name => "tags", + :eval => "row.tag_list", :sort => "tag_list"}), + ] + end + +This will show three columns, including a column named 'tags'. Every model that acts_as_taggable has some extra methods. tag_list is a single string containing all tags seperated by spaces. So, the tags column shows the tag_list for that row. With :sort I specify that AS can just sort keywords alphabetically. + +Great! I now can see tags on disks! But, we also need to add those tags and that's where it got tricky. + +

Adding tags

+ +Adding tags is not done by assignment but by calling a method with your tags, as shown before: tag_with (string). I could create a custom create and update method for the Disks, but there's a prettier solution available. + +tag_list returns a string with the current tags. How about using that same name to assign tags? It's rather easy. Here's my Disk model: + + :::ruby + class Disk < ActiveRecord::Base + acts_as_taggable + + validates_presence_of :name, :number + + def tag_list=(new_tags) + tag_with new_tags + end + end + +Now we can assign tags to tag_list as well as read the tags out. Now the only step is to add a special textfield to the form partial for AS. + + :::rhtml + + <%= text_field 'disk', 'tag_list' %> + +Now when a new disk is created or when one is updated, the tag_list will automagically be updated in a correct fashion. + +Enjoy! diff --git a/content/posts/2006-10-13-torrents-for-your-ipod.md b/content/posts/2006-10-13-torrents-for-your-ipod.md new file mode 100644 index 0000000..25a1697 --- /dev/null +++ b/content/posts/2006-10-13-torrents-for-your-ipod.md @@ -0,0 +1,20 @@ +--- +title: "Torrents for your iPod!" +kind: article +slug: torrents-for-your-ipod +created_at: 2006-10-13 +tags: + - General + - Everything + - Blog + - Links + - Apple + - iPod +--- + +I normally wouldn't promote torrent sites here, but when I came across Podtropolis I was amazed! + +This site features a lot of (video) iPod specific stuff like music videos, TV Shows and even full length movies. + +Of course, don't download any of the stuff! Buy it if you want it. + \ No newline at end of file diff --git a/content/posts/2006-10-17-happy-birthday-to-me.md b/content/posts/2006-10-17-happy-birthday-to-me.md new file mode 100644 index 0000000..5d96ef1 --- /dev/null +++ b/content/posts/2006-10-17-happy-birthday-to-me.md @@ -0,0 +1,15 @@ +--- +title: "Happy birthday to me!" +kind: article +slug: happy-birthday-to-me +created_at: 2006-10-17 +tags: + - General + - Everything + - Ariejan.net + - Blog +--- + +Today I'm turning 26. Yes, call me an old fart, but I still feel like 25! + +Anyway, I'm off celebrating with my friends. \ No newline at end of file diff --git a/content/posts/2006-10-23-ruby-on-rails-for-php-cakephp.md b/content/posts/2006-10-23-ruby-on-rails-for-php-cakephp.md new file mode 100644 index 0000000..d5aceff --- /dev/null +++ b/content/posts/2006-10-23-ruby-on-rails-for-php-cakephp.md @@ -0,0 +1,29 @@ +--- +title: "Ruby On Rails for PHP: CakePHP" +kind: article +slug: ruby-on-rails-for-php-cakephp +created_at: 2006-10-23 +tags: + - General + - Everything + - Blog + - Web Development + - RubyOnRails + - CakePHP +--- + +The framework has been around for some time, but I found out about it a few days ago: CakePHP. + +I've been using Ruby on Rails for quite some time now. It's a very cool framework and it's fun to work with. However, there's one big problem I have with it: I can't host it anywhere! + +Well, there are several hosting companies that offer Rails hosting, but the price is pretty steep when compared to my current hosting plan. I could get a VPS or dedicated server, but that just be overkill for the applications I want to run. + +So, I started thinking. What can be done in Ruby could be done in PHP as well. Maybe not in such an elegant and intuitive way, but it should be possible. And I was right. +~ +After some googling I came across CakePHP. CakePHP aims to implement the MVC (Model-View-Controller) structure in PHP much in the same way Rails does it for Ruby. + +After giving it a try I found that CakePHP is a pretty good alternative to Rails. Most importantly, it runs with both PHP4 and PHP5 and it can handle MySQL, SQLite and other standard databases. + +If you're an agile developer but you don't want to nail yourself down on hosting with Ruby or if you really want to use PHP, CakePHP is a great framework to use! + +http://www.cakephp.org diff --git a/content/posts/2006-10-29-do-your-ads-pay-your-blogging-bills.md b/content/posts/2006-10-29-do-your-ads-pay-your-blogging-bills.md new file mode 100644 index 0000000..8ad3ff7 --- /dev/null +++ b/content/posts/2006-10-29-do-your-ads-pay-your-blogging-bills.md @@ -0,0 +1,23 @@ +--- +title: "Do your ads pay your (blogging) bills?" +kind: article +slug: do-your-ads-pay-your-blogging-bills +created_at: 2006-10-29 +tags: + - General + - Everything + - Blog + - AdSense +--- +I've read many articles on earning money with Google's AdSense. Some guru's claim to recieve five figure checks from Google every month. + +The trick with these people is that they have a lot of content. More content, means more visitors, which means more clicks and thus more money. The content is written to attract certain keywords which are known earn a lot of money. Of course, professionals don't just have one site, they have several. In order to cope with all this content they have an almost full-time job. That's not bad if you earn a five figure amount every month. +~ +But, how about us, regular bloggers who just want to share our bit of knowledge with the rest of the world. Most of us have Google Ads on our site, including me. My site has been refurbished quite a few times in the past few months and the content I had is gone. So, I'm not really hot in this business. + +Most bloggers post a few items every few days. Some of us are really cool and post several items a day. What I want to know is: can Google Ads pay your (blogging) bills? + +I mean, is there really anyone, except the kick-ass professional bloggers, who can make a bit of money of their blog? And if so, do they have any tips they'd like to share with the world? + +Please let me know or post any relevant articles here. I'm looking forward to your reponse! + diff --git a/content/posts/2006-10-30-adsense-resource-inventory.md b/content/posts/2006-10-30-adsense-resource-inventory.md new file mode 100644 index 0000000..2f68575 --- /dev/null +++ b/content/posts/2006-10-30-adsense-resource-inventory.md @@ -0,0 +1,46 @@ +--- +title: "Adsense Resource Inventory" +kind: article +slug: adsense-resource-inventory +created_at: 2006-10-30 +tags: + - General + - Everything + - Blog + - Links + - AdSense +--- + +In addition to my previous post I have gathered some popular resources from around the web that talk about making money off Google Adsense. I hope this sums up all you need to start making money yourself. + +Please, let me know if I missed something. + +

Things to know about making money with Google Adsense

+ +There are three things you need in order to make money off your site or blog. + +
    +
  • Visitors
  • +
  • Content
  • +
  • Ads
  • +
+ +This post is about the last part, Ads. I'll talk about getting visitors and creating content in a later post, so stay tuned! +~ +So, how to get those ads setup right so you generate the most revenue possible. It's not as easy as it sounds and mostly it's a trial-and-error process. But, there are some general guidelines that will help you get started. + +I highly recommend you do NOT buy any software that claims to track Google Ads or create content or sites for you! If you need to track your ads, use Google Analytics, it's free. Content and site generators give you the same site and same content as others. Google knows all and they don't like copy-cats whos only goal is to make money. + +
    +
  1. Google Adsense Tips and Tools Collection
    This should be in your bookmarks! Great tips, tools and free software that will get you started immediately!
  2. +
  3. Adsense Calculator
    This nifty device tries to predict how much money you can make of your site according to your current statistics. Let me know if this actually works or not!
  4. +
  5. How to boost your Adsense revenue
    Great article that introduces new users to Adsense and gives tips on how to get started. See my remark about buying software, though.
  6. +
  7. 15 Common mistakes
    15 Common mistakes made by Adsense publishers. Read this, and don't make the same mistake.
  8. +
  9. SEOBook
    Great site. It contains many links and resources to Adsense related articles.
  10. +
  11. More Adsense Tips and Secrets
    Another site with great tips and Adsense secrets.
  12. +
  13. Adsense Wisdom
    10 Easy steps in making your Adsense experience successful.
  14. +
  15. Explorer Destroyer
    This site has some code that will kick Internet Explorer users to use Firefox instead. Oh, you use Google referrals for that.
  16. +
  17. Inside Adsense
    The official AdSense blog from Google.
  18. +
+ +Please post a comment if I have missed anything important. diff --git a/content/posts/2006-10-30-cheat-sheets-look-here.md b/content/posts/2006-10-30-cheat-sheets-look-here.md new file mode 100644 index 0000000..94df44d --- /dev/null +++ b/content/posts/2006-10-30-cheat-sheets-look-here.md @@ -0,0 +1,18 @@ +--- +title: "Cheat sheets? Look here!" +kind: article +slug: cheat-sheets-look-here +created_at: 2006-10-30 +tags: + - General + - Everything + - Blog + - Links +--- + +Getting confused of all the tools you use on a daily basis? Don't remember all the exact method names and possibilities of SubVersion, Apache, HTML, CSS, MySQL, Ruby on Rails, CVS, AJAX, JavaScript, FireFox, Google, etc. etc.? There is a solution! Cheat sheets make your daily work easier by providing you all you need to know on a single paper! + +Here's a very comprehensive list of cheat sheets. + +Cheat sheets are simple put a collection of all important (if not just all) functions and methods you can use for a given product, framework or language. They're great to have on your desk if you just can't quite remember what function to use. Check 'em out, print 'em out and start loving 'em! + \ No newline at end of file diff --git a/content/posts/2006-10-30-confused-about-css-columns.md b/content/posts/2006-10-30-confused-about-css-columns.md new file mode 100644 index 0000000..3754fc9 --- /dev/null +++ b/content/posts/2006-10-30-confused-about-css-columns.md @@ -0,0 +1,16 @@ +--- +title: "Confused about CSS Columns?" +kind: article +slug: confused-about-css-columns +created_at: 2006-10-30 +tags: + - General + - Everything + - Blog + - Web Development +--- + +I'm mainly confused on how to make different layouts with pure CSS. Back in the time when tables were okay, it was rather easy. But with CSS it's gotten rather tricky. + +Dynamic Drive has some great examples (with CSS code!) of different kinds of layouts. It's great to use as a starting point for your design! + \ No newline at end of file diff --git a/content/posts/2006-10-30-how-does-your-site-look-on.md b/content/posts/2006-10-30-how-does-your-site-look-on.md new file mode 100644 index 0000000..1ddbb95 --- /dev/null +++ b/content/posts/2006-10-30-how-does-your-site-look-on.md @@ -0,0 +1,20 @@ +--- +title: "How does your site look on …?" +kind: article +slug: how-does-your-site-look-on +created_at: 2006-10-30 +tags: + - General + - Everything + - Blog + - Web Development + - Links +--- + +When designing a web site you always check how the site looks in different browsers, even maybe at different resolutions. But how many browsers do you really use? Firefox, Safari when on Mac, Internet Explorer when on Windows and that's mostly it. + +Browser Shots allows you to select different browsers and set several options like colour depth, screen resolution, java etc. and make screenshots of your site (by entering the URL). + +I don't need to tell you this is a great resource. It's great to check if your design works and it's even better to get a screenshot generated from your new designs. Only problem is that you'll have to put them online first. + +Check out some screenshots here. \ No newline at end of file diff --git a/content/posts/2006-10-31-wordpressmu-dont-allow-new-blogs.md b/content/posts/2006-10-31-wordpressmu-dont-allow-new-blogs.md new file mode 100644 index 0000000..a63d020 --- /dev/null +++ b/content/posts/2006-10-31-wordpressmu-dont-allow-new-blogs.md @@ -0,0 +1,28 @@ +--- +title: "WordpressMu: Don’t allow new blogs" +kind: article +slug: wordpressmu-dont-allow-new-blogs +created_at: 2006-10-31 +tags: + - General + - Everything + - Features + - WordPressMu +--- +If you're using WordpressMu, the blog hosting tool used on Wordpress.com, you may want to disable the creation of blogs by your visitors. + +Whatever your reasons for this are, I wanted to prevent this, because I (and my team of editors) want to maintain several blogs on different topics. Users are free to register and post comments, but creating new blogs is reserved for the administrator. + +So, how do you implement this in WordpressMu? There is no checkbox (yet) that disables this feature. So, I had to hack the WordpressMu code a bit. +~ +First, open up wp-signup.php. If you access a blog that does not exist, you'll be redirected to the signup page and be presented a signup form for that particular blog. + +Open up wp-singup.php en just above the get_header(); call, place the following code: + + :::php + if (!is_user_logged_in() || $user_identity != 'admin') { + header("Location: http://example.com/gofishatthispage/"); + exit(); + } + +What this does is make sure that only a logged in user named 'admin' is allowed to proceed to the blog creation form. Others will be redirected to a location of your choice. A good idea is to send people to a page that explains why they can't' create a blog or what they have to do to get an administrator to create one for them. diff --git a/content/posts/2006-11-13-cups-426-upgrade-required.md b/content/posts/2006-11-13-cups-426-upgrade-required.md new file mode 100644 index 0000000..bf7776d --- /dev/null +++ b/content/posts/2006-11-13-cups-426-upgrade-required.md @@ -0,0 +1,29 @@ +--- +title: "CUPS: 426 - Upgrade Required" +kind: article +slug: cups-426-upgrade-required +created_at: 2006-11-13 +tags: + - General + - Everything + - Features + - Linux + - Ubuntu +--- + +As I was installing my printer on my Ubuntu 6.06 Dapper LTS server with CUPS I noticed the following error: + +**426 Upgrade Required** + +After some research I came to the conclusion that CUPS, by default, tries to use SSL whenever possible. So, with this 426 error, you are redirected to the SSL domain. Chances are, you haven't configured SSL properly, if at all. + +In my case, I didn't want to configure SSL. To get rid of this problem, the key lies in editing your configuration files ( /etc/cups/cupsd.conf ) and adding the following line: + +
DefaultEncryption Never
+ +There are several options, Never, IfRequired and Required. By setting this to Never, SSL will never be enforced. Just restart your CUPS server with + + $ /etc/init.d/cupsys restart + +and you're good to go. + diff --git a/content/posts/2006-11-15-ubuntu-610-live-dvd-on-the-apple-macbook.md b/content/posts/2006-11-15-ubuntu-610-live-dvd-on-the-apple-macbook.md new file mode 100644 index 0000000..df0662d --- /dev/null +++ b/content/posts/2006-11-15-ubuntu-610-live-dvd-on-the-apple-macbook.md @@ -0,0 +1,78 @@ +--- +title: "Ubuntu 6.10 Live DVD on the Apple MacBook" +kind: article +slug: ubuntu-610-live-dvd-on-the-apple-macbook +created_at: 2006-11-15 +tags: + - General + - Everything + - Blog + - Linux + - Ubuntu +--- + +Since I teach various Linux courses at Fontys Centrum IT, I want to run a live CD or DVD on my MacBook. First off, here are my specs: + +
    +
  • Apple MacBook (White)
  • +
  • Intel Core Duo 2.0Ghz
  • +
  • 1.0Gb RAM
  • +
  • 13.3" 1280x800 TFT
  • +
  • 60 Gb Internal Harddisk
  • +
  • 300 Gb External FireWire harddisk
  • +
+ +As my Live medium I chose the Ubuntu Linux 6.10 Live DVD. This DVD has several nice options (install server, for example) that I like. It also can boot up in a live desktop environment. +~ +As a matter of fact, everything I need works out of the box. Wireless works fine, my mouse (USB Logitech) works, the FireWire harddisk (which has a FAT32 filesystem, so I can hook it to my girl friend's Windows PC) works perfectly. + +There are only two things that didn't work. + +1. Xvid codecs are missing, so I can't watch my Prison Break episodes from the Live environment, but that's perfectly fine. That's not what I need the live environment for. + +2. The screen resolution can only be set to a maximum of 1024x768. As I mentioned before, the TFT has a native resolution of 1280x800. Because of the scaling, my screen doesn't look as sharp and sexy as it should. + +In order to solve this problem I had to take a few, rather easy, steps. + +To get started, let us assign a password to the default ubuntu user. + + sudo passwd ubuntu + +Now enter something that you'll remember easily, twice. + +In order to get Ubuntu to recognize the native screen resolution automatically, we'll need a special package: 915resolution. Since the MacBook has an Intel 945 graphics chipset, we can use this tool. But, this tool is not in the default package respository. It's in the Universe. + +So, we now need to change /etc/apt/sources.list and add the universe repository. This is rather easy, because these repositories already exist, but are commented out. Just open up /etc/apt/sources.list and uncomment the two universe lines. Make sure your sources.list looks like this: + + deb http://archive.ubuntu.com/ubuntu edgy main restricted + deb-src http://archive.ubuntu.com/ubuntu edgy main restricted + + ## Uncomment the following two lines to add software from the 'universe' + ## repository. + ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu + ## team, and may not be under a free licence. Please satisfy yourself as to + ## your rights to use the software. Also, please note that software in + ## universe WILL NOT receive any review or updates from the Ubuntu security + ## team. + deb http://archive.ubuntu.com/ubuntu edgy universe + deb-src http://archive.ubuntu.com/ubuntu edgy universe + + deb http://security.ubuntu.com/ubuntu edgy-security main restricted + deb-src http://security.ubuntu.com/ubuntu edgy-security main restricted + +Now, we can update our system and install the 915resolution package. + + apt-get update + apt-get install 915resolution + +You'll notice that 915resolution spits out a lot of information on your chipset and native resolution. You should check here that the 1280x800 resolution has been detected. + +Now you're all set. Well, you'll need to restart the X Server in order for it to recognize the newly discovered resolution. I basically pressed CTRL-ALT-BACKSPACE to kick the X Server. I'm presented with a login screen. Here I login as 'ubuntu' with the password I specified earlier. + +If everything went fine, you'll now get a clear and sharp display of your Ubuntu life desktop. + +I also turned on sub-pixel smoothing in System -> Preferences -> Fonts to get some sexier and smoother looking fonts. + +That's all. I can now boot-up the Ubuntu Live DVD and get the native resolution for my MacBook. + +For those who want to keep the native resolution on their installed Ubuntu, just install the 915resolution package mentioned above and you're set. Easy as that. diff --git a/content/posts/2006-11-17-announcing-cse-tool-deploy-you-google-cse-with-ease.md b/content/posts/2006-11-17-announcing-cse-tool-deploy-you-google-cse-with-ease.md new file mode 100644 index 0000000..a97ad3b --- /dev/null +++ b/content/posts/2006-11-17-announcing-cse-tool-deploy-you-google-cse-with-ease.md @@ -0,0 +1,17 @@ +--- +title: "Announcing CSE-Tool: Deploy you Google CSE with ease" +kind: article +slug: announcing-cse-tool-deploy-you-google-cse-with-ease +created_at: 2006-11-17 +tags: + - General + - Everything + - Blog + - CSE-Tool +--- + +This is just a short note to let you know that CSE-Tool 1.0 has been released. + +The CSE-Tool allows you to easily (just copy-paste a few items presented to you by Google) deploy your Custom Search Engine. All you need is a CSE (of course) and optionally a Google Analytics account to track who's coming by. + +System requirements are pretty low. You'll only need PHP 4.3.x or better. Check out the SourceForge Project Page or jump directly to the downloads section. \ No newline at end of file diff --git a/content/posts/2006-11-21-cse-tool-110-released.md b/content/posts/2006-11-21-cse-tool-110-released.md new file mode 100644 index 0000000..b8a9435 --- /dev/null +++ b/content/posts/2006-11-21-cse-tool-110-released.md @@ -0,0 +1,23 @@ +--- +title: "CSE-Tool 1.1.0 Released" +kind: article +slug: cse-tool-110-released +created_at: 2006-11-21 +tags: + - General + - Everything + - Blog + - CSE-Tool +--- + +Hot of the press! CSE-Tool 1.1.0 has been released just a few minutes ago! Grab the code now or check a live demonstration! + +Please report any feature, support or bug requests back at the project's SourceForge page. + +Also, if you like CSE-Tool (and want your feature added sooner rather than later) please consider making a small donation to the project to encourage further development and support. + +Donations are handled by SourceForge. + +Please consider making a donation to the project. Thank you! + +To see how I manage a the creation of a release through SubVersion, check this post over here. \ No newline at end of file diff --git a/content/posts/2006-11-21-svn-how-to-release-software-properly.md b/content/posts/2006-11-21-svn-how-to-release-software-properly.md new file mode 100644 index 0000000..abeeca1 --- /dev/null +++ b/content/posts/2006-11-21-svn-how-to-release-software-properly.md @@ -0,0 +1,66 @@ +--- +title: "SVN: How to release software properly" +kind: article +slug: svn-how-to-release-software-properly +created_at: 2006-11-21 +tags: + - General + - Everything + - Web Development + - Features + - Subversion +--- +Many projects use SubVersion nowadays to store their project code. I do this also at work, and for my personal projects like CSE-Tool. + +The question, however, is how to release your current code properly to the public. You probably don't want your users to check out your current development code. Either you want them to check out a certain version (release) or you want to present them with a download archive containing the code. + +I'm going to show you how to release a simple PHP application from SubVersion as an archive file to my users. +~ +The base layout of my svn repository is like this. I have directory named 'trunk' that always contains the most recent version of the software. This is the development branch, so to say. I also have a 'branches' and a 'tags' directory. If you don't have these, you'll need to create them now: + + $ svn mkdir -m "Creating branches directory" svn://yourrepository/branches + Commited revision 123. + $ svn mkdir -m "Creating tags directory" svn://yourrepository/tags + Commited revision 124. + +In this case my current development code, in the trunk of the svn repository is at revision 10. All files in the trunk are marked to be develoment quality. This means that I don't display version numbers, but simply show 'HEAD' to indicate you're working with a development quality product. Before I release this code to the public, I want to tweak a few things. Since this is not general development, I create a Release Branch. This release branch is basically a copy of the current code in the trunk. Changes to that branch are stored seperately from the development code, so I can easily tweak it to release quality. + +Creating the release branch is really easy. Let's say I want to release version 1.1.0 of CSE-Tool. I just copy the code in subversion. Note that I don't download and store the code on my local machine. SubVersion is smart and duplicates the code on the server. This can save you valueable bandwidth, especially when you're working on a large project. + +Well, create the Release Branch which is named, by convention, RB-1.1.0. + + $ svn copy -m "Creating release branch 1.1.0" https://svn.sourceforge.net/svnroot/cse-tool/trunk https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.1.0 + Committed revision 11. + +You can see at http://cse-tool.svn.sourceforge.net/viewvc/cse-tool/branches/ that the new release branch (RB-1.1.0) was created as a directory containing a copy of the current develoment code. + +I can now do two things. Either I checkout a seperate working copy of the release branch or I change my current working copy (which is using the trunk) to use the release branch. Checking out is easy, but I'll show it first to you anyway. Next I'll show you how to switch your repository. + +Just check out your code as you'd normally do, but make sure you specify the release branch. I've also specified to store this code in a directory named cse-tool-1.1.0 so I don't confuse it with the trunk code, which is stored in a directory named 'cse-tool'. + + $ svn co https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.0.0 cse-tool-1.1.0 + +I could also swich my current working copy to the release branch. This may be useful if your project is very huge and you don't want to download the whole thing again. Switching between a release branch and the trunk is usually more efficient because you only need to download the differences between the two. + + $ svn switch https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.0.0 + +Okay, now I can work on the release branch. Branding it with the right version number among other things. In your case it might be a good place to two SQL files to install or update a database you are using. You might want to update the changelog and other documentation. + +When you commit changes, they will be applied to the release branch only, not not to the current development code. + +When you're done you may switch back to the current development code: + + $ svn switch https://svn.sourceforge.net/svnroot/cse-tool/trunk + +The code in the release branch is now ready to be shipped out. We want to mark this code as being Relese 1.1.0. This is called tagging. A tag is nothing more than a copy of the repository on a give moment. Technically, a branch and tag are the same. However, the conventions I use dictate that you don't change the code in a tag because it represents a certain state of your code, in this case the state the code was in at the time of Release 1.1.0. + +Now, to actually create a release tag, named REL-1.1.0, we use the same procedure as with the creation of the release branch. Just note the differences in the source and destination repositories. + + $ svn copy -m "Tag release 1.1.0" https://svn.sourceforge.net/svnroot/cse-tool/branches/RB-1.1.0 https://svn.sourceforge.net/svnroot/cse-tool/tags/REL-1.1.0 + Committed revision 13. + +With the REL-1.1.0 tag we can create an archive that we can distribute to our users. Because we don't want to include svn metadata in our release we can't use checkout for this. SubVersion allows us to export our code, which is basically a check out, but without all the svn metadata. This is ideal to ship to our customers. + + $ svn export https://svn.sourceforge.net/svnroot/cse-tool/tags/REL-1.1.0 cse-tool-1.1.0 + +Next I can tar up the cse-tool-1.1.0 directory and put the files on SourceForge. (Download them here :)) diff --git a/content/posts/2006-11-22-google-project-hosting-sourceforge-competitor.md b/content/posts/2006-11-22-google-project-hosting-sourceforge-competitor.md new file mode 100644 index 0000000..efc684d --- /dev/null +++ b/content/posts/2006-11-22-google-project-hosting-sourceforge-competitor.md @@ -0,0 +1,21 @@ +--- +title: "Google Project Hosting: SourceForge Competitor" +kind: article +slug: google-project-hosting-sourceforge-competitor +created_at: 2006-11-22 +tags: + - General + - Everything + - Blog + - Web Development + - Links + - Google +--- + +I just found out that Google Code is now offering Project Hosting! It's in the same style as SourceForge, but the Google way! + +As a Google user you can create a project with a built-in issue tracker and Subversion repository. And if that's not all, you can tie in a blog (at Blogger.com is you like) and adiscussion group (on Google Groups, of course). + +This is really a nice package Google is offering and it looks like a serious competitor for SourceForge. + +It's just that I didn't find any possibility to release files yet, but other than that, I'd like to move there! \ No newline at end of file diff --git a/content/posts/2006-11-22-plugins-used-on-ariejannet.md b/content/posts/2006-11-22-plugins-used-on-ariejannet.md new file mode 100644 index 0000000..8c2ad6a --- /dev/null +++ b/content/posts/2006-11-22-plugins-used-on-ariejannet.md @@ -0,0 +1,52 @@ +--- +title: "Plugins used on Ariejan.net" +kind: article +slug: plugins-used-on-ariejannet +created_at: 2006-11-22 +tags: + - General + - Everything + - Wordpress + - Ariejan.net + - Blog +--- + +After I released iAriejan I got some questions about what plugins I run on Ariejan.net. So, upon your request, here is the full listing. + +I've included links to all the sites where you can download the plugins. + +Note that iAriejan comes bundeled with WP Wetfloor and AJAX Comments. +~ +
    +
  • AJAX Comments +This plugin allows the instant posting of plugins with a nice and sexy AJAX effect. Very Web 2.0ish.
  • + +
  • Askimet
    +Allows the screening of comments for SPAM. This is great in combination with the AJAX Comments!
  • + +
  • Mighty Adsense
    +Places Google Ads on my blog (without editing the theme) in order to cover my hosting expenses.
  • + +
  • Adsense Target
    +AdSense Target marks the important stuff so Google Ads match even better with the content on Ariejan.net. This is called Ad Targetting and supported by Google. Invisible to you, but still very nice!
  • + +
  • FeedBurner Widget
    +Allows me to publish my RSS feed through FeedBurner. This is great for the management of the feed and usage (not user!) tracking.
  • + +
  • Google Analytics
    +This plugin makes use of Google Analytics' ability of tracking visitors on your site. This way I can see how people visiting Ariejan.net interact with my site and which parts need improvement.
  • + +
  • iG:Syntax Hiliter
    +This plugin allows me to easily post different kinds of code like Ruby, PHP and C#. It will show the code properly formatted, syntax highlighting is included and it will allow you to copy it all as plain text! Want a demonstration?
  • + +
  • Sidebar Widgets
    +Actually, I don't use widgets right now. I use a static sidebar. I plan on using it some time. I have it installed for the FeedBurner widget to do its work.
  • + +
  • WP Wetfloor
    +This is a brilliant plugin. It uses Javascript to create reflections of images. It uses the current background colour as a basis and is fully configurable by adding a few CSS classes. Of course, this degrades very nicely on non-javascript clients. You'll just have to miss the eye-candy.
  • + +
+ +Well that's all there is. If you can think of any plugin that I should use (but am currently not using), feel free to drop a comment. + +Edit: I completely forgot to mention AJAX Comments. It has been added to the list. diff --git a/content/posts/2006-11-22-svn-how-to-fix-bugs-properly.md b/content/posts/2006-11-22-svn-how-to-fix-bugs-properly.md new file mode 100644 index 0000000..1e2c7f2 --- /dev/null +++ b/content/posts/2006-11-22-svn-how-to-fix-bugs-properly.md @@ -0,0 +1,84 @@ +--- +title: "SVN: How to fix bugs properly" +kind: article +slug: svn-how-to-fix-bugs-properly +created_at: 2006-11-22 +tags: + - General + - Everything + - Web Development + - Features + - Subversion +--- +I've already told you about releasing your project with help from Subversion. Now I want to talk to you about using Subversion to fix bugs in your application. + +Fixing bugs can be as easy as fixing a few lines of code or as hard as rewriting a significant portion of your application. Both situations need a different approach from us. Let's talk about the easy stuff first. + +For this example let's say we have a project. It has a release branch named RB-1.0 and current development is going on in the trunk. + +A user has submitted a bug report (numbered #3391) against your 1.0 release. Here's what to do: +~ +### Easy bug fixes + +Let's say bug #3391 is an easy fix. First, check out a working copy of the release branch. + + $ svn co https://example.com/branches/RB-1.0 rb-1.0 + +Now, go in there, write tests that expose the bug and fix it. As I said, it's an easy fix so you can commit all your changes at once to the release branch. When you do this, remember the new revision number. + +**Note:** it's always smart to include the number of the bug (in this case #3391) in your commit message. This will make sure other developers (and later on, yourself) know what bug was fixed here. + + $ svn commit -m "Bug fixed #3391" + ... + Committed revision 183. + +As I said, remember the revision number: 183. + +Now you have the bug fixed in the release branch for version 1.0, you are probably wondering if the bug exists in your current development code as well. Since branches are plain copies of your development code, the big probably is there too. + +Don't start editing your working copy of the trunk and start fixing the bug all over again. Let Subversion do the work. + +Go into your trunk working copy and update it to the latest revision, this is revision 183. But, we only made changes to the release branch, and not to the trunk, so we need to merge those changes. We can do this by running the following command: + + $ svn merge -r182:183 https://example.com/branches/RB-1.0 rb-1.0 + +You'll now see the fix you applied in the release branch getting merged with your current development code. Great, isn't it? + +Before you leave to party, don't forget to commit the changes to the trunk. Again, name the bug number you fixed here and also which revision you userd to merge it. + + $ svn commit -m "Merge r183 (bug fixed #3391)" + +You can apply this merge process with any other release branch you have if that' necessary. + +### Difficult bug fixes + +Another scenario is that the bug is not easy to fix. You need to rewrite a lot of tests to expose the bug in the first place, and even then you're not sure how to go about fixing it. + +If that's the case, you are better of creating a seperate bug fix branch. This allows you to fix the bug in the background, possibly with the help of others, while current development can still go on. Also, you want to create snapshots of before (pre) and after (post) the bug fix. These tags will help you merge the fix into other branches later on. + +First, create a bug fix branc. By convention Release Branches were called RB-1.0, Bug fix branches are called BUG-###. Of course, ### corresponds to the bug report number. In this case we create a branch named BUG-3391. We also need to create a snapshot of the code before we start fixing the bug. We call this tag PRE-3391. + + $ svn copy -m "Create bugfix branch" https://example.com/branches/RB-1.0 https://example.com/branches/BUG-3391 + $ svn copy -m "Tag start of bug fix" https://example.com/branches/BUG-3391 https://example.com/tags/PRE-3391 + +Now, you can checkout the bug fix branch and start work. You may call in the help of others if you need to. It's okay to make multiple commits to this branch. + +When you have reached the point were the bug is fixed, you'll need to mark the end of it. We create a new tag named POST-3391 to mark the end of the bugfix: + + $ svn copy -m "Tag end of bug fix" https://example.com/branches/BUG-3391 https://example.com/tags/POST-3391 + +Well. You're done! Your bug has been fixed! But wait a minute. The fix is not present in the release branch yet! Here, again, we need to merge the bug fix into the release branch (and possibly into the trunk also). + +First, update your current working of the release branch and merge the changes between the PRE-3391 and POST-3391 tags with the release branch. When done, run your tests to make sure everything works as expected and commit your changes. + + $ svn update + $ svn merge https://example.com/tags/PRE-3391 https://example.com/tags/POST-3391 + $ svn commit -m "Merged bug fix for bug #3391" + +### Final note + +It's clear that fixing bugs the easy way is the preferred method. Try to use this method as much as possible. But it's okay to use the difficult way to solve the more difficult bugs. + +Putting the bug report number into Subversion comments is a good way to keep track of what code changes have been made based on what bug report. Most bug tracking tools can make use of this and link bug reports to the appropriate revisitons and visa-versa. If your bug tracking tool doesn't have this ability, it's a good idea to comment the appropriate revision number or tags on the bug so that others know where to look for the fix. + +Happy bug fixing! diff --git a/content/posts/2006-11-22-wordpress-author-comment-highlighting.md b/content/posts/2006-11-22-wordpress-author-comment-highlighting.md new file mode 100644 index 0000000..d599997 --- /dev/null +++ b/content/posts/2006-11-22-wordpress-author-comment-highlighting.md @@ -0,0 +1,40 @@ +--- +title: "WordPress: Author comment highlighting" +kind: article +slug: wordpress-author-comment-highlighting +created_at: 2006-11-22 +tags: + - General + - Everything + - Wordpress + - Web Development + - Features +--- +I've seen it lots of times before, but I just added it to Ariejan.net (and the next release of the iAriejan theme). Sometimes there are lots of comments and it's nice for visitors to see what the official reaction of the blog author is. + +Since I haven't really looked into a plugin or anything, this is just a very simple theme hack. + +You can apply it to your current theme with almost no effort at all. +~ +Open up your comments.php file in your themes directory. And look for the following code: + + :::php +
  • + +and replace it with + + :::php +
  • + +What this will do is match the e-mail address of the poster with the e-mail address of the post author. This is in some way spoofable, as users may be able to post a comment with your e-mail address on it. + +If you posted the comment an extra CSS class named 'authorcomment' is added. So add the following to your style.css file. (You may change this to suit your own taste of course): + + :::css + .authorcomment { + background-color: #363636; + border: 1px solid #969696; + } + +To prevent this you can add your e-mail address (the one you use with your WP account) to Options -> Discussion -> Comment Moderation. This will keep any post that contains your email address back for moderation by you. This is the only fool-proof method I know right now to keep people from spoofing. There might be some other hacks for this, but I haven't had time to think about that yet. + diff --git a/content/posts/2006-11-24-svn-how-to-structure-your-repository.md b/content/posts/2006-11-24-svn-how-to-structure-your-repository.md new file mode 100644 index 0000000..3976714 --- /dev/null +++ b/content/posts/2006-11-24-svn-how-to-structure-your-repository.md @@ -0,0 +1,104 @@ +--- +title: "SVN: How to structure your repository" +kind: article +slug: svn-how-to-structure-your-repository +created_at: 2006-11-24 +tags: + - General + - Everything + - Features + - Subversion +--- +_You are reading an article about Subversion. That's great, because it +means you're thinking about the benefits of version control for your +project. However, I have long since moved from Subversion to Git. I +strongly recommend you read up on my Best +Practice - The Git Developmenet Cycle. Git is much faster an +flexible than Subversion, go check it out now!_ + +Most people know what Subversion is and that there's something called "The Trunk" with code in it. Well, there's more to your SubVersion repository than you think! This article will discuss how to structure your repository in order for you to take full advantage of Subversion's possibilities. + +As you may have read in my previous Subversion articles the base of your Subversion repository are three directories: branches, tags and trunk. + +Each directory in subversion can be checked out seperately. See the examples for more information. +~ +### Trunk + +The trunk contains the most current development code at all times. This is where you work up to your next major release of code. + +I see, almost too often, that people only use the trunk to store their code. Releases are pulled from a certain revision and then development continues. This is no good. Not for you, not for your product. + +The trunk should only be used to develop code that will be your next major release. Don't brand the trunk with version numbers or release names. Just keep the trunk in "development mode" at all times. + +Example: + + https://svn.example.com/svnroot/project/trunk + +### Branches + +There are different types of branches. I'll tell you about the most common ones here. + +With the branches directory you can create paths for you code to travel to more specific goals like an upcoming release. As I discussed in my article on releasing software from Subversion the branches directory contains copies of your trunk at various stages of development. + +#### Release Branches + +We have already seen the RB-x.x release branches. When the trunk reaches the stage that it's ready to be released (or when you want to freeze the addition of new features) you create a release branch. This release branch is just a copy of your current trunk code. + +The branch can be checked out seperately and you can start branding and versioning the project. You can also use the release branch to fix bugs that pop up during (beta) testing (See my article on that too). The idea of this is to keep development in the trunk going without being bothered by release specific stuff. So it's perfectly fine to add new features to your trunk while you (or others) prepare the release. + +Of course, you can address a release branch directly to check it out: + + https://svn.example.com/svnroot/project/branches/RB-1.0 + +#### Bug fix branches + +Branches may also be used to address the more serious bugs found in the trunk or a release branch. The bugs are of such a magnitute that you can't fix them by yourself in a single commit. So, in order to focus on the problem of fixing this bug you should create a new branch for this purpose. This allows development in the trunk or your release branch to continue, and you won't disturb them with new bugs or tests that break the current code. + +Bug fix branches are named after the ID they are assigned in your bugtracking tool. Mostly this is a number: BUG-3391 + +Of course, you can access your bugfix branch like any other. + + https://svn.example.com/svnroot/project/branches/BUG-3391 + +Read my how to fix bugs properly article for more specific bug fixing information. Also read on in this article to the tags section. + +#### Experimental branches + +Something that also happens a lot is the introduction of new technologies. This is fine, of course, but you don't want to bet your entire project on it. + +Imagine that you want to change from PHP 4 to PHP 5 for your web application. How long would it take you to convert your entire project? Do you want your entire code base (trunk) to be useless until you have converted all of your code? Probably not! + +These experiments, maybe PHP 5 is a bridge too far for your app, should be given their own branch. You can hack your way to PHP 5-ism there and if you fail, you still have your current PHP 4 code in the branch. + +Experimental branches may be abandonned when the experiment fails. If they succeed you can easily merge that branch with the trunk and deliver your big new technology. These branches are named after what you're experimenting with. I always prefix them with 'TRY-': + + https://svn.example.com/svnroot/project/branches/TRY-new-technology + +### Tags + +Tags are, like branches, copies of your code. Tags, however, are not to be used for active development. They mark (tag) a certain state your code is in. + +#### Release tags + +Release tags mark the release (and state) of your code at that release point. Release tags are always copies of the corresponding release branch. Release tags are prefixed with 'REL-' followed by a version number. + +You can access these tags easily: + + https://svn.example.com/svnroot/project/tags/REL-1.0.0 + +See my article on releasing software for more information. + +#### Bug fix PRE and POST tags + +When you have created a bug fix branch, you want to mark (tag) the situation of your code before and after the bugfix. This allows you to easily refer to the changes you made when you want to merge them back to your trunk or release branch. + +The start-tag is called 'PRE' and the end-tag called 'POST'. Of course, you should add the bug ID number here too to show what bug you are tagging here. + +You probably don't check out bug fix tags, but you want to reference them when merging bug fixes with your other code: + + https://svn.example.com/svnroot/project/tags/PRE-3391 + https://svn.example.com/svnroot/project/tags/POST-3391 + +Read more on fixing bugs wiht subversion in my other article. + diff --git a/content/posts/2006-11-25-the-three-corner-stones-of-developerhood.md b/content/posts/2006-11-25-the-three-corner-stones-of-developerhood.md new file mode 100644 index 0000000..83e8656 --- /dev/null +++ b/content/posts/2006-11-25-the-three-corner-stones-of-developerhood.md @@ -0,0 +1,36 @@ +--- +title: "The Three Corner Stones of Developerhood" +kind: article +slug: the-three-corner-stones-of-developerhood +created_at: 2006-11-25 +tags: + - General + - Everything + - Blog + - Web Development + - Lists +--- + +As a developer, there are just some things you can't do without. I have found that there are three things that I need. Really need. If one of them is missing, I have trouble doing my job properly. + +I call them "The Three Corner stones of Developerhood": +~ +

    1. Coffee

    + +Most importantly coffee. I just can't do without it. I don't drink it to stay awake or anything, but it's just a warm beverage that keeps me focused and gives me the energy I need to keep working. I prefer coffee over all those horrible caffeine drinks. + +

    2. A good text editor

    + +No matter how good Integrated Developement Environments are, I always prefer my trusty TextMate to get things done for me. + +Syntax highlighting and the huge amount of macro's is just a pleasure to work with. I support PHP, HTML, CSS and most importantly, Ruby on Rails. + +If you have a Mac. Buy this now! + +

    3. Google

    + +I can't tell you how many problems I've solved by using Google to find the answer. + +Of course, you can just insert any search engine here, but Google is really my favourite. With Google Groups I can find stuff in news groups easily, and as a developer, news groups are a very valuable resource of information. + +It's very well possible that you have three other corner stones. Feel free to comment them below and let me know. diff --git a/content/posts/2006-11-30-rails-security-check-up.md b/content/posts/2006-11-30-rails-security-check-up.md new file mode 100644 index 0000000..2143ecb --- /dev/null +++ b/content/posts/2006-11-30-rails-security-check-up.md @@ -0,0 +1,17 @@ +--- +title: "Rails: Security Check-up" +kind: article +slug: rails-security-check-up +created_at: 2006-11-30 +tags: + - General + - Everything + - RubyOnRails + - Blogroll +--- + +Is your Rails app secure? Really? Maybe you need to perform a major check-up of your Rails application to make sure. + +Here's a comprehensive list that will take you through the most common mistakes and forgotten security risks in your Rails application. + +It's a great post. Print it, hang it on your wall and create secure Rails apps from now on. \ No newline at end of file diff --git a/content/posts/2006-12-01-how-to-setup-a-ubuntu-development-server-part-1.md b/content/posts/2006-12-01-how-to-setup-a-ubuntu-development-server-part-1.md new file mode 100644 index 0000000..afde7bc --- /dev/null +++ b/content/posts/2006-12-01-how-to-setup-a-ubuntu-development-server-part-1.md @@ -0,0 +1,201 @@ +--- +title: "How to setup a Ubuntu development server - Part 1" +kind: article +slug: how-to-setup-a-ubuntu-development-server-part-1 +created_at: 2006-12-01 +tags: + - General + - Everything + - Web Development + - Features + - Ubuntu + - Subversion +--- +Since I'm starting some real work on my final school project, I want to install a Ubuntu development server here at home. I have a Pentium 4 box here that will perform that task. + +In this first part I will show you how to install Subversion over WebDAV. All of this will be done in such a way that it's easy to serve multiple projects at once. + +In future parts I will tell you more about installing Trac, FastCGI (with Apache) to host Rails applications and how to use Capistrano to deploy your app properly. + +For now, let's get cracking at Subversion. +~ +First off, I installed Ubuntu 6.10 on my server. Because I don't need a graphical user interface, I have installed Ubuntu in text-only mode. + +

    Open up to the universe

    + +The first thing I always do when I install a Ubuntu box is to enable the universe package repositories. This is rather easy. + +Edit /etc/apt/sources.list and uncomment all the Universe related lines. Also, comment out your install disk. Here's what my /etc/apt/sources.list looks like: + + # deb cdrom:[Ubuntu 6.10 _Edgy Eft_ - Release i386 (20061025)]/ edgy main restricted + + deb http://nl.archive.ubuntu.com/ubuntu/ edgy main restricted + deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy main restricted + + ## Major bug fix updates produced after the final release of the + ## distribution. + deb http://nl.archive.ubuntu.com/ubuntu/ edgy-updates main restricted + deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy-updates main restricted + + ## Uncomment the following two lines to add software from the 'universe' + ## repository. + ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu + ## team, and may not be under a free licence. Please satisfy yourself as to + ## your rights to use the software. Also, please note that software in + ## universe WILL NOT receive any review or updates from the Ubuntu security + ## team. + deb http://nl.archive.ubuntu.com/ubuntu/ edgy universe + deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy universe + + ## Uncomment the following two lines to add software from the 'backports' + ## repository. + ## N.B. software from this repository may not have been tested as + ## extensively as that contained in the main release, although it includes + ## newer versions of some applications which may provide useful features. + ## Also, please note that software in backports WILL NOT receive any review + ## or updates from the Ubuntu security team. + # deb http://nl.archive.ubuntu.com/ubuntu/ edgy-backports main restricted universe multiverse + # deb-src http://nl.archive.ubuntu.com/ubuntu/ edgy-backports main restricted universe multiverse + + + deb http://security.ubuntu.com/ubuntu edgy-security main restricted + deb-src http://security.ubuntu.com/ubuntu edgy-security main restricted + deb http://security.ubuntu.com/ubuntu edgy-security universe + deb-src http://security.ubuntu.com/ubuntu edgy-security universe + +The next step is to make sure all software present is up-to-date. There are already a few updates available so run these two commands: + + $ sudo apt-get update + $ sudo apt-get dist-upgrade + +That's it. + +

    Getting SSH up and running

    + +Because I have a MacBook, I'd like to use it to do my work. To do so I need to install the OpenSSH server on my server so I can access it over the network. + + $ sudo apt-get install ssh + +This will install ssh and the OpenSSH server. It also generates everything you need automatically like RSA keys and all that. + +Now, try to login wiht SSH from your desktop machine. + +

    Apache

    + +Since I want to use Subversion of WebDAV I will need to install Apache first. I'll grab a vanilla copy of Apache from Ubuntu here. + + $ sudo apt-get install apache2 + +If that's finished you should see a placeholder when you access your server with a browser. Check this now. + +

    Getting Subversion

    + +Subversion is also easily installed. + + $ sudo apt-get install subversion subversion-tools + +Next we should setup a location for our Subversion repositories. I choose to put them in /var/lib/svn. Create this directory now: + + $ sudo mkdir -p /var/lib/svn + +I also create a repository now and create a basic Subversion structure there. In my case the project is called 'colt'. + + $ sudo mkdir -p /var/lib/svn/colt + $ sudo svnadmin create /var/lib/svn/colt + $ sudo svn mkdir file:///var/lib/svn/colt/trunk -m "Trunk" + $ sudo svn mkdir file:///var/lib/svn/colt/tags -m "Tags" + $ sudo svn mkdir file:///var/lib/svn/colt/branches -m "Branches" + +Note that you need to sudo the svn commands because only root has write access to your repository currently. + +

    WebDAV for SVN

    + +Okay. You are already at revision 3 on your repository. Good work! Now let's make sure that you repositories are accessable over the web. First, we install libapache2-svn. This packages includes WebDAV support for SVN. + + $ sudo apt-get install libapache2-svn + +Next I open up /etc/apache2/mods-available/dav_svn.conf. This file contains configuration for the WebDAV and SubVersion modules we just installed. + +Here I enable basic HTTP authentication, which is good enough for my local network. I also enable DAV by uncommenting "DAV svn". + +You need to take special care of the "SVNPath" variable here. We don't host just one repository. We host several and /var/lib/svn is the parent directory of all our repositories. E.g. 'colt' is a sub directory that resides in /var/lib/svn. To make Apache understand this we need to change "SVNPath" to "SVNParentPath". This enables all sub directories to be independent repositories. + +Note: The authentication file we use here can be recycled later when we install Trac! Handy-Dandy, isn't it? + +I made my configuration look like this: + + # dav_svn.conf - Example Subversion/Apache configuration + # + # For details and further options see the Apache user manual and + # the Subversion book. + + # ... + # URL controls how the repository appears to the outside world. + # In this example clients access the repository as http://hostname/svn/ + + + # Uncomment this to enable the repository, + DAV svn + + # Set this to the path to your repository + SVNParentPath /var/lib/svn + + # The following allows for basic http authentication. Basic authentication + # should not be considered secure for any particularly rigorous definition of + # secure. + + # to create a passwd file + # # rm -f /etc/apache2/dav_svn.passwd + # # htpasswd2 -c /etc/apache2/dav_svn.passwd dwhedon + # New password: + # Re-type new password: + # Adding password for user dwhedon + # # + + # Uncomment the following 3 lines to enable Basic Authentication + AuthType Basic + AuthName "Subversion Repository Access" + AuthUserFile /etc/apache2/dav_svn.passwd + + # Uncomment the following line to enable Authz Authentication + # AuthzSVNAccessFile /etc/apache2/dav_svn.authz + + # The following three lines allow anonymous read, but make + # committers authenticate themselves. + + + Require valid-user + + + + +Now kick Apache to reload and you should be able to access your repository over the web! Try http://example.com/svn/colt. + +

    Authentication

    + +Reading of the repository is okay without authentication. But writing needs to be protected. We need to create a password file for this. This is easy and is already explained in /etc/apache2/mods-available/dav_svn.conf: + + $ sudo htpasswd2 -c /etc/apache2/dav_svn.passwd ariejan + +Go ahead, add as many users as you need. + +

    Apache, Subversion and the world

    + +Before you start using Subversion, make sure you make the repositories owned by Apache. Apache is the one who wil access the repositories physically. This is really easy: + + $ sudo chown -R www-data.www-data /var/lib/svn + +When you access your repository for write actions now, you will recieve the following message: + + Authentication realm: Subversion Repository Access + Password for 'ariejan': + +Alright sparky! Subversion access is ready for you now! Next time I'll tell you how to integrate Trac with your hot new Subversion repositories. + +

    Notes

    + +All users in you passwd file can write to all repositories. I've not yet found a way to prevent this, since I don't need that functionality right now. If you know more about this, please let me know. + +Subversion repositories are always readable by anonymous people. You should remove the LimitExcept block from dav_svn.conf to make sure all users authenticated themselves before accessing your repository. + +If you add more repositories, you always have to chown them to your apache's user and group. After that you can use them through WebDAV. diff --git a/content/posts/2006-12-02-how-to-setup-a-ubuntu-development-server-part-2.md b/content/posts/2006-12-02-how-to-setup-a-ubuntu-development-server-part-2.md new file mode 100644 index 0000000..5fcec42 --- /dev/null +++ b/content/posts/2006-12-02-how-to-setup-a-ubuntu-development-server-part-2.md @@ -0,0 +1,101 @@ +--- +title: "How to setup a Ubuntu development server - Part 2" +kind: article +slug: how-to-setup-a-ubuntu-development-server-part-2 +created_at: 2006-12-02 +tags: + - General + - Everything + - Web Development + - Features + - Ubuntu +--- + +Also read Part 1 - Subversion. + +In this part I will tell you how to install Trac on top of your Subversion repositories on your Ubuntu development server. Trac offers you a wiki, roadmap, tickets (tracking system) and access to your SubVersion repository. All of this is bundeled in a very sexy web interface. + +Well, let's get to work now and get Trac installed. When you're done you will have trac available for all your Subversion repositories. +~ + +

    Install Trac

    + +First thing to do is install trac. Here I will also install mod_python for your apache webserver and python-setuptools that we'll need later with the webadmin plugin. + + $ sudo apt-get install trac libapache2-mod-python python-setuptools + +Now, I create a directory where all Trac information will be stored. + + $ sudo mkdir -p /var/lib/trac + +Common sense dictates that you use the same name here for the trac environment as for the subversion repository. + +Change to the trac directory and intitialize the project: + + $ cd /var/lib/trac + $ sudo trac-admin colt initenv + +You'll need to name the project, choose a database file (default is okay), specify where the subversion repository resides ( /var/lib/svn/colt, in this case) and a template (the default is okay here too). + +I recommend you also create an administrator user right now. Make sure you add a user who's already in your /etc/apache2/dav_svn.passwd file. + + $ sudo trac-admin colt permission add ariejan TRAC_ADMIN + +Well, that's it. Trac has been installed. Now let's make sure we can access trac through the web. + +

    Configuring Apache

    + +Configuring apache is rather easy when you know what to do. Add the following code to /etc/apache2/sites-available/default (at the bottom before the end of the virtualhost tag) or put it in a seperate virtual host file if you want to dedicate a special domain to this. + + + SetHandler mod_python + PythonHandler trac.web.modpython_frontend + PythonOption TracEnvParentDir /var/lib/trac + PythonOption TracUriRoot /projects + + + + AuthType Basic + AuthName "Trac Authentication" + AuthUserFile /etc/apache2/dav_svn.passwd + Require valid-user + + +Notice here, again that we use TracEnvParentDir to show we host multiple instances of Trac. You may change the TracUriRoot to something different. + +Again, make sure to chown your Trac installation to www-data: + + $ sudo chown -R www-data.www-data /var/lib/trac + +Now, access your trac over the web: http://example.com/projects for a complete listing of hosted projects or http://example.com/projects/colt for the COLT project. + +You may also login now! As you can see, we use the dav_svn.passwd file here so everyone with subversion access also has access to trac. + +

    Webadmin

    + +Normally you would administrate a Trac installation through the command-line interface we used to initialize the environment and add the administrator user. Nowadays there is a webadmin plugin for Trac, which will be included in Trac from version 0.11. Since Ubuntu ships with Trac 0.9.3 we need to add this webadmin ourselves. + +First, download the following file to your server: http://trac.edgewall.org/attachment/wiki/WebAdmin/TracWebAdmin-0.1.2dev_r4240-py2.4.egg.zip. + +Don't unzip this file, just remove the .zip extension. + +Because we installed setuptools earlier, we can now use easy_install to install this plugin system-wide, enabling it for all our trac installations. + + $ sudo easy_install TracWebAdmin-0.1.2dev_r4240-py2.4.egg + +Next we enable webadmin in the global configuration file of track. You may need to create the 'conf' directory in this case: + + $ cd /usr/share/trac + $ sudo mkdir conf + $ sudo vi conf/trac.ini[/conf] + +Next enter the following in trac.ini + + [components] + webadmin.* = enabled + +Save the file and off you go. Login as the administrator user you specified earlier and you can make use of the 'admin' button that has appeared in the menu of Trac. + +Enjoy your trac! Next time (in Part 3) I will talk about setting up a commit-hook so Trac tickets are updated by posting subversion commit messages (throught commit-hooks). + +Also read Part 1 - Subversion on how to install Subversion over WebDAV. diff --git a/content/posts/2006-12-03-installing-rails-on-ubuntu-dapper-edgy.md b/content/posts/2006-12-03-installing-rails-on-ubuntu-dapper-edgy.md new file mode 100644 index 0000000..f679ba5 --- /dev/null +++ b/content/posts/2006-12-03-installing-rails-on-ubuntu-dapper-edgy.md @@ -0,0 +1,82 @@ +--- +title: "Installing Rails on Ubuntu Dapper / Edgy" +kind: article +slug: installing-rails-on-ubuntu-dapper-edgy +created_at: 2006-12-03 +tags: + - General + - Everything + - RubyOnRails + - Features +--- +Update 2010-03-25: Bumped to RubyGems version 1.3.6. + +Update 2009-02-19: Bumped to RubyGems version 1.3.1 and MySQL 5 libraries. This guide now works for all recent version of Ubuntu and Debian. Enjoy! + +Installing Ruby on Rails on your Ubuntu box is not always as easy as it seems. Here's a comprehensive overview of the steps you need to take. Mostly you'll be using apt-get and gems, so it's not all that hard after all. + +This method was tested on both Dapper and Edgy systems. It may work on other Ubuntu releases as well. It's also possible that it works on Debian. + +Besides Rails, I'll also be install mysql and sqlite3 support. + + +

    1. Install Ruby

    + +Before putting anything on Rails, install Ruby. + + $ sudo apt-get install irb1.8 libreadline-ruby1.8 libruby libruby1.8 rdoc1.8 ruby ruby1.8 ruby1.8-dev + +You may now check what version of Ruby you have by running `ruby -v`. It's just for your information. + +

    2. Install Ruby Gems

    + +Surf to http://rubyforge.org/frs/?group_id=126 and download the latest available gems pacakge in tgz format. (You may also use the zip if you feel comfortable.) + + $ wget http://rubyforge.org/frs/download.php/69365/rubygems-1.3.6.tgz + $ tar zxf rubygems-1.3.6.tgz + $ cd rubygems-1.3.6 + $ sudo ruby setup.rb + $ sudo ln -sf /usr/bin/gem1.8 /usr/bin/gem + +

    3. Install Rails!

    + +You may now install Rails now! + + $ sudo gem install rails + +That's it! Well, almost. You probably want some other things as well. + +

    4. Install development tools

    + +Before you continue, stop a moment to install some development tools. These tools are probably needed to compile and install the gems we are going to install next. + + $ sudo apt-get install build-essential + +

    MySQL Support

    + +You probably want MySQL Ruby support. The MySQL code in Rails sucks (no offence), but the Ruby code is much better. You should install it. Rails will notice that it's available and use it. + +First, install MySQL. + + $ sudo apt-get install mysql-server mysql-client + +Next, install development files for MySQL. We'll need these in order to make Ruby understand. + + $ sudo apt-get install libmysqlclient15-dev + +Then, you may install the gem + + $ sudo gem install mysql + +You have to choose what version you want to install. Enter the number corresponding with the latest version that is tagged 'ruby'. Installing win32 stuff on linux is generally not a good thing. + +

    SQLite 3

    + +For SQLite 3 you need to install some packages before installing the gem. + + $ sudo apt-get install sqlite3 libsqlite3-dev + $ sudo gem install sqlite3-ruby + +

    If things go wrong

    + +If things go wrong, make sure you installed all recommended packages mentioned above. Also , check any log files that the error message refers to. diff --git a/content/posts/2006-12-06-install-ruby-mysql-on-mac-os-x-104-tiger.md b/content/posts/2006-12-06-install-ruby-mysql-on-mac-os-x-104-tiger.md new file mode 100644 index 0000000..fb86022 --- /dev/null +++ b/content/posts/2006-12-06-install-ruby-mysql-on-mac-os-x-104-tiger.md @@ -0,0 +1,34 @@ +--- +title: "Install ruby-mysql on Mac OS X 10.4 Tiger" +kind: article +slug: install-ruby-mysql-on-mac-os-x-104-tiger +created_at: 2006-12-06 +tags: + - General + - Everything + - RubyOnRails + - Features + - Mac OS X +--- + +You probably know that the built-in mysql code in Rails sucks. To rephrase that, the ruby-mysql gem contains better code, so you want that. Rails automatically detects if you have ruby-mysql installed or not, and uses it if you have it. + +Most notably, you want to install this gem if you get dropped MySQL connections running your Rails application. + +So we do: + +
    $ sudo gem install mysql
    +...
    +ERROR: Failed to build gem native extension.
    + +Normall this would install fine, but not on Mac OS X. This is because Mac OS X keeps its code, headers and libraries in odd places (compared to Linux). But don't panic. There's an easy solution to all this! + + +Just compile the gem yourself, and add some special ingredients. Well, not even that. Just compile it again like this: + +
    $ cd /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7
    +$ sudo ruby extconf.rb --with-mysql-lib=/usr/local/mysql/lib --with-mysql-include=/usr/local/mysql/include
    +$ sudo make
    +$ sudo make install
    + +Now, you have the ruby-mysql gem installed and ready to go! No more dropped MySQL connections for Rails! \ No newline at end of file diff --git a/content/posts/2006-12-11-announcing-subversion-kick-start.md b/content/posts/2006-12-11-announcing-subversion-kick-start.md new file mode 100644 index 0000000..24b3416 --- /dev/null +++ b/content/posts/2006-12-11-announcing-subversion-kick-start.md @@ -0,0 +1,23 @@ +--- +title: "Announcing “Subversion Kick-Start”" +kind: article +slug: announcing-subversion-kick-start +created_at: 2006-12-11 +tags: + - General + - Everything + - Blog + - Subversion + - kickstart +--- + +With this post I officially announce my first book, "Subversion Kick-Start". + +Subversion is a popular version control system that's used by many, many developers around the globe. Most developers know how to find their way around Subversion, but quite a lot do not. + +SKS is for every software engineer who wants to learn how to put Subverstion to real use for their projects. It's more than just storing code. It's about structuring the way you write code. It's about optimizing the way you work. + +Subversion Kick-Start is aimed at getting you and Subversion up and running as quickly as possible with the minimal amount of effort required. + +Please subscribe to my feed newsletter or RSS feed and stay up-to-date! You'll be the first to know when the book is available. + \ No newline at end of file diff --git a/content/posts/2006-12-13-show-the-current-svn-revision-in-your-rails-app.md b/content/posts/2006-12-13-show-the-current-svn-revision-in-your-rails-app.md new file mode 100644 index 0000000..2c16d0d --- /dev/null +++ b/content/posts/2006-12-13-show-the-current-svn-revision-in-your-rails-app.md @@ -0,0 +1,60 @@ +--- +title: "Show the current SVN revision in your Rails app" +kind: article +slug: show-the-current-svn-revision-in-your-rails-app +created_at: 2006-12-13 +tags: + - General + - Everything + - RubyOnRails + - Features + - Subversion + - Capistrano +--- + +I'm current developing a Rails application. I deploy this application to a demonstration server using capistrano. + +To streamline feedback and bug reporting I want to show the current revision number of the code that's published on the demo server to show in the footer of every page. + +First I looked into Subversion keyword expansion, but this is marked as 'evil' and it doesn't meet my requirements. I want to show the latest revision number of the entire repository and not just that of the current file. + +Luckily for me, I use capistrano. Here's how I fixed the problem. + + +First of all, I created a partial that contains the revision number and render this in my layout. + +app/views/layouts/_revision.rhtml: +
    +CURRENT
    +
    + +This shows CURRENT always when I work with my working copy. + +app/views/layouts/mylayout.rhtml +
    +  < %= render :partial => 'layout/revision' %>
    +
    + +Now, I assume you have already setup capistrano for your project and that you have a config/deploy.rb file. + +I've added the following helper to my config/deploy.rb file: + +
    desc "Write current revision to app/layouts/_revision.rhtml"
    +task :publish_revision do
    +  run "svn info #{release_path} | grep ^Revision > #{release_path}/app/views/layouts/_revision.rhtml"
    +end
    + +Next, I added the following hook 'after_update_code'. This will automatically be run after update_code which is called in 'deploy'. + +
    desc "Run this after update_code"
    +task :after_update_code do
    +  publish_revision
    +end
    + +That's it. When I deploy the application, the current code is checked out and and the layouts/_revision.rhtml file is overwritten with the current revision information. + +

    Bonus

    + +You could also leave the layouts/_revision.rhtml files empty and update it for your demonstration server, but not for your production box. This way there won't be a revision added. + +Of course, you could also create a deploy_demonstration method in deploy.rb and call publish_revision manually from there. \ No newline at end of file diff --git a/content/posts/2006-12-20-svn-how-often-should-you-commit.md b/content/posts/2006-12-20-svn-how-often-should-you-commit.md new file mode 100644 index 0000000..17a4b76 --- /dev/null +++ b/content/posts/2006-12-20-svn-how-often-should-you-commit.md @@ -0,0 +1,39 @@ +--- +title: "SVN: How often should you commit?" +kind: article +slug: svn-how-often-should-you-commit +created_at: 2006-12-20 +tags: + - General + - Everything + - Features + - Subversion +--- + +I often hear discussion about how often developers should commit their work to the central repository. Some say that you should only commit when you're next 'release' is ready. Others say that you should commit every change you make in your code. There are even people who say you should commit your changes only at the end of the day. + +All wrong! There is no such thing as 'the way', but there is a thing called best practice and that's what I want to talk to you about. How often and what should you commit to your Subversion repository. + + +Actually, it's quite simple. If you use a tool like Trac or any other ticketing tracking system you have already got a great starting point (given, of course, that you add 'things you need to do' to your tracker). + +I split my development activities into small atomic actions. Normally these actions take up a few minutes to an hour of your time. Besides the fact that these atomic actions are managable, they also give you a sense of satisfaction because you can complete quite a few of these smaller actions every day. + +All right. You have these atomic actions. I grab my code, perform the atomic actions and when it succeeds, I comment my changes back to Subversion. This always closes a ticket in my Trac system and most of the time refences the 'big picture' ticket. + +Here's what I mean. + +Big picture ticket: "Create an accounting system for users" + +Atomic Action ticket: "Create and customize the user model and unit tests" +Atomic Action ticket: "Install acts_as_authenticated plugin" + +So, I start with my code and first I install the acts_as_authenticated plugin. Great. Commit that. This takes a few minutes at the most. + +Next, I create and customize the user model and unit tests. When that's done, I commit my changes again. As you can see, I've already closed two tickets (and got that warm satisfactory feeling). + +Okay, you get the picture. Why would you work like this? Subversion allows you to keep track of changes in your code, but it doesn't force you on how ofter or when you track changes. + +The most logical way of keeping track of changes is atomic actions. If you decide later that you don't want the installed version of the acts_as_authenticated plugin, but a later release. You can see what files were added when you installed the plugin. + +By using atomic actions you create atomic changesets in Subversion. This way you can easily jump between different states (revisions) of you code without breaking things. It keeps your code working and it allows you to easily track changes you made and to what purpose. \ No newline at end of file diff --git a/content/posts/2006-12-20-svn-merge-a-branch-with-your-trunk.md b/content/posts/2006-12-20-svn-merge-a-branch-with-your-trunk.md new file mode 100644 index 0000000..b264550 --- /dev/null +++ b/content/posts/2006-12-20-svn-merge-a-branch-with-your-trunk.md @@ -0,0 +1,34 @@ +--- +title: "SVN: Merge a branch with your trunk" +kind: article +slug: svn-merge-a-branch-with-your-trunk +created_at: 2006-12-20 +tags: + - General + - Everything + - Features + - Subversion +--- + +When created a TRY-branch a few days back to try some fancy new AJAX technology in my application. Not problems there, so now I want to merge the code in the branch with my trunk. + +Since I'm a lone hacker, the trunk has not been touched since I created the branch. I have a checked-out working copy of the branch available. + +Here's a handy-dandy guide on how to merge your branch code with your trunk. + + +Firstly, make sure you have a working copy of your trunk. I choose to switch my working copy back: (oh, make sure you have all your changes checked in in your branch before you switch!) + +
    $ svn switch http://example.com/svn/myproject/trunk
    + +This removes, adds and updates all files you have worked on in your branch and creates a working copy of the code in the trunk. + +Now, with my trunk in place, I can call 'merge' and apply the changes. + +
    $ svn merge http://example.com/svn/myproject/trunk http://example.com/svn/myproject/branches/TRY-AJAX
    + +Since the files from the trunk and the beginning of the TRY-branch are still exact copies, I won't get in any trouble. If you do (and you did change your code in your trunk), make sure to resolve merging problems before checking in. When ready, check in your new code! + +
    $ svn ci -m "Merge TRY-AJAX branch with trunk"
    + +That's it. You have now merged the branch with your trunk. Enjoy! \ No newline at end of file diff --git a/content/posts/2006-12-22-textmaterails-easy-partials-for-better-code.md b/content/posts/2006-12-22-textmaterails-easy-partials-for-better-code.md new file mode 100644 index 0000000..0d8e570 --- /dev/null +++ b/content/posts/2006-12-22-textmaterails-easy-partials-for-better-code.md @@ -0,0 +1,95 @@ +--- +title: "Textmate+Rails: Easy partials for better code" +kind: article +slug: textmaterails-easy-partials-for-better-code +created_at: 2006-12-22 +tags: + - General + - Everything + - RubyOnRails + - Features + - Textmate +--- + +As you may know, I use TextMate for editing Rails code. + +I've just been browsing the Rails bundle today and I came across some very interesting things. Today I'll tell you about partials. + +Partials are ERb templates. They are mostly HTML (or RJS or XML or whatever output format you use) and include some embedded Ruby to show actual content. Partials are not linked to a method in a controller, but instead they can be easily rendered through-out your application. + +TextMate allows you to refactor your application to use partials with almost no effort! + + +First something about partials for those who don't know about it. + +Partials are stored in files that start with an _ (underscore) and end in .rthml (or .rjs or .rxml) and you may include them in your ERb like this: + +
    < %= render :partial => 'userinfo' %>
    + +This will include the partial _userinfo.rhtml for you. This way you can avoid duplication by including the same ERb template (partial) over and over again. + +Partials are also useful to keep your views (templates) clean and easy to understand. In large projects this will allow you later to easily change a view without having to spit through 500 lines of ERb. + +Let's say you have a profile page where you want to show a users's profile and some options for when the user is viewing his own profile. Also, you want to show a simple message when the users has not yet created a profile, which is a possibility in my application. The show.rhtml template for the profile may look like this + +

    Profile for < %=h @profile.user.login %>

    + +< % if @profile.exists? -%> + +
    < %= @profile.firstname %> has been a member for < %= distance_of_time_in_words_to_now @profile.user.created_at %>
    + +

    General information

    +
    Name: < %= @profile.fullname %>
    +
    Country: < %= @profile.country %>
    +
    Birthday: < %= fmt_date @profile.birthday %>
    + +

    About < %= @profile.firstname %>

    +
    < %= markdown @profile.about %>
    + +< % else -%> + +
    < %= @profile.user.login %> has been a member for < %= distance_of_time_in_words_to_now @profile.user.created_at %>
    + +

    Oh my!

    +

    This user has not yet created a profile.

    + +< % end -%> + +< % if @profile.user.id == current_user.id -%> +

    Options

    +

    This is your profile, so you have some options:

    +
      +
    • < %= link_to "Change password", :controller => 'account', :action => "change_password" %>
    • +
    • < %= link_to "Edit your profile", profile_edit_url(:login => current_user.login) %> +
    +< % end -%>
    + +As you may see, this is chaos. It's not easy to read and you can't easily change something when you need to. Wouldn't it be great to store all three seperate blocks (profile, no profile message and options) in partials? + +This is where TextMate does it's magic. Here's a before snaphot: + +before.png + +Now, select the part between the if and else statements. This is where a normal profile is shown. Next, use TextMate: SHIFT-CTRL-H. You'll get a pop-up asking you how you'd like to name the partial. In this case I choose 'profile'. + +during.png + +After you click OK, the partial will be created containing the code you selected and an appropriate render method is placed instead. This is what the code looks like after: + +after.png + +After all this the same ERb template looks like this: + +

    Profile for < %=h @profile.user.login %>

    + +< % if @profile.exists? -%> + < %= render :partial => 'profile' %> +< % else -%> + < %= render :partial => 'noprofile' %> +< % end -%> + +< % if @profile.user.id == current_user.id -%> + < %= render :partial => 'options' %> +< % end -%>
    + +Now, isn't that more readable? Even, if you want to change a profile, you can just edit _profile.rhtml and you're done. \ No newline at end of file diff --git a/content/posts/2007-01-12-rails-group-results-by-week-using-group_by.md b/content/posts/2007-01-12-rails-group-results-by-week-using-group_by.md new file mode 100644 index 0000000..21fea88 --- /dev/null +++ b/content/posts/2007-01-12-rails-group-results-by-week-using-group_by.md @@ -0,0 +1,61 @@ +--- +title: "Rails: Group results by week (using group_by)" +kind: article +slug: rails-group-results-by-week-using-group_by +created_at: 2007-01-12 +tags: + - General + - Everything + - RubyOnRails + - Features +--- + +The Enumerable class in Rails contains a method named 'group_by'. This method is pure magic for a developer's point of view. I'll give you a simple example that shows the power of group_by. + +Let's say you have a table 'posts' containing blog posts. Now, you normally show these chronologically a few at a time. Nothing special there. For some special overview page, you want to group your posts by week. + +With normal ActiveRecord operations this would be quite an elaborate task. But with group_by from Enumerable, it becomes child's play. + + +First of all, in the controller, just get all the posts you need. In this case, all of them: + +Controller: +
    def list
    +  @posts = Post.find :all
    +end
    + +As you can see, I perform no ordering or whatsoever here. + +Now, in your view you normally would iterate over all posts like this: + +
    < %= render :partial => 'post', :collection => @posts %>
    + +But, as I said, we want to group the posts by week. To make life easy, I add a method to the Post class that returns the week number in which a post was written: + +Model Post: +
    def week
    +  self.created_at.strftime('%W')
    +end
    + +Now, the magic will happen in our view: + +
    < % @posts.group_by(&:week).each do |week, posts| %>
    +  
    +

    Week < %= week %>

    + < %= render :partial => 'post', :collection => @posts %> +
    +< % end %>
    + +Let me explain the above. We specify that we want to call group_by for @posts. But we need to say how we want to group these posts. By specifying &:week we tell group_by that we want to group by the result of the week attribute of every post. This is the attribute we specified earlier in the model. + +Well, when the grouping is done we create a block that will handle every group of items. We extract 'week' and 'posts' here. 'week' contains the week number and 'posts' all the posts for that week. + +As normal, we can now show the week number and iterate over the posts. + +

    Sorting groups

    + +The result of group_by is not guaranteed to be ordered in any way. Simply call 'sort' before each and you're set: + +
    @posts.group_by(&:week).sort.each do |week, posts|
    + +Mostly, you'll find that the posts for every group are not sorted either. With the example above I think it's easy to figure out how to do that now. (hint: .sort) \ No newline at end of file diff --git a/content/posts/2007-01-19-print-this-page-with-ruby-on-rails.md b/content/posts/2007-01-19-print-this-page-with-ruby-on-rails.md new file mode 100644 index 0000000..eb753da --- /dev/null +++ b/content/posts/2007-01-19-print-this-page-with-ruby-on-rails.md @@ -0,0 +1,44 @@ +--- +title: "“Print this page” with Ruby on Rails" +kind: article +slug: print-this-page-with-ruby-on-rails +created_at: 2007-01-19 +tags: + - General + - Everything + - RubyOnRails + - Features +--- + +You have put a lot of effort into creating a sexy overview of whatever data your application stores and allow your users to manipulate that data through AJAX controls. But, some people just want to print their data. + +How to go about that? Just printing the page with data is generally not a good idea because it has been optimized for display on a screen. The first step we need to take is adapting our page for printing. Stylesheets are very handy tools for this. Check the following part of the header of my layout: + + +
    stylesheet_link_tag('default', :media => :screen)
    +stylesheet_link_tag('print', :media => :print)
    + +This will generate HTML code that includes two stylesheets. However, only the 'default' stylesheet is used on screen. When the users prints a particular page, the print stylesheet is used instead. So, what do you want to change in the print stylesheet? + +
      +
    • Fonts should be serif (not sans-serif) for printing
    • +
    • Hide images as much as possible
    • +
    • Hide ads
    • +
    • Display printable ads that are hidden on screen
    • +
    • Hide navigational elements
    • +
    • Use a black-on-white colour scheme
    • +
    • Underline links and colour them blue for easy recognition
    • +
    • Add the actual URL to your links (see below)
    • +
    + +To add the actual URL in the href-part of your link to the name of your link add the following to you print stylesheet: + +
    a:after { content:' [' attr(href) '] '}
    + +Now, with all this in place, load your page with data. Print your page or watch a preview and be amazed at what stylesheets can do for you! + +The final touch is adding a "Print this page" link to your navigation on screen. Here you can use link_to_function: + +
    link_to_function("Print this Page", "javascript:print()")
    + +That's it. Just hit the Print this Page button and your browsers print dialog will pop up and use your fancy new print stylesheet. \ No newline at end of file diff --git a/content/posts/2007-01-19-why-ruby-rocks-convince-your-fellow-developers.md b/content/posts/2007-01-19-why-ruby-rocks-convince-your-fellow-developers.md new file mode 100644 index 0000000..419becf --- /dev/null +++ b/content/posts/2007-01-19-why-ruby-rocks-convince-your-fellow-developers.md @@ -0,0 +1,15 @@ +--- +title: "Why Ruby Rocks - Convince your fellow developers" +kind: article +slug: why-ruby-rocks-convince-your-fellow-developers +created_at: 2007-01-19 +tags: + - General + - Everything + - Blog + - Ruby +--- + +I often hear questions from my Java and PHP oriented friends about what makes Ruby so great and easy to use. Until today I've shown them some of my Rails feats (AJAX Scaffold always amazes people). Now, I came across this 20 minute Ruby introduction. Starting with the basic "Hello World" item, this article show step by step improvements to end up with blocks, objects and all that makes Ruby really worth while. + +So, if you need to show developers what Ruby (and not so much Rails) can do, show 'em this! It impresses the shit out of 'em. \ No newline at end of file diff --git a/content/posts/2007-01-23-new-in-rails-resource-scaffold-generator.md b/content/posts/2007-01-23-new-in-rails-resource-scaffold-generator.md new file mode 100644 index 0000000..4a915ce --- /dev/null +++ b/content/posts/2007-01-23-new-in-rails-resource-scaffold-generator.md @@ -0,0 +1,33 @@ +--- +title: "New in Rails: Resource Scaffold Generator" +kind: article +slug: new-in-rails-resource-scaffold-generator +created_at: 2007-01-23 +tags: + - General + - RubyOnRails + - Features +--- + +Oh boy! Rails 1.2 is all about resources. A product entry in your application is not just a rendered HTML page, but it "is" data. Rails 1.2 allows you to add a .xml extension to your url to retrieve the same product information in XML format! + +Now, this all sounds hard. Two ways of rendering the same action depending on an extension. Generating XML code for every model in your database. No way you want to spend time developing all that stuff. + +Well, Rails wouldn't be Rails if there weren't a generator for it! + + +This example is really easy. I want to create products in my database and expose them as HTML and XML to my users. Just do the following: + +
    $ ./script/generate scaffold_resource Product title:string description:text
    + +A migration is generated automatically so update that if you want to before running + +
    $ rake db:migrate
    + +As you can see in config/routes.rb, Rails has automatically generated routes for this resource! + +
    map.resource :products
    + +Now you can start up your Rails app and access http://localhost:8000/products. Add some products if you like. Now if you got to http://localhost:8000/products/1 you a nice HTML scaffold. If you add .xml: http://localhost:8000/products/1.xml you'll get nice XML! + +Well, writing a seperate desktop application that retrieves informatin from your web application just got 100x easier! \ No newline at end of file diff --git a/content/posts/2007-01-23-rails-nested-resource-scaffold.md b/content/posts/2007-01-23-rails-nested-resource-scaffold.md new file mode 100644 index 0000000..e04e236 --- /dev/null +++ b/content/posts/2007-01-23-rails-nested-resource-scaffold.md @@ -0,0 +1,102 @@ +--- +title: "Rails: Nested resource scaffold" +kind: article +slug: rails-nested-resource-scaffold +created_at: 2007-01-23 +tags: + - General + - Everything + - Features +--- + +In my previous post I told you about the resource scaffold. What you'll be doing a lot is nesting these resources. Ingredients in recipes, comments on posts, options for products. You name it, you nest it! + +Since Rails does not automatically nest resources for you, you should do this yourself. This is, with some minor tweaks, really easy to accomplish. In this example I'll create recipes that have multiple ingredients. + +I assume you have Rails 1.2.1 installed for this tutorial to work properly. + + +First, I create an new rails project named 'cookbook'. I use an SQLite3 database because it's easy to do so. You may use any Rails compatible database for this example. + +
    $ mkdir cookbook
    +rails --database sqlite3 cookbook
    +cd cookbook
    + +First I create resource scaffolds for both the Recipe and Ingredient models: + +
    $ ./script/generate scaffold_resource Recipe title:string instructions:text
    +./script/generate scaffold_resource Ingredient name:string quantity:string
    + +As you can see I did not add a recipe_id to the ingredient model because of the has_many relationship. Add this column to the migration file. You should now be able to migrate your database: + +
    $ rake db:migrate
    + +If you add the recipe_id to the generate script the view for your ingredients will include a field for the recipe_id and that's not what you want. + +Next, make the has_many relationship in your models. + +app/models/recipe.rb: +
    class Recipe < ActiveRecord::Base
    +  has_many :ingredients
    +end
    + +app/models/ingredient.rb +
    class Ingredient < ActiveRecord::Base
    +  belongs_to :recipe
    +end
    + +So far, nothing new. Next we check out config/routes.rb: +
    map.resources :ingredients
    +map.resources :recipes
    + +What we want is to map ingredients as a resource to recipes. Replace these two lines with: + +
    map.resources :recipes do |recipes|
    +	recipes.resources :ingredients
    +end
    + +This will give you urls like /recipes/123/ingredients/321 + +Now we need to make some changes to the ingredients controller. Every ingredient belongs to a recipe. First add the filter: + +
    before_filter(:get_recipe)
    +
    +private
    +def get_recipe
    +	@recipe = Recipe.find(params[:recipe_id])
    +end
    + +This will make sure that every ingredient knows what recipe it belongs to. + +In the index method of the ingredient controller, make sure you have this: + +
    @ingredients = @recipe.ingredients.find(:all)
    + +This makes sure you only show ingredients for this recipe, and not all ingredients in the database. + +Because we changed the route for ingredients, we need to update all ingredient_url() and ingredient_path() calls in our controller and views. Change all occurrences of + +
    ingredient_url(@ingredient)
    +and +
    ingredient_path(@ingredient)
    + +to + +
    ingredient_url(@recipe, @ingredient)
    +and +
    ingredient_path(@recipe, @ingredient)
    + +Note: Make sure that you don't replace 'ingredient' with '@ingredient' in your views! + +Add a link to the ingredients to your recipe's index.rhtml view. + +
    link_to 'Ingredients', ingredients_path(recipe)
    + +And, at last, make sure the create method of your ingredients controller attaches the ingredient to the right recipe. Make sure the first two lines look like this: + +
    def create
    +  @ingredient = @recipe.ingredients.new(params[:ingredient])
    + +You may now start your webserver and check out http://localhost:8000/recipes . Create some recipes and click 'ingredients'. You can now add ingredients for this recipe! + +The next step will be customizing each method to suit your own needs.
    \ No newline at end of file diff --git a/content/posts/2007-01-23-updates-wordpress-21-themes-and-social.md b/content/posts/2007-01-23-updates-wordpress-21-themes-and-social.md new file mode 100644 index 0000000..7ff0ad2 --- /dev/null +++ b/content/posts/2007-01-23-updates-wordpress-21-themes-and-social.md @@ -0,0 +1,19 @@ +--- +title: "Updates: Wordpress 2.1, Themes and Social" +kind: article +slug: updates-wordpress-21-themes-and-social +created_at: 2007-01-23 +tags: + - General + - Everything + - Wordpress + - Ariejan.net + - Blog +--- + +You can't really see it, but Ariejan.net has been upgraded to Wordpress 2.1. I've been running 2.1 beta's on a private server for some time now, so there weren't any surprises during the upgrade. + +I've also updated the theme to something more stylish and sober. Google Ads are less annoying now and merge nicely with the content. I've also re-enabled the social bookmark links so you can quickly bookmark articles on Del.icio.us or Digg. + +Hope you like the new style. Some minor tweaks will be applied the following days where needed. Please let me know your thoughts on ariejan.net! + \ No newline at end of file diff --git a/content/posts/2007-01-28-ruby-sort-an-array-of-objects-by-an-attribute.md b/content/posts/2007-01-28-ruby-sort-an-array-of-objects-by-an-attribute.md new file mode 100644 index 0000000..a2ba661 --- /dev/null +++ b/content/posts/2007-01-28-ruby-sort-an-array-of-objects-by-an-attribute.md @@ -0,0 +1,28 @@ +--- +title: "Ruby: Sort an array of objects by an attribute" +kind: article +slug: ruby-sort-an-array-of-objects-by-an-attribute +created_at: 2007-01-28 +tags: + - General + - Features + - Ruby +--- +In this example I'll show you how easy it is to sort an array of (the same kind of) objects by an attribute. Let's say you have an array of User objects that have the attributes 'name' and 'login_count'. First, find all users. + + :::ruby + @users = User.find(:all) + +Now, we have to sort this array by 'name'. Since we don't know if any user used capitals in his name or not, we use 'downcase' to sort without case sensitivity. + +A small not. 'sort' returns a new array and leaves the original unchanged. You may want to just reorder the @users array, so use the 'sort!' method. The '!' indicates it's a destructive method. It will overwrite the current @users array with the new sorting. + + :::ruby + @users.sort! { |a,b| a.name.downcase <=> b.name.downcase } + +That's all! Since strings are comparable, this will sort you user objects alphabetically by name. Want to sort on login_count instead? + + :::ruby + @users.sort! { |a,b| a.login_count <=> b.login_count } + +So, now you can easily sort any object in an array just like you want it too! diff --git a/content/posts/2007-01-29-hobo-and-you-thought-rails-made-life-easy.md b/content/posts/2007-01-29-hobo-and-you-thought-rails-made-life-easy.md new file mode 100644 index 0000000..5fe02d9 --- /dev/null +++ b/content/posts/2007-01-29-hobo-and-you-thought-rails-made-life-easy.md @@ -0,0 +1,21 @@ +--- +title: "Hobo - And you thought Rails made life easy!" +kind: article +slug: hobo-and-you-thought-rails-made-life-easy +created_at: 2007-01-29 +tags: + - General + - Blog + - RubyOnRails +--- + +If you've seen anything of Ruby on Rails, you know it makes your life really easy with all its generators and plugins. Well, check out Hobo! + +Hobo is a Rails plugin that adds tons of extra features that make your live even easier. Here's a quote from their site: + +
    +Hobo is an Open-Source project that makes development with Rails even faster than it already is. It features: +
      +
    • A template engine that extends Rails' standard ERB templates with user-defined tags
    • +
    • A powerful library of pre-defined tags for knocking up ajaxified data-driven sites in a snap + (lots still to do \ No newline at end of file diff --git a/content/posts/2007-02-09-my-photos-on-flickr.md b/content/posts/2007-02-09-my-photos-on-flickr.md new file mode 100644 index 0000000..252902f --- /dev/null +++ b/content/posts/2007-02-09-my-photos-on-flickr.md @@ -0,0 +1,29 @@ +--- +title: "My photo’s on Flickr" +kind: article +slug: my-photos-on-flickr +created_at: 2007-02-09 +tags: + - General + - Everything + - Blog +--- + + + +
      + +
      + Near Camden Lock Market, London, UK, originally uploaded by ariejan. +
      + +

      + In an idle moment I decided to up all my photo's to Flickr. Normally I burn my photo's to disk or zip them up on an external harddisk. But then, I never look at them again and there a few nice pictures there.
      +
      +Feel free to check out my photo's now. Oh, keep track of more new stuff there. I'm planning on making some very nice pictures this weekend. +

      \ No newline at end of file diff --git a/content/posts/2007-02-17-goals-in-the-park.md b/content/posts/2007-02-17-goals-in-the-park.md new file mode 100644 index 0000000..af0ceee --- /dev/null +++ b/content/posts/2007-02-17-goals-in-the-park.md @@ -0,0 +1,29 @@ +--- +title: "Goals in the Park" +kind: article +slug: goals-in-the-park +created_at: 2007-02-17 +tags: + - General + - Everything + - Blog +--- + + + +
      + +
      + Goals in the Park, originally uploaded by ariejan. +
      + +

      + My new hobby is photography, if you didn't know already. Today I was strolling the park and I took this picture. Actually, I took three pictures and created a HDR (High Dynamic Range) image of them.
      +
      +Hope you enjoy the view and let me know if you want more information on this subjejct! +

      \ No newline at end of file diff --git a/content/posts/2007-02-21-ror-link_to_remote-with-a-text_field-value-as-an-argument.md b/content/posts/2007-02-21-ror-link_to_remote-with-a-text_field-value-as-an-argument.md new file mode 100644 index 0000000..5715581 --- /dev/null +++ b/content/posts/2007-02-21-ror-link_to_remote-with-a-text_field-value-as-an-argument.md @@ -0,0 +1,55 @@ +--- +title: "RoR: link_to_remote with a text_field value as an argument" +kind: article +slug: ror-link_to_remote-with-a-text_field-value-as-an-argument +created_at: 2007-02-21 +tags: + - General + - RubyOnRails + - Features +--- + +Forms are very well supported in Ruby on Rails. But in some cases you want to get information about a value a user has entered in a form, but without submitting the entire form. A good example of this is the sign up procedure at Yahoo. You enter your desired username, click "check availability" and you know if the name is available to you. + +In my case I want to add locations to my database and use geocoding to get latituden/longitude values. So, I want users to enter an address and click "check" to verify that geocoding is successful. When successful, the full address and lat/lng values are automatically filled out in the form. + +This article shows you how to update those text fields and most importantly, it shows how to add a text_field value as a paramater to your link_to_remote tag. + + +This last part is easy with RJS: + +
      render :update do |page|
      +  page[:location_address].value = gecode.full_address
      +  page[:location_lat].value = geocode.lat
      +  page[:location_lng].value = geocode.lng
      +end
      + +But now the tricky part! How to include the value of a text_field in a link_to_remote tag?? A normal link_to_remote tag might look like this: + +
      link_to_remote "Check address",
      +   :url {
      +      :controller => 'locations',
      +      :action => 'check_address',
      +      :address => @location.address
      +   },
      +   :method => :get
      + +The problem here is that we're at the 'new' form, so @location doesn't contain anything yet. Erb isn't going to help us either because it's already rendered and cannot change something 'onClick'. + +You got it, JavaScript is your friend! You could now write a JS function that retrieves the value of the text_field and add it to the link_to_remote request. Well, you could. But there is an easier way! + +Prototype is the answer! It comes default with Rails and if you already use the javascript defaults, it's already at your diposal. If not, include prototype in your layout first: + +
      < %= javascript_include_tag 'prototype' %>
      + +Now, we can use Form.element.getValue to get the text_field value. Since Prototype is very nice, there's a shortcut available: $F(element). + +The next question is how to integrate this with our link_to_remote tag. The :with paramater offers help! + +
      link_to_remote "Check address",
      +   :url {
      +      :controller => 'locations',
      +      :action => 'check_address',
      +      :address => @location.address
      +   },
      +   :with => "'address='+encodeURIComponent($F(
      \ No newline at end of file
      diff --git a/content/posts/2007-02-23-subversion-cheat-sheet-10.md b/content/posts/2007-02-23-subversion-cheat-sheet-10.md
      new file mode 100644
      index 0000000..f805864
      --- /dev/null
      +++ b/content/posts/2007-02-23-subversion-cheat-sheet-10.md
      @@ -0,0 +1,36 @@
      +---
      +title: "Subversion Cheat Sheet 1.0!"
      +kind: article
      +slug: subversion-cheat-sheet-10
      +created_at: 2007-02-23
      +tags:
      +  - General
      +  - Features
      +  - Subversion
      +---
      +
      +I've noticed a huge interest in my Subversion articles lately and I thought to create a nice cheat sheet for all you.
      +
      +The cheat sheets includes common commands that you'll use when using Subversion on a daily basis. I didn't include every option or command that subversion supports, nor did I include any administration stuff. This sheet is aimed at developers who use Subversion on a daily basis.
      +
      +
      +You can find the Subversion Cheat Sheet and future updates at:
      +
      +
      http://ariejan.net/svncheatsheet
      + +Please link to the url above from your blog to spread the word! Thanks. + +Topics covered in this cheat sheet are: + +
        +
      • Subversion Status codes
      • +
      • Daily usage commands
      • +
      • Bug fixing
      • +
      • Branching and tagging
      • +
      • File Magic
      • +
      • Locking
      • +
      • Properties
      • +
      • Experimenting
      • +
      + +Please let me know if anything's missing. \ No newline at end of file diff --git a/content/posts/2007-02-26-4-unusual-uses-for-subversion.md b/content/posts/2007-02-26-4-unusual-uses-for-subversion.md new file mode 100644 index 0000000..bd1a843 --- /dev/null +++ b/content/posts/2007-02-26-4-unusual-uses-for-subversion.md @@ -0,0 +1,33 @@ +--- +title: "4 Unusual uses for Subversion" +kind: article +slug: 4-unusual-uses-for-subversion +created_at: 2007-02-26 +tags: + - General + - Features + - Subversion +--- + +The most common use of Subversion is to keep source code of applications versioned and secure. However, there are quite a few other options that are not so common at all. + +Quickly read on and find out if maybe you can put Subversion to use in quite a few ways you didn't expect. + + +1. CMS + +Maybe CMS is a bit strong, but it's very easy to develop a static website locally and store it in Subversion. You just checkout your website on your production server and you're good to go. You can easily jump back to previous content and you always have a backup around. + +2. Todo list / Notes + +You can version a list of text files as todo items or notes. You can check it out anywhere you want and keep track of all your past notes and items. You will have to establish some sort of formatting convention for yourself as how you name files and all that. + +3. Backups + +Put your entire home directory in Subversion. You'll always have a backup handy and you can easily jump back in time! Maybe it's not as sophisticated as Mac OS X Leopard's Time Machine, but it serves its purpose. + +4. Community Story Writing + +It's a bit like coding. You could use Subversion to write a story (or manuscript or book or ...) as a community. It's a bit like a wiki, but you can all use your favourite editors. You'll have to agree on an ASCII based file format, though. + +Maybe you know some very cool way to use Subversion as well? Please comment below! \ No newline at end of file diff --git a/content/posts/2007-03-06-subversion-cheat-sheet-update-101.md b/content/posts/2007-03-06-subversion-cheat-sheet-update-101.md new file mode 100644 index 0000000..0c8a479 --- /dev/null +++ b/content/posts/2007-03-06-subversion-cheat-sheet-update-101.md @@ -0,0 +1,20 @@ +--- +title: "Subversion Cheat Sheet Update: 1.0.1" +kind: article +slug: subversion-cheat-sheet-update-101 +created_at: 2007-03-06 +tags: + - General + - Blog + - Subversion +--- + +I've just uploaded version 1.0.1 of the Subversion Cheat Sheet to Ariejan.net. + +Please download this new version and get the following change: + +
        +
      • Fixed typo. Thanks to Gregory Gerard.
      • +
      + +Head to the Subversion Cheat Sheet page now and download the new version. \ No newline at end of file diff --git a/content/posts/2007-03-07-wordpress-plugin-autoflickr-10.md b/content/posts/2007-03-07-wordpress-plugin-autoflickr-10.md new file mode 100644 index 0000000..018c055 --- /dev/null +++ b/content/posts/2007-03-07-wordpress-plugin-autoflickr-10.md @@ -0,0 +1,57 @@ +--- +title: "Wordpress Plugin: AutoFlickr 1.0" +kind: article +slug: wordpress-plugin-autoflickr-10 +created_at: 2007-03-07 +tags: + - General + - Wordpress + - Features + - AutoFlickr +--- + +This post announces my first Wordpress Plugin: AutoFlickr. + +

      What does it do?

      + +When enabled, AutoFlickr will automatically find and insert one ore more photos that are related to the content of your post. Photos are found and hosted on Flickr. + +

      Features

      + +
        +
      • Automatically insert one or more photos
      • +
      • Customizable (photo size, number of photos and type of search).
      • +
      • Option to select what pages to automatically show photos.
      • +
      + + +

      Installation

      + +
        +
      1. Download the latest version here: AutoFlickr 1.0.
      2. + +
      3. Unzip the downloaded file and put the entire 'autoflickr' directory in /wp-content/plugins
      4. + +
      5. Activate the plugin under Plugins
      6. + +
      7. Go to Options -> AutoFlickr to customize the settings.
      8. + +
      + +That's it. Optionally you may want to adapt autoflickr.css to change the way the images are aligned and styled. + +If you want to include images in another place than the default, you may disable AutoFlickr for all pages and include the following PHP code in your template. Note that this code must be used within "The Loop". + +
      + +

      Demo

      + +This plugin was developed for CityTrippers.net. You can see the plugin in action when reading a single story. + +

      Support

      + +Please leave a comment or contact me directly if you have any questions regarding this plugin. + +

      New versions?

      + +Please subscribe to my RSS Feed (or use e-mail subscription) to get automatic updates about Ariejan.net and this plugin. \ No newline at end of file diff --git a/content/posts/2007-03-20-8-great-ways-to-use-google-for-your-start-up.md b/content/posts/2007-03-20-8-great-ways-to-use-google-for-your-start-up.md new file mode 100644 index 0000000..b9ff0ef --- /dev/null +++ b/content/posts/2007-03-20-8-great-ways-to-use-google-for-your-start-up.md @@ -0,0 +1,57 @@ +--- +title: "8 Great ways to use Google for your start-up" +kind: article +slug: 8-great-ways-to-use-google-for-your-start-up +created_at: 2007-03-20 +tags: + - General + - Blog + - Google +--- + +Google is more than a search engine or an advertising company. Of course, it's their core business, but Google has more to offer. In this article I'll describe how you can put Google to work for your start-up (or existing business!). Oh, did I mention you can do all of this for free? + +The most commonly known service Google provides besides search and advertising is Gmail. Nowadays Gmail is a common concept in the field of e-mail services. When the services started you got a 1Gb inbox. Today you can store up to around 2.8Gb of emails and Google Talk chat transcripts! Wouldn't it be great to use this for you company for free? + +Well you can! And a lot more! + +Most people may have heard about Google Apps. + +So, what is Google Apps and how can I put it to use for my start-up? + +When you visit Google Apps you can sign up for the "free" edition which will give you the following + +
        +
      1. Gmail with 2Gb inbox per user - You know how it works. Just for @yourdomain.com
      2. +
      3. Google Talk - Allow your users to chat easily with Google Talk
      4. +
      5. Google Calendar - Shared and personal calendars for free!
      6. +
      7. Docs & Spreadsheets - Write documents and spreadsheets easily online and collaborate with co-workers.
      8. +
      9. Page Creator - Creating a web site has never been easier!
      10. +
      11. Start Page - Know Google Personal Homepage? Same stuff but for yourdomain.com!
      12. +
      13. Easy administration panel to manager services and users
      14. +
      15. Unlimited number of users and online support
      16. +
      + +So, if you already own a domain name, or register one for a few bucks (or for $10 at Google), you can run email, instant messenging, calendar services, online office (docs & spreadsheets), page creator and a custom start page for your users for free! + +This will cost you $10 bucks a year (for the domain name when registered with Google). All you need is a computer, Internet connecton and a decent browser. + +How's that for an initial investment in your start-up company? Oh, don't forget that you can access all the above everywhere as long as you have an Internet connection! You can always (and easily) access your documents and e-mail or check your calendar. You can even do that with your mobile phone! + +Personally I think Google Apps are a great investment in your start-up company. I'll give you the pro's and cons one more time: + +Pro + +
        +
      • It's free (except for the domainname)
      • +
      • Quality service you expect from Google
      • +
      • You have it all under one roof ready to go.
      • +
      • Access to your stuff everywhere in the world!
      • +
      + +Con +
        +
      • Nothing really.
      • +
      + +Let me know if you have any experience with Google Apps. Did I miss some feature? Please let me know! \ No newline at end of file diff --git a/content/posts/2007-03-20-how-i-made-6-figures-with-google-adsense-in-10-days.md b/content/posts/2007-03-20-how-i-made-6-figures-with-google-adsense-in-10-days.md new file mode 100644 index 0000000..644591b --- /dev/null +++ b/content/posts/2007-03-20-how-i-made-6-figures-with-google-adsense-in-10-days.md @@ -0,0 +1,59 @@ +--- +title: "How I made 6 figures with Google Adsense in 10 days" +kind: article +slug: how-i-made-6-figures-with-google-adsense-in-10-days +created_at: 2007-03-20 +tags: + - General + - Blog + - Google + - AdSense + - Sarcasm +--- + +There has been a lot of talk about making money with Google Adsense. There are claims of people earning over $10.000 every month by just putting a few ads up on their site. + +I say they are amateurs! I'll show you how to make a 6-figure earning within 10 days after you put up ads on your site! Just follow these easy steps and you can quit your day job after just ten days of work. It' what I did. + + +

      Decide on a topic for your site

      + +First you must decide on what the topic of your site is. Try a keyword bid searching tool to find out which keywords and topics will generate the most. + +It's not important if you don't have any knowledge on the topic you will be writing about. It's just about making money. + +

      Register a domain

      + +Next you must register a domain and get your hosting setup. You can do this for a few bucks. This is the only investment you have to do here! For the $10 investment in hosting and domain registration you'll get a ROI of more than 100.000%. + +When choosing a domain, make sure it's catchy. Something like "earn-money-with-adsense-for-free.com" or "imearningsixfigureswithadsensesucker.com". It doesn't need to be readable it just needs to contain a lot of keywords for you site. + +Don't bother with quality hosting. Just find the cheapest reseller you can find. Make sure you get at least 5Tb of bandwidth, though. You'll need it. + +

      Setup your site

      + +Open up Frontpage and write an article. It only needs to be a few paragraphs. Make sure to include lots of keywords so Google can attach the right, high paying, ads. + +

      Add the ads!

      + +With your site in order now, add the maximum amount of ads allowed. For optimal results use the biggest ad format available and make sure a visitor sees the ads even before they reach the content of your site. This ensure the maximum clich-thru rate possible. + +Also include a link-block. This will lure visitors to a page with only ads from Google! This is a real gold mine! + +The third thing Google offers are referrals. Plaster some big images at the bottom. Claim how much you make with your site and have users sign-up for adsense too. This will make you $200 almost instantly! + +Don't forget the obligatory "Use Firefox" referral! + +

      Publish your site

      + +Now it's time to publish your site and start making the big bucks! It's almost time to quit your day job. + +Surf around and post a link to your site everywhere you can. Comment on any blog you see and put your link there. Roam forums and randomly insert links to your site there too. + +If you're feeling adventurous you may also setup an Adwords campaign yourself and pay for visitors. Since you have many high-paying ads on your site you can make money at a 1:10 ratio ($1 spend on adwords is $10 earned in Adsense) + +

      And now... you wait

      + +Now you wait ten days and then quit your job (you have a two weeks notice anyway). After those ten days you'll have made a nice 6 figures in hard currency. Just wait for the check to arrive and you're done! + +Oh, don't forget to ***** my *** since I gave you this very valuable information and I still need to reach my 6 figures for this month. Thank you. \ No newline at end of file diff --git a/content/posts/2007-03-21-26-things-you-can-do-with-an-old-pc.md b/content/posts/2007-03-21-26-things-you-can-do-with-an-old-pc.md new file mode 100644 index 0000000..7a3ab2a --- /dev/null +++ b/content/posts/2007-03-21-26-things-you-can-do-with-an-old-pc.md @@ -0,0 +1,55 @@ +--- +title: "26 Things you can do with an old PC" +kind: article +slug: 26-things-you-can-do-with-an-old-pc +created_at: 2007-03-21 +tags: + - General + - Blog + - Fun +--- + +We've all been there. Your old trusty PC dies. First thing you do is buy a new one. After you're done playing with all the new bells and whistles your old PC didn't have, it's time to thing what to do with the old fella. + +Here are some tips! + + +The whole system can be used like this: +
        +
      1. Give it away to friends or family who can use the "upgrade"
      2. +
      3. Take it apart and see what comes out
      4. +
      5. Create route/firewall for your network
      6. +
      7. Creata a file server
      8. +
      9. Install and try Linux
      10. +
      11. Create a jukebox / media player
      12. +
      13. Sell it
      14. +
      15. Trash it / Recycle it
      16. +
      17. Ceremonially set it on fire
      18. +
      19. See what happens when you shoot it
      20. +
      21. Symbolically throw it out of the window
      22. +
      +If you only have the casing, this can be put to great use! +
        +
      1. Take everthing out and turn it into a barbeque
      2. +
      3. See what happens when you don't cool your processor
      4. +
      5. Strip it and sell the working parts
      6. +
      7. Blow it up
      8. +
      +You an also strip the insides of your case and put another device in it: +
        +
      1. Coffee maker
      2. +
      3. Aquarium (with plexi glass plating, of course)
      4. +
      5. Webcam
      6. +
      7. Paper shredder
      8. +
      9. Use it as a trash can
      10. +
      11. Make it a mailbox
      12. +
      13. Build a can dispenser (hit 'reset' to obtain a beverage of your choice)
      14. +
      +The monitor (CRT) can be used for various purposes as well: +
        +
      1. This can also be converted into an aquarium
      2. +
      3. Remove the tube and create a cage for small animals
      4. +
      5. Ash tray
      6. +
      7. CD / DVD Rack
      8. +
      +Got your own personal favourite? Please post a comment and share it with us all! \ No newline at end of file diff --git a/content/posts/2007-03-22-rails-tip-snippet-logging-informational-messages-to-your-log.md b/content/posts/2007-03-22-rails-tip-snippet-logging-informational-messages-to-your-log.md new file mode 100644 index 0000000..48933e1 --- /dev/null +++ b/content/posts/2007-03-22-rails-tip-snippet-logging-informational-messages-to-your-log.md @@ -0,0 +1,27 @@ +--- +title: "Rails Tip Snippet: Logging informational messages to your log" +kind: article +slug: rails-tip-snippet-logging-informational-messages-to-your-log +created_at: 2007-03-22 +tags: + - General + - RubyOnRails + - Features + - TipSnippets +--- + +This "Rails Tip Snippet" is one in a series of small blocks of code that will make your life developing Rails applications a bit easier. + +This first snippet shows you how you can log informational message to your log file so you can track any important actions that happened. As an example you may want to log all user accounts that get deleted: + +
      def destroy
      +   @user = User.find(params[:id])
      +   @user.destroy!
      +   logger.info("User account '#{@user.login}' deleted by user #{current_user.login})
      +   ...
      +end
      +Line 4 will put a string like "User account 'johnlocke" delete by user ariejan" in your log file. Use that information any way you want. + +You may also use the logger to inspect variables: +
      logger.info("article inspection: #{@article.inspect}")
      +Until next time! Any tips are welcome at tips@ariejan.net. \ No newline at end of file diff --git a/content/posts/2007-03-23-be-productive-37-things-you-can-do-on-the-toilet.md b/content/posts/2007-03-23-be-productive-37-things-you-can-do-on-the-toilet.md new file mode 100644 index 0000000..ffdb88a --- /dev/null +++ b/content/posts/2007-03-23-be-productive-37-things-you-can-do-on-the-toilet.md @@ -0,0 +1,94 @@ +--- +title: "Be productive! 37 things you can do on the toilet" +kind: article +slug: be-productive-37-things-you-can-do-on-the-toilet +created_at: 2007-03-23 +tags: + - General + - Blog + - Features + - Lists + - Fun + - Productivity +--- + +We all have to go. Most of us once a day. Others have to do it more often. However, we can be much more productive while doing or daily duty at the toilet. + +Besides the old "joke book" and "newspaper" what can you do on the toilet to kill the time? I've found 37 things that might be a good option for you next number 2. + + +1. Newspaper - The age old classic of reading the newspaper. Nothing strage about that, right? + +2. Joke book - Some loos feature a book with jokes for you to read. Nice, but mostly the jokes are of a very low quality. + +3. Puzzle - Bring your sudoku book (any other type of puzzle will do) and start puzzling. + +4. Novels - If you find it hard make time to read that novel everyone has been talking about, read it on the toilet. It'll take a bit longer than usual, but you'll get there. + +5. Brainstorm - It's quiet out there. Use your time to brainstorm and get some new ideas for whatever you need new ideas for. A pen and paper are handy to write it all down. Otherwise you may flush your ideas with the rest. + +6. Redo your makeup - This one is for women only, but this is a nice place to redo your makeup with that travel-makeup-kit-thingy you always carry around. + +7. Poke your nose - This is also a classic one. Nobody pokes his nose in public (well, most of us don't) but if you like to clean it out, the toilet a good place to do it. No one sees you and you'll get to wash your hands afterwards. + +8. Listen to music - iPods are portable. Carry them with you at all times and listen to some tunes while sitting there. Other music devices such as mobile phones or mp3 players can be used as well. + +9. Listen to podcasts - If you subscribe to podcasts, this is the time to listen to a few of them. + +10. Watch some video - Have an iPod video or another portable video device such as the Playstation Portable? Make sure to set a clock, some movies run longer than 2 hours. + +11. Play a game - You can play games on mobile phones, PDAs, iPods and portable gaming devices such as the Playstation Portable or a GameBoy. This is the time to finally beat the machine or set a new high score. + +12. Learn stuff - Want to learn how to play Texas Hold'em poker? How to build your own kite? How to ... You get it. Print out an article or two, bring a book or even your laptop and read more on these subjects. + +13. Watch movie trailers - Don't have time to watch trailers? Put 'em on your iPod and watch while waiting. + +14. Chat - A lot of mobile devices have chat services. A laptop is the easiest way of chatting on the loo. If you want to talk, go ahead. + +15. Read your mail - Use you mobile phone, PDA or laptop to check your (personal) email. + +16. Check your stock - If you have stock feel free to check how much it's worth now. + +17. Blog - Want to write a blog post? Write it on the loo. If you don't have wireless access (you should get that right now!) you can submit the post when you come back. + +18. Tune your phone - This is more of a one time thing, but you may want to change your phones theme, background or ringtone every so often. + +19. Read news - If you don't have a newspaper feel free to read the news now on your laptop or PDA. + +20. Organise your schedule - While you have the time, plan all the activities you need to plan and mark any problems in your schedule so you can fix them later (or fix them right now!). + +21. Check Adsense - It's always a good time to check up on Adsense and todays earnings. + +22. Check your site statistics - Login to Analytics or view your logfiles and see if anything new is developing. + +23. Decide on dinner - Don't know what to eat after work? This is a time to decide on that. + +24. Install/update software - Are new updates for your favourite programs waiting, take a bit of time and install them now. + +25. Organise your to-do list - Check off items you have finished and add new ones you just thought off. + +26. See what movies are running - Feel like going to a movie? Check online what's running tonight. + +27. Ping your coffee machine - If you're a real geek, ping your coffee machine and brew a fresh pot for when you're finished here. + +28. Browse YouTube - Find a few movies you like and watch them. Laptop required. + +29. Uninstall software - Uninstall all applications you don't use to free up more space for your iTunes library. + +30. Organise your documents - Organise all your files. Put them in neat folders and rename them properly. + +31. SEO your site - Apply some quick patches to your site to improve SEO. + +32. Link! - Post a speedlinking post or add links to your blogroll for sites your recently visisted. + +33. Check free sites - Check those sites that collect free offers. Maybe something interesting has come up. + +34. Hack the wireless routes - Try hack the wireless router your currently using. Make sure you don't get fired over it! + +35. Plan your trip home - Use an online planner to plan your trip home. + +36. Work! - Need to finish a document fast? Continue working! + +37. Do nothing - Old fashioned Number 2. Just sit there, do your thing and go! + +Do you know other tasks you may perform while doing a number 2? Please continue the list in the comments. Thanksk for sharing! \ No newline at end of file diff --git a/content/posts/2007-03-25-speedlinking-top-28-startup-resources.md b/content/posts/2007-03-25-speedlinking-top-28-startup-resources.md new file mode 100644 index 0000000..05085ff --- /dev/null +++ b/content/posts/2007-03-25-speedlinking-top-28-startup-resources.md @@ -0,0 +1,43 @@ +--- +title: "Speedlinking: Top 28 startup resources" +kind: article +slug: speedlinking-top-28-startup-resources +created_at: 2007-03-25 +tags: + - General + - Blog + - Links +--- + +I've been reading a lot about startups this weekend. Here is a quick post with all the articles I came across this weekend and found worth mentioning: +
        +
      1. Startupping TIP
      2. +
      3. How to start a startup
      4. +
      5. Ten rules for web startups
      6. +
      7. Business 2.0: How to build a bulletproof startup
      8. +
      9. The 18 Mistakes that kill Startups
      10. +
      11. The Hardest Lessons for Startups to Learn
      12. +
      13. How to fund a startup
      14. +
      15. 5 Reasons to create your first startup
      16. +
      17. 9 Must reads before you start a startup
      18. +
      19. Little Known Ways to Brand on the Cheap: 99 Tips for Poor Web Startups
      20. +
      21. 6 Startup Lessons For The Year 2007
      22. +
      23. Tips for startup companies
      24. +
      25. Ideas for startups
      26. +
      27. 8 Simple Steps to Build Traffic For Your Internet Startup
      28. +
      29. 7 Tips for naming your Web 2.0 startup
      30. +
      31. 7 Rules for Web 2.0 Startups
      32. +
      33. Startup Website That Work
      34. +
      35. Hindsight 2.0: Lessons From A Failed Web 2.0 Startup
      36. +
      37. 10 Stupid Mistakes Made by the Newly Self-Employed
      38. +
      39. The 5 Most common mistakes made by startups
      40. +
      41. A Student's guide to startups
      42. +
      43. 10 Steps to a Hugely Successful Web 2.0 Company TIP
      44. +
      45. Startup Success: The Phenomenal Force Of Focus
      46. +
      47. Web 2.0 Startup: The Business Model TIP
      48. +
      49. Small Biz 101: How to Get Started TIP
      50. +
      51. 5 ways to start a company (without quitting your day job)
      52. +
      53. Startup Suicide: Five Ways To Kill Your Startup, Which Will You Pick?
      54. +
      55. Startups for the rest of us
      56. +
      +Got any articles that are not on the list? Please add a comment to let me know! \ No newline at end of file diff --git a/content/posts/2007-03-27-rails-tip-snippet-create-a-comma-seperate-list.md b/content/posts/2007-03-27-rails-tip-snippet-create-a-comma-seperate-list.md new file mode 100644 index 0000000..80419c3 --- /dev/null +++ b/content/posts/2007-03-27-rails-tip-snippet-create-a-comma-seperate-list.md @@ -0,0 +1,19 @@ +--- +title: "Rails Tip Snippet: Create a comma-seperate list" +kind: article +slug: rails-tip-snippet-create-a-comma-seperate-list +created_at: 2007-03-27 +tags: + - General + - RubyOnRails + - Features + - TipSnippets +--- + +Do you have the need to create a list of roles a certain user belongs to? Enumerate the users attached to a company? All you want is a simple list with the names seperated by commas. + +Users: John, Dick, Harry + +With Ruby on Rails this is really easy. You probably have a collection of user objects. All you want is a list of names: +
      @users.collect{|u| u.name}.join(', ')
      +Read more Tip Snippets? \ No newline at end of file diff --git a/content/posts/2007-03-27-subversion-how-to-revert-to-a-previous-revision.md b/content/posts/2007-03-27-subversion-how-to-revert-to-a-previous-revision.md new file mode 100644 index 0000000..bc3bb6a --- /dev/null +++ b/content/posts/2007-03-27-subversion-how-to-revert-to-a-previous-revision.md @@ -0,0 +1,15 @@ +--- +title: "Subversion: How to revert to a previous revision" +kind: article +slug: subversion-how-to-revert-to-a-previous-revision +created_at: 2007-03-27 +tags: + - General + - Features + - Subversion +--- +You've been there. You have been developing in your trunk for a while and at revision 127 you get the feeling you've done it all wrong! The production server is humming away at revision 123 and that's where you want to start out again. But how can you start again from revision 123? Easy as this with Subversion: + + svn merge -rHEAD:123 . + +This will see what changes you've made since r123 up until now (r127 in your case) and 'undo' them. Next you check in the code and you've go a sweet r128 that is exactly the same as r123. You can start over now! \ No newline at end of file diff --git a/content/posts/2007-03-29-5-reasons-why-pc-oems-should-offer-linux.md b/content/posts/2007-03-29-5-reasons-why-pc-oems-should-offer-linux.md new file mode 100644 index 0000000..d688576 --- /dev/null +++ b/content/posts/2007-03-29-5-reasons-why-pc-oems-should-offer-linux.md @@ -0,0 +1,27 @@ +--- +title: "5 Reasons why PC OEMs should offer Linux" +kind: article +slug: 5-reasons-why-pc-oems-should-offer-linux +created_at: 2007-03-29 +tags: + - General + - Blog + - Linux + - Opinion +--- + +DELL currently offers Linux as an option for certain laptop and desktop models. Talk is that this service may be expanded to all models. This is a good thing, of course. Results of a recent survey by DELL show that more than 70% of over 100.000 respondents want to use Linux for home and office! What has been keeping companies like DELL away from this? + +I've found five good reasons why OEMs should offer Linux as at least an option: + +1. Freedom of Choice You pay for your hardware. Its' yours. You should be able to use that hardware any way you want. The OEM should not force you to use anything else than what you want. + +2. It saves customers money. If a customer doesn't want to use Microsoft Windows? Why charge him for a license anyway? + +3. It's an unserved market. There are no mayor OEMs out there offering Linux as a default option. You get Microsoft Windows. In some cases you don't have to buy Windows and get a clean hard drive instead. People want to use Linux, but they don't want to go through the download-install-configure process. This is a big oppertunity, if you ask me. + +4. Your hardware gets more popular. We've seen this with Nvidia already. May Linux users buy Nvidia cards because nvidia offers superb Linux drivers. Plug in the cards, install the drivers and off you go. If a player like DELL would support their hardware like that, people would be willing to buy DELL instead of Brand X. Once the word gets out that Linux 'just works' on a DELL, you'll know that more customers will come your way. + +5. Current support options are sufficient. A majority of respondents to the Linux survey at DELL indicated that the current community support forums would suffice their needs. Besides that, there's a big Linux community out there willing to help new Linux users. + +This space is reserved for linking to a list of why Linux should be the default OS for OEMs and Windows an option. (Contact me if you want to post that list a a guest blogger to Ariejan.net). \ No newline at end of file diff --git a/content/posts/2007-04-02-what-do-you-want-for-autoflickr.md b/content/posts/2007-04-02-what-do-you-want-for-autoflickr.md new file mode 100644 index 0000000..aaf0dff --- /dev/null +++ b/content/posts/2007-04-02-what-do-you-want-for-autoflickr.md @@ -0,0 +1,18 @@ +--- +title: "What do you want for AutoFlickr?" +kind: article +slug: what-do-you-want-for-autoflickr +created_at: 2007-04-02 +tags: + - General + - Blog + - AutoFlickr +--- + +Almost a month ago I published my WordPress plugin AutoFlickr, which shows one or more related photos from the popular Flickr site in your post. + +Now I'm wondering what new features AutoFlickr users want. What parts of the current plugin are superfluous. Also, I'd like to know how is using AutoFlickr? + +Please post a comment with your wishes for AutoFlickr 1.1. People using AutoFlickr may contact me to let me know about their site. I will include a list of the ten most beautiful (my pick) sites that use AutoFlickr with the announcement of AutoFlickr 1.1. + +I hope for a lot of feedback to make this plugin even better. Thank you. \ No newline at end of file diff --git a/content/posts/2007-04-03-tipsnippet-create-a-rss-feed.md b/content/posts/2007-04-03-tipsnippet-create-a-rss-feed.md new file mode 100644 index 0000000..e829bc7 --- /dev/null +++ b/content/posts/2007-04-03-tipsnippet-create-a-rss-feed.md @@ -0,0 +1,52 @@ +--- +title: "TipSnippet: Create a RSS feed" +kind: article +slug: tipsnippet-create-a-rss-feed +created_at: 2007-04-03 +tags: + - General + - RubyOnRails + - Features + - TipSnippets +--- + +RSS is hot! So, you want to fit your new Rails app with one too! That's easy, of course, but you just need to know what to do. + +This snippet will show you how to create an RSS feed form your RESTful articles. I'll assume you know how to generate a resource named 'article' with a title, body and the default created_at and updated_at attributes. + +You'll first need to add a new collection to your resource in config/routes.rb +
      map.resources :articles, :collections => {:rss => :get}
      +This will expose your RSS feed as http://localhost:3000/articles;rss + +Create a corresponding action in the articles controller in app/controllers/articles_controller.rb. This method fetches the ten latest articles. +
      def rss
      +  @articles = Article.find(:all, :limit => 10, :order => 'created_at DESC')
      +  render :layout => false
      +end
      +I assume you render your articles in a layout. The render method here prevents your layout from rendering to create a plain XML file (which is what an RSS feed is). + +Next we create a view. This is not the regular RHTML you're used to but RXML. This enables the XML generator which we'll use to generate the RSS feed. Create app/views/articles/rss.rxml +
      xml.instruct! :xml, :version=>"1.0"
      +xml.rss(:version=>"2.0"){
      +  xml.channel{
      +    xml.title("My Great Blog")
      +    xml.link("http://www.example.com/")
      +    xml.description("My great blog about very interesting stuff!")
      +    xml.language('en-uk')
      +      for article in @articles
      +        xml.item do
      +          xml.title(article.title)
      +          xml.description(article.body)
      +          xml.author("support@example.com")
      +          xml.pubDate(article.created_at.strftime("%a, %d %b %Y %H:%M:%S %z"))
      +          xml.link(article_url(article))
      +          xml.guid(article_url(article))
      +        end
      +      end
      +  }
      +}
      +Well, that's it. You now have a working RSS feed! + +If you want to enable auto discovery, you should add the following line to the header of your layout. (Auto discovery enables that little RSS icon in the address bar of your browser.) +
      < %= auto_discovery_link_tag(:rss, :controller => 'articles', :action => 'rss') %>
      +Share and enjoy! Thank you. \ No newline at end of file diff --git a/content/posts/2007-04-12-rails-resources-and-permalinks.md b/content/posts/2007-04-12-rails-resources-and-permalinks.md new file mode 100644 index 0000000..ead81fc --- /dev/null +++ b/content/posts/2007-04-12-rails-resources-and-permalinks.md @@ -0,0 +1,68 @@ +--- +title: "Rails, Resources and Permalinks" +kind: article +slug: rails-resources-and-permalinks +created_at: 2007-04-12 +tags: + - General + - RubyOnRails + - Features +--- + +There has been quite a bit of discussion about creating permalinks with a rails resource. In this article I will show you how to create permalinks for a resource named 'pages' without giving up on any of the resource goodness! + +Before I start I'll presume you have a page scaffold_resource setup in your rails application. Make sure you have at least the following fields in your page model: +
      t.column :title, :string
      +t.column :permalink, :string
      +t.column :content, :text
      +Okay, what you want is the permalink_fu plugin. This plugin greatly simplifies the act of generating a permalink from a title. Install it first: +
      $ cd railsapp
      +$ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/permalink_fu/
      +In your Page model you may now add the following line. This line will generate a permalink in the permalink attribute automatically, so you don't have to show the permalink field in any forms. +
      has_permalink :title
      +That's it for generating the appropriate permalink string in your database. + +Rails goodness has already provided you with the basic RESTful routes: +
        +
      • /pages
      • +
      • /pages/123
      • +
      • /pages/new
      • +
      • /pages/123;edit
      • +
      +But what you really want, is something like: +
        +
      • /pages/perma-link-here
      • +
      +Notice that the permalink url is only a GET request and should not be used for editing or updating the page in question. + +Since using any other identifier than :id in a resource is madness, I create two new routes that will allow me to access permalinked pages. Not only that, but I do maintain the format option. Basically this means that you get three routes: +
        +
      • /page/perma-link-here
      • +
      • /page/perma-link-here.html
      • +
      • /page/perma-link-here.xml
      • +
      +Notice that I removed the 's' from 'pages' here. This is to avoid confusion with the resource 'pages'. But more on that later. + +Now in config/routes.rb add the following two lines: +
      map.permalink 'page/:permalink', :controller => 'pages', :action => 'permalink'
      +map.connect 'page/:permalink.:format', :controller => 'pages', :action => 'permalink', :format => nil
      +The first line adds a named route to an action named 'permalink' in your PagesController. This gives you the ability to add peralink links easily: +
      permalink_url(@page.permalink)
      +The second link is unnamed, and allows you to specify a format like HTML or XML. + +The permalink action looks like this: +
      # GET /page/perma-link
      +# GET /page/permal-link.xml
      +def permalink
      +  @page = Page.find_by_permalink(params[:permalink])
      +
      +  respond_to do |format|
      +    format.html { render :action => 'show' }
      +    format.xml  { render :xml => @page.to_xml }
      +  end
      +end
      +This special permalink action uses the same 'show' view as your resource. + +If you want to maintain the 'pages' part of the URL, that's possible. You'll have to write a condition that makes sure that the :permalink parameter is a string an not an integer (ID). This article does not cover this. + +You may now use permalinks for your pages! Congratulations. \ No newline at end of file diff --git a/content/posts/2007-04-29-ariejannet-server-move.md b/content/posts/2007-04-29-ariejannet-server-move.md new file mode 100644 index 0000000..4ff5db2 --- /dev/null +++ b/content/posts/2007-04-29-ariejannet-server-move.md @@ -0,0 +1,22 @@ +--- +title: "Ariejan.net server move" +kind: article +slug: ariejannet-server-move +created_at: 2007-04-29 +tags: + - General + - Ariejan.net + - Blog +--- + +Just a note to let you know that Ariejan.net has just been moved to a new server! Well, I've moved from shared hosting with Delta9 Internet to a VPS solution. + +Why on earth would I do such a thing? I'm very happy with the service I've recieved from Delta9 in the past. However, I wanted more control over my hosting. I'm a web developer and I have special needs. Such needs include Subversion and Ruby on Rails hosting, state-of-the art PHP and MySQL installations. PostgreSQL support and some other things. Concluding that for Ariejan.net, shared hosting was no longer an option. + +I could have taken a big step and buy myself a shiny new server all for my self, but that just doesn't fit into my budget at this time. When the time comes to host a dedicated server I will come back to Delta9 Internet. + +So, Ariejan.net has moved to a VPS solution for the time being. Other sites I run are still hosted at Delta9, so I don't abandon them all together. + +If you are looking for quality hosting with a personal touch, give Delta9 Internet a try! Tell 'em I send you :) + +I want to thank Delta9 Internet for hosting Ariejan.net for such a long time, and when the time's right... I'll be back! \ No newline at end of file diff --git a/content/posts/2007-05-09-rails-snippet-write-like-orwell-with-to_sentence.md b/content/posts/2007-05-09-rails-snippet-write-like-orwell-with-to_sentence.md new file mode 100644 index 0000000..30d4b53 --- /dev/null +++ b/content/posts/2007-05-09-rails-snippet-write-like-orwell-with-to_sentence.md @@ -0,0 +1,21 @@ +--- +title: "Rails Snippet: Write like Orwell with to_sentence" +kind: article +slug: rails-snippet-write-like-orwell-with-to_sentence +created_at: 2007-05-09 +tags: + - General + - RubyOnRails + - Features +--- + +A few weeks ago I posted an article that explained how to create a comma separated list from a hash of objects. When I was browsing the Rails API documentation I came across a method named to_sentence. + +What this little bugger does is create a human readable, comma separated list of items in an array or hash. But the big difference here is that you can specify what the last separator must be. By default this is set to 'and'. See the following example. +
      @users = User.find(:all)
      +@users.collect {|u| u.firstname}.to_sentence
      +=> "Tom, Dick, and Harry"
      +Of you course, you can specify the last separator, called the connector. Also it's possible to not show the last comma. +
      @users.collect {|u| u.firstname}.to_sentence(:connector => "and of course,", :skip_last_comma => true)
      +=> "tom, Dick and of course, Harry"
      +I bet this will greatly simplify the way you list names, tags, categories or whatever else you want summed up in a comma separated list with a human touch. \ No newline at end of file diff --git a/content/posts/2007-05-18-feedburner-acquired-by-google.md b/content/posts/2007-05-18-feedburner-acquired-by-google.md new file mode 100644 index 0000000..aca454b --- /dev/null +++ b/content/posts/2007-05-18-feedburner-acquired-by-google.md @@ -0,0 +1,14 @@ +--- +title: "FeedBurner acquired by Google!" +kind: article +slug: feedburner-acquired-by-google +created_at: 2007-05-18 +tags: + - General + - Blog + - Google +--- + +It looks like Google is about to acquire FeedBurner. Just a note to all of you to let you know. If you have a blog or RSS enabled website, burn your feed now and get your AdSense account ready! Soon you'll be able to put targeted ads in your RSS feed and add another source of income to your list! Great news! + +Please digg and share! Thank you! \ No newline at end of file diff --git a/content/posts/2007-05-21-merge-request-failed-on-pathtofile.md b/content/posts/2007-05-21-merge-request-failed-on-pathtofile.md new file mode 100644 index 0000000..253f3a7 --- /dev/null +++ b/content/posts/2007-05-21-merge-request-failed-on-pathtofile.md @@ -0,0 +1,22 @@ +--- +title: "MERGE request failed on ‘/path/to/file’" +kind: article +slug: merge-request-failed-on-pathtofile +created_at: 2007-05-21 +tags: + - General + - Features + - Subversion + - Trac +--- + +After upgrading my Subversion server to Ubuntu Feisty, I noticed that when committing I got the following error: +
      svn: MERGE request failed on '/svn/repository/trunk'
      +svn: MERGE of '/svn/repository/trunk': 200 OK (http://svn.myserver.com)
      +Although the messages says that the commit failed, it has not. A simple 'svn update' will merge the changes you made to the repository to your working copy again and you're good to go. + +So, what is the problem here? Are you by any chance running Trac? Did you install the post-commit hook to integrate Subversion with Trac? Right, so did I. + +The problem here is that the trac-post-commit-hook script needs a few updates in order to include the proper modules. Take a look at the most recent 0.10.x version. + +After updating the trac-post-commit-hook script, commits worked fine again. Such an easy fix for such a nasty problem. \ No newline at end of file diff --git a/content/posts/2007-05-28-bat-ter-y.md b/content/posts/2007-05-28-bat-ter-y.md new file mode 100644 index 0000000..4d51794 --- /dev/null +++ b/content/posts/2007-05-28-bat-ter-y.md @@ -0,0 +1,16 @@ +--- +title: "BAT - TER - Y" +kind: article +slug: bat-ter-y +created_at: 2007-05-28 +tags: + - General + - Blog + - Fun +--- + +You all know the word: "Battery". Like most words, it has several meanings. This site greatly explains the meaning of the word battery, and adds a little lyric-sugar to it! + +Come on boys and girls! Sing along to BAT • TER • Y. + +(Please digg the site or post a link to it. Thanks!) \ No newline at end of file diff --git a/content/posts/2007-05-29-installing-rmagick-ruby-gem-on-mac-os-x-1049.md b/content/posts/2007-05-29-installing-rmagick-ruby-gem-on-mac-os-x-1049.md new file mode 100644 index 0000000..3dd7456 --- /dev/null +++ b/content/posts/2007-05-29-installing-rmagick-ruby-gem-on-mac-os-x-1049.md @@ -0,0 +1,68 @@ +--- +title: "Installing RMagick Ruby Gem on Mac OS X 10.4.9" +kind: article +slug: installing-rmagick-ruby-gem-on-mac-os-x-1049 +created_at: 2007-05-29 +tags: + - General + - RubyOnRails + - Features + - Mac OS X + - Ruby +--- + +When you want to manipulate images with Ruby (or your Rails application) you'll probably want RMagick installed. This is no easy feat on Mac OS X. + +The official guide suggests installing X11 and using darwinports to install everything. This guide shows you how to easily install RMagick on you Mac OS X system. In this case I use Mac OS X 10.4.9. + +Before you jump in, make sure you have Xcode installed. You can get it for free from Apple. + +I'll also assume you have Ruby and rubygems installed and working already. + +You will need to download, compile and install several graphics libraries that RMagick needs. Let's do this now. +
      curl -O http://download.savannah.gnu.org/releases/freetype/freetype-2.1.10.tar.gz
      +tar xzvf freetype-2.1.10.tar.gz
      +cd freetype-2.1.10
      +./configure --prefix=/usr/local
      +make
      +sudo make install
      +cd ..
      +
      +curl -O http://superb-west.dl.sourceforge.net/sourceforge/libpng/libpng-1.2.10.tar.bz2
      +bzip2 -dc libpng-1.2.10.tar.bz2 | tar xv
      +cd libpng-1.2.10
      +./configure --prefix=/usr/local
      +make
      +sudo make install
      +cd ..
      +
      +curl -O ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
      +tar xzvf jpegsrc.v6b.tar.gz
      +cd jpeg-6b
      +ln -s `which glibtool` ./libtool
      +export MACOSX_DEPLOYMENT_TARGET=10.4
      +./configure --enable-shared --prefix=/usr/local
      +make
      +sudo make install
      +cd ..
      +
      +curl -O ftp://ftp.remotesensing.org/libtiff/tiff-3.8.2.tar.gz
      +tar xzvf tiff-3.8.2.tar.gz
      +cd tiff-3.8.2
      +./configure --prefix=/usr/local
      +make
      +sudo make install
      +cd ..
      +Next we install ImageMagick: +
      curl -O http://easynews.dl.sourceforge.net/sourceforge/imagemagick/ImageMagick-6.3.0-0.tar.gz
      +tar xzvf ImageMagick-6.3.0-0.tar.gz
      +cd ImageMagick-6.3.0
      +./configure --prefix=/usr/local
      +make
      +sudo make install
      +cd ..
      +And now, ladies and gentlemen, what you've all been waiting for: RMagick: +
      sudo gem install --no-rdoc --no-ri RMagick
      +In my case the generation of the documentation fails, so I tell rubygems not to compile the docs. + +You now have RMagick installed on you Mac OS X 10.4.9 machine! Congratulations! \ No newline at end of file diff --git a/content/posts/2007-05-29-slow-connections-with-proftpd.md b/content/posts/2007-05-29-slow-connections-with-proftpd.md new file mode 100644 index 0000000..c011710 --- /dev/null +++ b/content/posts/2007-05-29-slow-connections-with-proftpd.md @@ -0,0 +1,19 @@ +--- +title: "Slow connections with ProFTPD" +kind: article +slug: slow-connections-with-proftpd +created_at: 2007-05-29 +tags: + - General + - Features + - Linux + - Ubuntu +--- + +My shiny new VPS, which is running Ubuntu Linux, uses ProFTPD for FTP access. Today I noticed that setting up the connection takes about 5 to 10 seconds. This is really annoying when editing files through FTP. + +So, I investigated and found that by default ProFTPD tries to revolve the hostname of the client in order to put that in the logs instead of a plain IP address. This lookup can take quite some time, let's say 5 to 10 seconds, especially when the look up fails and you have to wait on a time-out. + +It's easy to stop ProFTPD from behaving like this by adding the following line to your proftpd.conf in /etc/proftpd: +
      IdentLookups off
      +Restart ProFTPD and you'll have a fast FTP connection to enjoy! \ No newline at end of file diff --git a/content/posts/2007-05-30-trac-webadmin-plugin-and-global-configuration.md b/content/posts/2007-05-30-trac-webadmin-plugin-and-global-configuration.md new file mode 100644 index 0000000..58f69f0 --- /dev/null +++ b/content/posts/2007-05-30-trac-webadmin-plugin-and-global-configuration.md @@ -0,0 +1,55 @@ +--- +title: "Trac, WebAdmin plugin and global configuration" +kind: article +slug: trac-webadmin-plugin-and-global-configuration +created_at: 2007-05-30 +tags: + - General + - Features + - Linux + - Ubuntu + - Trac +--- + +As you may know I manage quite a few trac installations. A few days ago I upgrade my server from Ubuntu "Dapper Drake" 6.06 to Ubuntu "Feisty Fawn" 7.04. This also upgrade trac 0.9.x to 0.10.3. + +I was happy, since trac 0.10.3 has many improvements over 0.9.x, but there was one thing I was not so happy about. After the upgrade, I upgraded all my trac installations and everything seemed to be okay, except for the WebAdmin plugin. Apparently it was not installed anymore. + +What happened? After upgrading the trac package, the plugins directory was emptied. Well, just re-install the WebAdmin plugin for 0.10.x. + +
      cd /usr/share/trac/plugins
      +sudo svn co http://svn.edgewall.org/repos/trac/sandbox/webadmin/
      +cd webadmin
      +sudo python setup.py bdist_egg
      +cd dist
      +sudo easy_install-2.4 TracWebAdmin-0.1.2dev_r4429-py2.4.egg
      +That was easy, next I wanted to enable the plugin for all my trac installations by adding the proper configuration to /usr/share/trac/conf/trac.ini, the global trac configuration file that is used by all trac installs. +
      [components]
      +webadmin.* = enabled
      +After restarting Apache (this is needed for some reason to get trac to read the new configuration file), no admin button showed up in any of the projects. + +What went wrong is that Ubuntu (or Debian?) maintainers have changed the location of the global configuration file for trac. There are three solutions to this, all of them work fine, although I recommend you use the first one. + +1. Move your global configuration + +The best way to tackle this problem is to move your global configuration file to the new location: /etc/trac/trac.ini +
      sudo mv /usr/share/trac/conf/trac.ini /etc/trac/trac.ini
      +This way you're configuration is safe from new upgrades and confirms to the defaults the package maintainer has set. + +2. Symlink the configuration + +If for some reason you don't want to actually move your /usr/share/trac/conf/trac.ini file, you can create a symlink to the new location: +
      sudo ln -sf /usr/share/trac/conf/trac.ini /etc/trac/trac.ini
      +
      +
      +This leaves your original configuration file in tact, but it may be removed by new upgrades.
      +
      +3. Change Trac
      +
      +You may also change the location where trac looks for the configuration file. Open up /var/lib/python-support/python2.5/trac/siteconfig.py and change the following:
      +
      < __default_conf_dir__ = '/etc/trac'
      +> __default_conf_dir__ = '/usr/share/trac/conf'[/pre]
      +
      +(Note: the > and < symbols mark what is removed and what is added to the file.)
      +
      +In any case, reboot your web server and you should be good to go again.
      \ No newline at end of file diff --git a/content/posts/2007-06-07-coming-up-ubuntu-development-server-guide.md b/content/posts/2007-06-07-coming-up-ubuntu-development-server-guide.md new file mode 100644 index 0000000..4693b51 --- /dev/null +++ b/content/posts/2007-06-07-coming-up-ubuntu-development-server-guide.md @@ -0,0 +1,23 @@ +--- +title: "Coming up: Ubuntu Development Server Guide" +kind: article +slug: coming-up-ubuntu-development-server-guide +created_at: 2007-06-07 +tags: + - General + - Blog + - Web Development + - Linux + - Ubuntu + - Subversion +--- + +My articles about setting up a Ubuntu Development Server (part 1 and part 2) have been very successful. + +I'm considering writing a new guide with more up-to-date information on how to setup a development server that allows you (and your team) to develop software, manage source code, track tickets and all that stuff. + +What should, according to you, be included in this guide? Please let me know! Just leave a comment and I'll pick it up. + +Thank you for your input! + +Update: Estimated release of the "Ubuntu Develpment Server Guide is mid july \ No newline at end of file diff --git a/content/posts/2007-06-10-find-and-replace-with-a-mysql-query.md b/content/posts/2007-06-10-find-and-replace-with-a-mysql-query.md new file mode 100644 index 0000000..bf73726 --- /dev/null +++ b/content/posts/2007-06-10-find-and-replace-with-a-mysql-query.md @@ -0,0 +1,19 @@ +--- +title: "Find and Replace with a MySQL Query" +kind: article +slug: find-and-replace-with-a-mysql-query +created_at: 2007-06-10 +tags: + - General + - Databases + - Features + - MySQL +--- + +There are times when you have a lot of data in a database (let's say wp_posts for a Wordpress blog like Ariejan.net). When you need to find and replace certain strings, this can be a very tedious task. Find all posts containing the "needle" string and manually replace all these occurrences with "chocolate". With about 200 posts, you can imagine how long this would take to do manually. + +But, as I always say: "You're a programmer! You should script the hell out of everything!" + +So, I found this: MySQL has built-in support to find and replace! Just a simple query will do: +
      UPDATE wp_posts set post_body = replace(post_body, 'needle', 'chocolate');
      +That's it. The entire table 'wp_posts' is searched and all occurrences of "needle" are replaced with "chocolate". The query only took about a split second. \ No newline at end of file diff --git a/content/posts/2007-06-10-send-mail-with-a-bash-shell-script.md b/content/posts/2007-06-10-send-mail-with-a-bash-shell-script.md new file mode 100644 index 0000000..dcc6a87 --- /dev/null +++ b/content/posts/2007-06-10-send-mail-with-a-bash-shell-script.md @@ -0,0 +1,42 @@ +--- +title: "Send mail with a BASH Shell Script" +kind: article +slug: send-mail-with-a-bash-shell-script +created_at: 2007-06-10 +tags: + - General + - Features + - BASH + - Shell Scripts +--- + +Like any good programmer, I try to automate the crap out of everything. If you have to do it more than once, I try to write a script for it. + +This time I want to show you how you can easily send an e-mail from a BASH script. The idea is that you want the script to send out an email to notify a user that something has happened. + +We're going to use the GNU Mail utility here. The basic syntax to send an email is like this: +
      /usr/bin/mail -s "Subject" someone@example.org < message.txt
      +The trick when using this in a shell script is creating and using the message.txt file correctly. + +Let's setup the basis first: +
      #!/bin/bash
      +SUBJECT="Automated Security Alert"
      +TO="alarms@ariejan.net"
      +MESSAGE="/tmp/message.txt"
      +
      +/usr/bin/mail -s "$SUBJECT" "$TO" < $MESSAGE
      +All we need to do now is create the message. In this example we're going to notify the receiver that something happened at a certain time. We can use the append (>>) operator to add text to the message file. Afterwards, we must remove the temporary message file, of course. The complete script now becomes: +
      #!/bin/bash
      +SUBJECT="Automated Security Alert"
      +TO="alarms@ariejan.net"
      +MESSAGE="/tmp/message.txt"
      +
      +echo "Security breached!" >> $MESSAGE
      +echo "Time: `date`" >> $MESSAGE
      +
      +/usr/bin/mail -s "$SUBJECT" "$TO" < $MESSAGE
      +
      +rm $MESSAGE
      +The email will contain the a timestamp from when the mail was sent. + +This method is great for letting an administrator now if something happened. Maybe you need to check if your webserver is up and running. This script can an administrator about the issue. \ No newline at end of file diff --git a/content/posts/2007-06-10-ultimate-list-of-ruby-resources.md b/content/posts/2007-06-10-ultimate-list-of-ruby-resources.md new file mode 100644 index 0000000..2196c0c --- /dev/null +++ b/content/posts/2007-06-10-ultimate-list-of-ruby-resources.md @@ -0,0 +1,50 @@ +--- +title: "Ultimate List of Ruby Resources" +kind: article +slug: ultimate-list-of-ruby-resources +created_at: 2007-06-10 +tags: + - General + - Web Development + - RubyOnRails + - Features + - Lists + - Ruby +--- + +This is the first post, named "Ruby", in a series of "Ultimate List of ... Resources". I'm going to compose several lists for different topics I encounter during my development work. To start, I begin with Ruby. Later, I will add "Ultimate Lists" about Ruby on Rails, Subversion, AJAX and some other topics. + +Feel free to let me know if I missed an important resource. I'm also open to suggestions about other "Ultimate Lists". + +For now, you'll have to settle for the "Ultimate List of Ruby Resources". + + +Books +
        +
      1. "Programming Ruby: The Pragmatic Programmers' Guide, Second Edition" by Dave Thomas, Chad Fowler and Andy Hunt
      2. +
      3. "The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming" by Hal Fulton
      4. +
      5. "Ruby Cookbook" by Lucas Carlson, Leonard Richardson
      6. +
      7. "Everyday Scripting with Ruby: For Teams, Testers, and You" by Brian Marick
      8. +
      9. "Beginning Ruby: From Novice to Professional" by Peter Cooper
      10. +
      11. "Ruby In A Nutshell" by Yukihiro Matsumoto
      12. +
      13. "Enterprise Integration with Ruby" by Maik Schmidt
      14. +
      +Online manuals and API's +
        +
      1. Noobkit — Easily searchable Ruby API
      2. +
      3. Core API documentation
      4. +
      5. Ruby Standard Library
      6. +
      +Groups, chat, forums, wiki and other community websites +
        +
      1. Ruby Forum: Ruby
      2. +
      3. Ruby Forum: Ruby Core
      4. +
      5. comp.lang.ruby — Newsgroup
      6. +
      7. Ruby-talk-google — Google Group
      8. +
      +Ruby Blogs (is yours listed yet? — Let me know!) +
        +
      1. O'Reilly Ruby Blog
      2. +
      3. Ruby Inside
      4. +
      5. RubyCorner
      6. +
      \ No newline at end of file diff --git a/content/posts/2007-06-11-activescaffold-acts_as_taggable_on_steroids.md b/content/posts/2007-06-11-activescaffold-acts_as_taggable_on_steroids.md new file mode 100644 index 0000000..862e535 --- /dev/null +++ b/content/posts/2007-06-11-activescaffold-acts_as_taggable_on_steroids.md @@ -0,0 +1,63 @@ +--- +title: "ActiveScaffold, Acts_as_taggable_on_steroids" +kind: article +slug: activescaffold-acts_as_taggable_on_steroids +created_at: 2007-06-11 +tags: + - General + - RubyOnRails + - Features + - ActiveScaffold +--- + +Update: also read Active Scaffold + Acts_as_taggable + Auto Completion. + +This is kind of an advanced topic, but I think it may be useful to a lot of people. + +ActiveScaffold is a great plugin to start building a user interface. The great thing about AS is, that is automatically recognizes associated models. When editing a model, you can easily add or select another model that you want to associate with is. + +The best example of this is an Article, where you can select the author (the associated User model) with a drop down box. + +There is only one point where I ran into trouble with ActiveScaffold: acts_as_taggable_on_steroids. + +Acts_as_taggable_on_steroids allows you to easily attach tags to models and do all kinds of crazy stuff with them. But, if you want to integrate in into AcitveScaffold, you're in for a tough ride. + +ActiveScaffold supports has_many :through associations, but not in a way that is compatible with acts_as_taggable_on_steroids. Let me show you. + +In your ArticlesController you specify which columns to show. "tag_list" is a stringified version of the tags associated with the Article, which is great for showing to a user. + +However, if you want to edit it an article (or create one), I don't want a text field where I have to enter tags manually, all I want are a bunch of check boxes, so I can check which tags apply to this article. + +Showing the check boxes is easy with AS. By default I show 'tags', only in the list view do I use 'tag_list' instead. Also, make sure to set the ui_type for the tags column to :select. This will show you check boxes, instead of a sub form that allows you to create tags manually. +
      active_scaffold :article do |config|
      +    config.columns = [:title, :body, :tags, :author, :created_at]
      +    config.list.columns = [:title, :author, :tag_list, :created_at]
      +    config.columns[:tags].ui_type = :select
      +end
      +Well, very nice, right. You can now happily select the tags you want, and save your article. Not. + +As you may have noticed, the tags are not saved. Why? Acts_as_taggable adds a 'tags' attribute to the model, however, when the Article model is saved, the tags attribute is overwritten by the tags specified in the "tags_list" attribute. + +The only way to solve this is to convert the tags selected in AS and store them as the tags_list attribute for the Article. + +First, let's add a private method in the ArticleController class: +
      private
      +
      +def new_tag_list(tag_ids)
      +    tag_ids.map {|k,h| h['id']}.collect {|i| Tag.find(i)}.map do |tag|
      +      tag.name.include?(Tag.delimiter) ? "\"#{tag.name}\"" : tag.name
      +    end.join(Tag.delimiter.ends_with?(" ") ? Tag.delimiter : "#{Tag.delimiter} ")
      +end
      +And add two protected methods that extend the functionality of ActiveScaffold: +
      protected
      +
      +def before_create_save(record)
      +    record.tag_list = new_tag_list(params[:record][:tags])
      +end
      +
      +def before_update_save(record)
      +    record.tag_list = new_tag_list(params[:record][:tags])
      +end
      +This will take the actual form values from AS and create a tags_list. This new tags_list is then assigned to the article (named 'record' here). The two protected methods process the tags every time an Article is created or updated. + +With this in place, you can happily assign tags to your articles! Please let me know if it worked for you, or if you have made any improvements to this solution. \ No newline at end of file diff --git a/content/posts/2007-06-12-bash-your-svn-and-trac-installation.md b/content/posts/2007-06-12-bash-your-svn-and-trac-installation.md new file mode 100644 index 0000000..3b67c69 --- /dev/null +++ b/content/posts/2007-06-12-bash-your-svn-and-trac-installation.md @@ -0,0 +1,71 @@ +--- +title: "BASH your SVN and Trac installation!" +kind: article +slug: bash-your-svn-and-trac-installation +created_at: 2007-06-12 +tags: + - General + - Features + - BASH +--- + +I've already discussed how to install Subversion and Trac on your Ubuntu server. In my case I have a server that manages different SVN and Trac installations for a group of developers. + +Creating a new SVN repository and Trac installation every time is quite boring and "if you need to do it more than once, you should automate it". So, that's what I did. + +The result is the following BASH script. It takes one argument, the name of the project you want to create. E.g if you wanted to create a SVN repository and trac installation for "My Project" you would run the following command: +
      $ ./create_dev_env my_project
      +The script it self looks like this: +
      #!/bin/sh
      +echo == Creating Subversion and Trac installation for $1
      +echo  = Creating SVN Repository...
      +
      +# Subversion
      +cd /var/lib/svn
      +mkdir -p /var/lib/svn/$1
      +svnadmin create /var/lib/svn/$1
      +sed s/EXAMPLE/$1/g /usr/share/trac/contrib/post-commit > /var/lib/svn/$1/hooks/post-commit
      +chmod +x /var/lib/svn/$1/hooks/post-commit
      +chown -R www-data:www-data /var/lib/svn/$1
      +
      +# Trac
      +echo  = Creating Trac install...
      +cd /var/lib/trac
      +mkdir -p /var/lib/trac/$1
      +
      +echo  - Creating files
      +trac-admin /var/lib/trac/$1 initenv $1 sqlite:db/trac.db svn \
      +/var/lib/svn/$1 /usr/share/trac/templates
      +
      +echo  - Removing anonymous permissions
      +trac-admin /var/lib/trac/$1 permission remove anonymous  BROWSER_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  CHANGESET_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  FILE_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  LOG_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  MILESTONE_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  REPORT_SQL_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  REPORT_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  ROADMAP_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  SEARCH_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  TICKET_CREATE
      +trac-admin /var/lib/trac/$1 permission remove anonymous  TICKET_MODIFY
      +trac-admin /var/lib/trac/$1 permission remove anonymous  TICKET_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  TIMELINE_VIEW
      +trac-admin /var/lib/trac/$1 permission remove anonymous  WIKI_CREATE
      +trac-admin /var/lib/trac/$1 permission remove anonymous  WIKI_MODIFY
      +trac-admin /var/lib/trac/$1 permission remove anonymous  WIKI_VIEW
      +
      +echo  - Creating Trac admins
      +trac-admin /var/lib/trac/$1 permission add ariejan TRAC_ADMIN
      +
      +chown -R www-data:www-data /var/lib/trac/$1
      +
      +echo
      +echo == Done.
      +First it creates the SVN directory in /var/lib/svn/my_project and creates repository and adds the trac post-commit hook for trac integration. + +Next, it creates the trac installation in /var/lib/trac/my_project and removes all the persmission the anonymous users has. (You may want to remove these lines for open source or public projects.) + +And, finally, I'm added as an administrator to the project. Make sure to replace this with you own username. + +Hope you find this script useful. Any improvements are welcome, please let me know. \ No newline at end of file diff --git a/content/posts/2007-06-14-buy-it-now-mac-os-x-105-leopard.md b/content/posts/2007-06-14-buy-it-now-mac-os-x-105-leopard.md new file mode 100644 index 0000000..9743672 --- /dev/null +++ b/content/posts/2007-06-14-buy-it-now-mac-os-x-105-leopard.md @@ -0,0 +1,16 @@ +--- +title: "Buy it now! Mac OS X 10.5 Leopard" +kind: article +slug: buy-it-now-mac-os-x-105-leopard +created_at: 2007-06-14 +tags: + - General + - Blog + - Mac OS X +--- + +It appears that Amazon is accepting pre-orders for Apple Mac OS X Version 10.5 Leopard, which is scheduled for release in October 2007. + +There are, as usual two version available, Apple Mac OS X Version 10.5 Leopard at $129 for a single license and the Apple Mac OS X Version 10.5 Leopard Family Pack for $199, which includes licenses for up to five Macs in your home. + +If you want to be sure you have the latest version of when it arrives: pre-order now! \ No newline at end of file diff --git a/content/posts/2007-06-19-geslaagd-passed-my-final-exams.md b/content/posts/2007-06-19-geslaagd-passed-my-final-exams.md new file mode 100644 index 0000000..7045853 --- /dev/null +++ b/content/posts/2007-06-19-geslaagd-passed-my-final-exams.md @@ -0,0 +1,39 @@ +--- +title: "Geslaagd! / Passed my final exams!" +kind: article +slug: geslaagd-passed-my-final-exams +created_at: 2007-06-19 +tags: + - General + - Ariejan.net + - Blog +--- + +Please scroll down for the English version. + +Bij wijze van hoge uitzondering, een post in het Nederlands op mijn weblog! Ik ben vandaag geslaagd voor mijn opleiding Hogere Informatica (aan Fontys Hogeschool ICT, Eindhoven) en mag mezelf nu gediplomeerd Software Engineer noemen! + +Ik ben op drie punten beoordeeld: +
        +
      • Eindrapport: 7,5 – Ik ben er gewoon niet goed in, maar het is toch nog wat redelijks geworden.
      • +
      • Eindpresentatie: 8 – Het ging gewoon lekker
      • +
      • Resultaat: 9 – Dit slaat op het werk dat ik heb afgeleverd, mijn inzet, zelfstandigheid etc.
      • +
      +Samen komt dit uit op een eindcijfer 8. Natuurlijk ben ik hier heel trots op, en ga het in ieder geval de rest van de dag vieren! + +Feliciteren kan in 'n commentaartje :P + +Read more for the English version + + +
      Today I passed my final exams for my degree in Software Engineering. I've been working on a Ruby on Rails project called "Course Builder" for the past few months. Today, I had to defend my work for a jury.In the Netherlands students are judged on 1 to 10 scale, where 10 is 'total perfection', 6 is 'seems okay' and 1 is total nonsense. + +I've been judged on three subjects: +
        +
      • Final report: 7.5 – I'm not very good at writing reports, but it's above average, still.
      • +
      • Presentation: 8 – It went very well. I'm so happy with my MacBook and KeyNote
      • +
      • Results: 9 – The results of my work and how I approached the project as a whole
      • +
      +The final grade is an 8. I'm very happy with the over-all result and I'll be celebrating the rest of the day. + +You may congratulate me in a comment :P \ No newline at end of file diff --git a/content/posts/2007-06-20-action-mailer-all-mail-comes-from-mailer-daemon.md b/content/posts/2007-06-20-action-mailer-all-mail-comes-from-mailer-daemon.md new file mode 100644 index 0000000..187c959 --- /dev/null +++ b/content/posts/2007-06-20-action-mailer-all-mail-comes-from-mailer-daemon.md @@ -0,0 +1,25 @@ +--- +title: "Action Mailer: All mail comes from MAILER DAEMON" +kind: article +slug: action-mailer-all-mail-comes-from-mailer-daemon +created_at: 2007-06-20 +tags: + - General + - RubyOnRails + - Features + - Ruby +--- + +Today I was trying to send mail from my Rails application through Action Mailer. This is quite simple, but I wanted to use a custom from-address. So, I create a setup_email method in my UserNotifier class that sets some defaults for every email sent out: +
      class UserNotifier < ActionMailer::Base
      +  protected
      +    def setup_email(user)
      +      @recipients  = "#{user.email}"
      +      @from        = "My Application 
      +    end
      +end
      +May you spotted the problem already, but I didn't. All the mail sent came from "MAILER DAEMON". +
      From: MAILER DAEMON
      +The problem was that @from didn't contain a properly formated from-address. It is missing the closing >, and so my email server ignores it. + +If you have this issue, double check the from address, and make sure it's valid! Cheers. \ No newline at end of file diff --git a/content/posts/2007-06-20-rails-production-server-setup-and-deployment-on-ubuntudebian.md b/content/posts/2007-06-20-rails-production-server-setup-and-deployment-on-ubuntudebian.md new file mode 100644 index 0000000..3b3580d --- /dev/null +++ b/content/posts/2007-06-20-rails-production-server-setup-and-deployment-on-ubuntudebian.md @@ -0,0 +1,215 @@ +--- +title: "Rails production server setup and deployment on Ubuntu/Debian" +kind: article +slug: rails-production-server-setup-and-deployment-on-ubuntudebian +created_at: 2007-06-20 +tags: + - General + - RubyOnRails + - Features + - Linux + - Ubuntu +--- + +Please digg this story to spread the word! Thanks! + +Okay, this is a big one! This article will show you (and explain to you) how to setup a Ruby on Rails production server with Ubuntu 7.04 or Debian 4.0 and how to deploy your Rails application there. + +First, what's getting installed: +
        +
      • Ruby 1.8.5
      • +
      • Ruby on Rails 1.2.3
      • +
      • Subversion 1.4
      • +
      • MySQL 5.x Server
      • +
      • Apache 2.2.x
      • +
      • Mongrel Cluster
      • +
      +I assume that you have just installed a fresh system with Ubuntu Linux 7.04 or Debian 4.0. If you haven't, do so now! You don't need to install the "DNS" or "LAMP" server in Ubuntu. Just a minimal system is enough for this tutorial. + +I'll be deploy an imaginary Rails application named "myapp" which uses MySQL and is stored in Subversion. More on that later on. + +Well, let's get going and get that Ruby on Rails server ready. + +

      Update your system

      +Before you do anything, use apt-get to update your system to the latest possible version. +
      sudo apt-get update
      +sudo apt-get dist-upgrade
      +This probably installs a new kernel (linux-kernel) so a reboot may be in order for optimal performance. +

      Enable SSH

      +Most people will want to have SSH on their server to login remotely. In this case I install both the server and client so you can SSH out if you need to: +
      $ sudo apt-get install openssh-server openssh-client
      +You will now be able to login remotely wiht SSH. + +You'll need SSH later if you want to use Capistrano to deploy your Ruby on Rails application. In any case, SSH is a good thing to have around. +

      Subversion

      +If you are serious about your Ruby on Rails server, you want to have Subversion around. Most deployment scripts pull the latest revision of your code from subversion. No configuration needed here. +
      $ sudo apt-get install subversion
      +We only need the client on the production server. We're not going to host Subversion repositories here. +

      Install MySQL Server

      +This is the first serious step you'll have to take. Both Ubuntu 7.04 and Debian 4.0 come with MySQL 5.0.x. +
      $ sudo apt-get install mysql-server mysql-client libmysqlclient15-dev
      +Be sure to set a password for the root MySQL user. Failing to do so will leave your database open for anyone who wishes to see. +
      $ mysqladmin -u root -h localhost password 'secret'
      +$ mysqladmin -u root -h myhostname password 'secret'
      +Make sure to replace secret with your actual password. + +Try logging in to MySQL with your new password to make sure everything works okay. +
      $ mysql -u root -p
      +Enter password:
      +mysql>
      +MySQL is in place now, so let's get cracking at Ruby and Rails now. +

      Ruby, Gems, Rails

      +Installing Ruby is quite easy: +
      $ sudo apt-get install ruby
      +You'll now have Ruby 1.8.5. You will also need to install some other develoment package to help you build native Ruby Gems. +
      $ sudo apt-get install make autoconf gcc ruby1.8-dev build-essentials
      +I'll install ruby gems the conventional way (so I'm not going to use Ubuntu's packages here). Download the latest Gems .tgz here. +
      $ wget http://rubyforge.org/frs/download.php/17190/rubygems-0.9.2.tgz
      +$ tar xvf rubygems-0.9.2.tgz
      +$ cd rubygems-0.9.2/
      +$ sudo ruby setup.rb
      +$ gem -v
      +That's it for the gems. Now, install Rails an all it's dependencies: +
      $ sudo gem install rails --include-dependencies
      +If you get an error message complaining that the 'rails' gem cannot be found include the --remote option. +

      Oh no! More gems!

      +Next, let's install some essential Ruby Gems that will make your life quite a bit easier. Here we'll install the following gems: +
        +
      • mysql - For good MySQL connectivity
      • +
      • capistrano - Just to have it handy when needed
      • +
      • mongrel - Rails server
      • +
      • mongrel-cluster - To operate mongrel clusters
      • +
      +
      $ sudo gem install mysql capistrano mongrel mongrel-cluster --include-dependencies
      +You'll be asked several times to choose a version for different gems. Always choose the latest available version for 'ruby'. (Don't choose win32. I don't need to explain why.) +

      Test Rails and MySQL operability

      +Before you continue you may want to take some time to test Rails and MySQL. It's not essential, but I recommend it because it will save you a lot of trouble later on. + +Create a new Rails application in your homedir and a create the corresponding MySQL database. Also edit config/database.yml to reflect your root password! +
      $ mysqladmin -u root -p create testapp_development
      +$ mkdir testapp
      +$ rails testapp
      +$ cd testapp
      +$ vi config/database.yml
      +$ rake db:migrate
      +If you get any errors from that last command, check the previous steps. Normally, all should be fine and you can continue safely. + +Most people configure a 'socket' in their config/database.yml for MySQL. Note that this socket is in different places for different distribution. Ubuntu and Debian keep it in: /var/run/mysqld/mysqld.sock. You may need to update your configuration in order to connect to the database. +

      Apache 2.2

      +The good thing about Ubuntu/Debian is that they both include Apache 2.2.x now. This branch of Apache includes the a balancing proxy, which allows you to distribute your workload over several Mongrel servers (in your mongrel cluster). I'll come back to that later. +
      $ sudo apt-get install apache2
      +Before you continue, enable several modules which we'll be using later on. +
      $ sudo a2enmod proxy_balancer
      +$ sudo a2enmod proxy_ftp
      +$ sudo a2enmod proxy_http
      +$ sudo a2enmod proxy_connect
      +$ sudo a2enmod rewrite
      +That's it for now on Apache. Let's move along. +

      Prepping the Rails application

      +Okay, let's prepare the Rails application 'myapp' for deployment now, shall we? + +First, create a production database on your server, and configure it in config/database.yml: +
      $ mysql -u root -p
      +> create database myapp_production;
      +> grant all privileges on myapp_production.* to 'myapp'@'localhost'
      +identified by 'secret_password'
      +The next step is to install capistrano. You can install it on your development machine as a gem, as demonstrated above. Next, apply capistrano to your application: +
      $ sudo gem install capistrano
      +$ cap --apply-to myapp
      +Take a look at myapp/config/deploy.rb. This file contains the configuration for the deployment of your application. Take special care of the following, here's an example for 'myapp': +
      require 'mongrel_cluster/recipes'
      +set :application, "myapp"
      +set :repository, "http://svn.myhost.com/svn/#{application}/trunk"
      +# We only have one host
      +role :web, "myapp.com"
      +role :app, "myapp.com"
      +role :db,  "myapp.com", :primary => true
      +# Don't forget to change this
      +set :deploy_to, "/home/ariejan/apps/#{application}"
      +set :mongrel_conf, "#{current_path}/config/mongrel_cluster.yml"
      +As you can see, I've already included several lines that enable the use of mongrel, we'll get to that next. + +Make sure you adapt this file to your own needs. myapp.com is the address of the server you're going to deploy your application to. the mongrel_cluster.yml file will be created in a moment. + +On the server, make sure you create the 'apps' directory. You can now setup a basic file structure for the deployment: +
      $ cd myapp
      +$ cap setup
      +On your server, you'll notice that the /home/ariejan/apps/myapp directory was created, including some subdirectories. + +If you are annoyed with entering your SSH password every time, create and upload your public SSH key to automate this. (I'll write something up about that later on.) + +Now, configure mongrel. For a normal setup, with moderate traffic, you can handle all traffic with two mongrel instances. The mongrel servers will only be accessable through 'localhost' on the server on non-default ports. Apache will do the rest later. + +In your rails app, run the following command: +
      $ mongrel_rails cluster::configure -e production -p 9000 \
      +-a 127.0.0.1 -N 2 -c /home/ariejan/apps/myapp/current
      +The configuration file we saw earlier has been created. Check-in all new files in to subversion now, and cold deloy your application! +
      $ cap cold_deploy
      +The deployment will checkout the most recent code from Subversion and start the mongrel servers. + +After the deployment, migrate your production database and restart the mongrel cluster: +
      $ cap migrate
      +$ cap restart
      +To check that your application is running, issue the following command on your server. It should return you the HTML code from your app: +
      $ curl -L http://127.0.0.1:9000
      +

      Configure Apache and the Balacing Proxy

      +You have two mongrel servers running, ready to handle incoming requests. But, you want your visitors to use 'myapp.com' and not an IP address with different port numbers. This is where apache comes in. + +Create a new file in /etc/apache2/sites-availbale named 'myapp' and add the following: +
      +
      +  BalancerMember http://127.0.0.1:9000
      +  BalancerMember http://127.0.0.1:9001
      +
      +
      +
      +  ServerName myapp.com
      +  ServerAlias www.myapp.com
      +
      +  DocumentRoot /home/ariejan/apps/myapp/current/public
      +
      +  
      +    Options FollowSymLinks
      +    AllowOverride None
      +    Order allow,deny
      +    Allow from all
      +  
      +
      +  RewriteEngine On
      +
      +  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
      +  RewriteCond %{SCRIPT_FILENAME} !maintenance.html
      +  RewriteRule ^.*$ /system/maintenance.html [L]
      +
      +  RewriteRule ^/$ /index.html [QSA]
      +
      +  RewriteRule ^([^.]+)$ $1.html [QSA]
      +
      +  RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
      +  RewriteRule ^/(.*)$ balancer://myapps_mongrel_cluster%{REQUEST_URI} [P,QSA,L]
      +
      +  ErrorLog /home/ariejan/apps/myapps/shared/log/tankfactions_errors_log
      +  CustomLog /home/ariejan/apps/myapps/shared/log/tankfactions_log combined
      +
      +Now enable this new site in apache: +
      $ sudo a2ensite myapp
      +$ /etc/init.d/apache2 force-reload
      +In some cases you may need to make a small change to /etc/apache2/mods-enabled/proxy.conf and swap +
      Order deny,allow
      +Deny from all
      +for +
      Order allow,deny
      +Allow from all
      +That's all, you can now access your app on myapp.com! +

      Maintaining your application

      +Now, happily develop your application and make update (you check them in to Subversion). To update your web server run: +
      $ cap deploy
      +If you made changes in the database, you may want to run a long_deploy: +
      $ cap long_deploy
      +And if for some reason, your mongrel cluster dies, just restart it. +
      $ cap restart
      +That's it! Happy hacking :) + +Please digg this story to help spread the word! Thanks a lot! + +Comments can be posted here or in my new and shiny Google Group! \ No newline at end of file diff --git a/content/posts/2007-06-21-ajax-rules-80-javascript-solutions-for-professional-coding.md b/content/posts/2007-06-21-ajax-rules-80-javascript-solutions-for-professional-coding.md new file mode 100644 index 0000000..fc577b9 --- /dev/null +++ b/content/posts/2007-06-21-ajax-rules-80-javascript-solutions-for-professional-coding.md @@ -0,0 +1,17 @@ +--- +title: "AJAX Rules! 80 JavaScript Solutions for professional coding" +kind: article +slug: ajax-rules-80-javascript-solutions-for-professional-coding +created_at: 2007-06-21 +tags: + - General + - Blog + - Web Development + - Links +--- + +Smashing Magazine has put together a very comprehensive list of 80 AJAX JavaScript solutions for professional coding. How great is that?! + +The list includes solutions for auto-completion, inline editing, menus, tabs, calendars, all sorts of interactive stuff and tables, charts, graphs, forms, grids, lightboxes, galleries, showcases, visual effects and also, some basic JavaScripts that everybody should know about. + +Check out the list now! \ No newline at end of file diff --git a/content/posts/2007-07-01-activescaffold-acts_as_taggable-auto-complete.md b/content/posts/2007-07-01-activescaffold-acts_as_taggable-auto-complete.md new file mode 100644 index 0000000..b9c7215 --- /dev/null +++ b/content/posts/2007-07-01-activescaffold-acts_as_taggable-auto-complete.md @@ -0,0 +1,84 @@ +--- +title: "ActiveScaffold + acts_as_taggable + Auto Complete" +kind: article +slug: activescaffold-acts_as_taggable-auto-complete +created_at: 2007-07-01 +tags: + - General + - RubyOnRails + - Features +--- + +I've talked before on how to use ActiveScaffold with acts_as_taggable_on_steroids. + +The problem with that solution was that, although the checkboxes for every tag are very nice, you couldn't easily add new tags. For some people, this may be fine, for others, it is not. + +Together with a colleague (who wishes not to be named), I found a solution that is quite elegant. Instead of using check boxes, and creating all kinds of subforms in ActiveScaffold, we opted for an auto_completing, comma-separated list of tags. + +This article descripes the solution we found. I think you'll like it very much! + +When you try to use acts_as_taggable with ActiveScaffold, you might use something like this in your BooksController. +
      active_scaffold :books do |config|
      +   config.columns = [:title, :body, :tags]
      +   config.list.columns = [:title, :tag_list]
      +   config.columns[:tags].ui_type = :select
      +   # ...
      +end
      +This is not so useful when you want the flexibility of creating new tags instantly. Therefore, it's better to use the tag_list: +
      active_scaffold :books do |config|
      +   config.columns = [:title, :body, :tag_list]
      +   config.list.columns = [:title, :tag_list]
      +   # ...
      +end
      +You get a text_field for writing down the tags (comma-separated). The problem of this is that the user has to keep all the tags in mind and is not allowed to make any typos in the tag. To help our users out, I use Rails' auto_complete feature. + +In your BooksController: +
      auto_complete_for :book, :tag_list
      +
      +def autocomplete_tag_list
      +  @all_tags = Tag.find(:all, :order => 'name ASC')
      +
      +  re = Regexp.new("^#{params[:record][:tag_list]}", "i")
      +  @tags = @all_tags.find_all do |t|
      +    t.name.match re
      +  end
      +
      +  render :layout => false
      +end
      +We can now create the template for the results which are found. +In app/views/books/autocomplete_tag_list.rhtml: +
      +
        +<% @tags.each do |t| %> +
      • <%= t %>
      • +<% end %>
      +
      +Now comes the difficult part, integration of the auto_complete widget within ActiveScaffold. + +ActiveScaffold has the possibility to change the way every attribute is displayed on the create and edit page. I want to change the form for the attribute 'tag_list'. To do this, I create create a file named app/views/books/_tag_list_form_column.rhtml: +
      +
      +
      + +
      +
      + <%= text_field_tag 'record[tag_list]', @record.tag_list %> +

      + style="{height: 80px;}"> + + +

      +
      +This shows a text field and generates a div that contains the available tags that we can show to the user. To populate the list of tags we use Ajax.Autocompler, which requires three arguments: the id of the text_field; the id of the div where you want to show possible tags to the user; and third, the URL of the action we created before, that returns the proper tags. + +The 'tokens' part of the last argument indicates that the user can seperate multiple tags with a comma. So, if you've entered one tag, added a comma and start typing a new tag, the auto complete feature will only lookup that second tag you're typing! + +That's it. Just spice things up a bit with some Style, and you're done. Enjoy! \ No newline at end of file diff --git a/content/posts/2007-07-02-how-to-force-data-to-be-downloaded-as-a-file-from-your-rails-app.md b/content/posts/2007-07-02-how-to-force-data-to-be-downloaded-as-a-file-from-your-rails-app.md new file mode 100644 index 0000000..08e0def --- /dev/null +++ b/content/posts/2007-07-02-how-to-force-data-to-be-downloaded-as-a-file-from-your-rails-app.md @@ -0,0 +1,36 @@ +--- +title: "How to force data to be downloaded as a file from your Rails app" +kind: article +slug: how-to-force-data-to-be-downloaded-as-a-file-from-your-rails-app +created_at: 2007-07-02 +tags: + - General + - RubyOnRails + - Features +--- + +In the essence of every application is data. One way or another your application manages data and at some point, you need to get that data out. Either you want to synchronize the data with another application or device. Or you want to move your data to another system all together. Either way, you'll need to gather your data and send it from your application to the client... as a file. + +Downloading files is not the hardest thing around. But the problem is that some formats, like XML, are automatically parsed by the browser and this makes it harder for users to download files like that. + +So, what you want to do is, ignore the browser and offer your data (in XML or whatever format you want) as a file that can be downloaded directly. The solution is rather easy, as always with Rails. + +Okay, in this example I have an action that renders current 'entries' as XML and offers this to the user: +
      def export_to_xml
      +  @entries = Entry.find(:all)
      +  render :xml => @entries.to_xml
      +end
      +This works as you'd expect it to. When calling this action, the user receives a XML file containing all entries. But really, do you want that in your browser? Especially when the XML file is rather large, this can be very annoying, because your browser will want to load it all in! + +What you want here is offer the users a file named 'entries.xml' for download. In this case we use Rails' send_data method. The previous action now looks like this: +
      def export_to_xml
      +  @entries = Entry.find(:all)
      +  send_data @entries.to_xml,
      +    :type => 'text/xml; charset=UTF-8;',
      +    :disposition => "attachment; filename=entries.xml"
      +end
      +It's clear that we send the XML data to the client. I specify the type and charset of the data with the 'type' paramater. This way the browser knows what is being send and allows the user to choose an application that can use the data. In this case an XML reader, for example. + +The disposition parameter tells the browser this should be downloaded as a file (or attachment). It also specifies what the name of the attachment is, 'entries.xml'. + +Now, link to this method and the user will be presented with a downloadable XML file. \ No newline at end of file diff --git a/content/posts/2007-07-03-got-updates.md b/content/posts/2007-07-03-got-updates.md new file mode 100644 index 0000000..0112eae --- /dev/null +++ b/content/posts/2007-07-03-got-updates.md @@ -0,0 +1,24 @@ +--- +title: "Got updates?" +kind: article +slug: got-updates +created_at: 2007-07-03 +tags: + - General + - Ariejan.net + - Blog +--- + +Do you want to stay up-to-date about what's happening at Ariejan.net? That's real easy! + +You can subscribe to my RSS Feed and receive updates as they happen in your favourite RSS reader. + +If you don't have an RSS reader (or don't want one), you can subscribe to my e-mail service. This service will gather up all the posts of the previous day (if any) and send them to you between 7am and 9am (CET). + +Interested? Sign-up now! + +
      Enter your email address: + + Delivered by FeedBurner + +Or grab the RSS Feed. \ No newline at end of file diff --git a/content/posts/2007-07-03-how-to-create-and-apply-a-patch-with-subversion.md b/content/posts/2007-07-03-how-to-create-and-apply-a-patch-with-subversion.md new file mode 100644 index 0000000..4d08d60 --- /dev/null +++ b/content/posts/2007-07-03-how-to-create-and-apply-a-patch-with-subversion.md @@ -0,0 +1,43 @@ +--- +title: "How to create and apply a patch with Subversion" +kind: article +slug: how-to-create-and-apply-a-patch-with-subversion +created_at: 2007-07-03 +tags: + - General + - Features + - Subversion +--- +It's been a while since I posted something new on the use of Subversion. I've been working with the tool a lot, and I've found that patches are a great way to communicate code changes. + +For those of you who are still learning, let me first explain what a patch is. A patch is a text file that contains the alteration that were made to a specific file. It includes the lines that have been removed and the lines that have been added. In short, if you have a ruby script and edited it, you could create a patch file, containing the changes you've made. + +Why is this useful? You could check in your changes to your repository directly. True, but there are cases that you don't have write access to the repository. For example, if you wanted to contribute code changes to Acts As Exportable, you should create a new ticket and attach a patch file. I will then review your changes before I apply them to the code and commit them to the repository. + +So, how do you go about creating a patch file and how do you later apply it to your source? +~ +### Creating a patch file +Creating a patch file is really easy. First, check out the most recent version of the code from Subversion using the 'checkout' command. + +Make your changes. + +Then, in the root the project run the following command. It will store the patch file in your home directory. Make sure to give it meaningful filename. + + svn diff > ~/fix_ugly_bug.diff + +The file has the .diff extention, which stands for differences. This extension is recognized by many text editors and enables 'syntax highlighting' automatically. (Give it a try with TextMate and you'll know what I mean.) + +You can send the diff-file to the author of the project by email, or you can create a ticket in Trac and add it as an attachment. The author will review the changes you made and possibly apply them to the source. + +### Applying a patch +You should never apply patches from any person other than your development team without first reading through the changes, apply them locally and test your application and then commit them. Patches can not only include bug fixes, but also alterations to create back doors or add other exploits to your code. + +Always read through a patch before applying it! + +When you are sure the patch will bring no harm to you, your application or your customers, go ahead an apply it to your working copy. Here, I assume that you downloaded the patch file we previously generated, and placed it in your home directory. In the root of your application now run: + + patch -p0 -i ~/fix_ugly_bug.diff + +This will apply all the changes in the patch to your source. The -p0 option makes sure that all files can be found correctly (this has to do with something called 'zero directories', I won't get into that right now). The -i option tells 'patch' what to use as input, in this case the 'fix_ugly_bug.diff' file in your home directory. + +With the code changes in place, run your tests and make sure everything works as expected. If it does, commit your changes and celebrate with a cup of coffee. diff --git a/content/posts/2007-07-04-how-to-resolve-subversion-conflicts.md b/content/posts/2007-07-04-how-to-resolve-subversion-conflicts.md new file mode 100644 index 0000000..d0de483 --- /dev/null +++ b/content/posts/2007-07-04-how-to-resolve-subversion-conflicts.md @@ -0,0 +1,92 @@ +--- +title: "How to resolve Subversion Conflicts" +kind: article +slug: how-to-resolve-subversion-conflicts +created_at: 2007-07-04 +tags: + - Subversion + - conflicts +--- +If there's more than one person working on a project, chances are (although slim) that at some point two developers work on the same piece of code and check it in. To clarify, let me give you an example. + +The repository is currently at revision 5 and contains a file named 'README'. Revision 5 of that file contains a single line: 'This is a README file'. + +Now, both you and your colleague check out r5 and edit README. Your colleague changes the line to 'This is a documentation file' and commits it back to the repository, which is bumped to revision 6. + +You're an island, and have no clue about the new revision being created. You just happily write away and change the README file to: 'This is fun stuff!'. + +When you commit your changes, you'll get an error message: + + $ svn commit -m "Updated README" + Sending README + Transmitting file data .svn: Commit failed (details follow): + svn: Out of date: '/myproject/README' + +~ +This is good. Subversion has detected that the file you want to commit has changed since you last updated it. Update the file to get it up-to-date again. + + $ svn update + C README + Updated to revision 6. + +The 'C' indicates there is a conflict with the README file, and Subversion does not know how to solve this. You are called in to help. + +If you now take a look at README, you'll notice that there are several markers that indicate what parts of the code are conflicting. You can easily see what you changed, and what has changed in the repository: + + <<<<<<< .mine + This is fun stuff! + ======= + This is a documentation file + >>>>>>> .r6 + +

      What are your options?

      +You have three options for resolving the conflict. Whatever you choose, make sure you confer with your colleague on the matter. + +1. Scrap your changes, and go with the current work from your colleague. + +This is the easiest solution. All you have to do is revert the changes you made, and update your working copy: + + $ svn revert README + Reverted 'README' + $ svn update README + At revision 6. + +2. Keep your changes, and dump whatever your colleague did. + +Performing a simple 'ls' will show you that there are four files related to this conflict: + +
        +
      • README – the original with markers
      • +
      • README.mine – your version
      • +
      • README.r5 – the original your worked with
      • +
      • README.r6 – the most update version from your colleague
      • +
      + +To check in your changes, copy your version over the original and tell Subversion you have resolved the conflict. + + $ cp README.mine README + $ svn resolved README + Resolved conflicted state of 'README' + +The 'resolved' command will clean up all the special files that were generated. + +3. Merge both version to a new version + +If you choose this option, you will have to manually edit README. Remove the markers and add whatever you need to add here. + +Subversion won't let you commit this file, so you'll have to mark it as 'resolved' as we saw during option 2: + + $ svn resolved README + Resolved conflicted state of 'README' + +Before you rush ahead + +With option 1, you're done. With options 2 and 3 there is some more work to do, because you didn't commit your changes yet. Because we're dealing with conflicts here, I recommend you don't just commit to your repository, but follow a slightly different route. + +First, update your working copy (again) to make sure you have all the latest, and are not trying to check in any more conflicting code. If any conflicts pop-up, fix these first. + +Now, run your tests to make sure everything is working as it should. + +When all is clear, commit your changes to your repository as you normally would. + +Done. Problem solved. diff --git a/content/posts/2007-07-06-how-to-write-a-rails-plugin-for-controllers.md b/content/posts/2007-07-06-how-to-write-a-rails-plugin-for-controllers.md new file mode 100644 index 0000000..1c3e436 --- /dev/null +++ b/content/posts/2007-07-06-how-to-write-a-rails-plugin-for-controllers.md @@ -0,0 +1,190 @@ +--- +title: "How to write a Rails Plugin (for controllers)" +kind: article +slug: how-to-write-a-rails-plugin-for-controllers +created_at: 2007-07-06 +tags: + - General + - RubyOnRails + - Features +--- + +A few days back I posted my very first Rails plugin, Acts As Exportable. Although writing a plugin is rather easy, you must know a few tricks on how to get things going. + +This article will show you how to develop a plugin that adds functionality to a controller (other plugins, e.g. for models) will follow later. In fact, I'll explain to you how I developed my Acts As Exportable plugin. + +Let's take a basic Rails application for starters. You have setup a model with some attributes and a scaffolded controller that allows you to CRUD your items. In this tutorial I'll be working with books. The model is named 'Book' and the controller 'BooksController'. Start your web server now and add some random data to play with. + +Before you dive into writing a plugin for the controller to export data to XML you should have some basic functionality in your controller first. I've found it easier to develop my code in the controller first, and then port it to a plugin. + +So, add a new method to your BooksController that'll export books to XML. This looks quite easy: +
      def export_to_xml
      +  books = Book.find(:all, :order => 'title')
      +  send_data books.to_xml,
      +    :type => 'text/xml; charset=UTF-8;',
      +    :disposition => "attachment; filename=books.xml"
      +end
      +Now, call /books/export_to_xml and you download a real XML file containing all your books! To make things a bit more complicated, we want to be able to feed this method some conditions to select books. A nice solution is to add a special method for this that defines these conditions. (You could also use them in listing books, for example.) I add a new method to the BooksController: +
      def conditions_for_collection
      +  ['title = ?', 'some title!']
      +end
      +The condition is of the same format you can feed to find. Here you could, for example, select only the books belonging to the currently logged in user. + +Next, update the export_to_xml method to use these conditions +
      def export_to_xml
      +  books = Book.find(:all, :order => 'title', :conditions => conditions_for_collection)
      +  send_data books.to_xml,
      +    :type => 'text/xml; charset=UTF-8;',
      +    :disposition => "attachment; filename=books.xml"
      +end
      +Nice that's it. Now, you like what you've made so far, and want to stuff it into a plugin and put it on your weblog. Here's how to go about that. +

      Creating the plugin

      +First, generate the basic code for a plugin: +
      ./script/generate plugin acts_as_exportable
      +This will create a new directory in vendor/plugins containing all the basic files you need. First, we'll take a look at vendor/plugins/acts_as_exportable/lib/acts_as_exportable.rb. This is where all the magic happens. + +What we want is to is add a method to ActionControllerBase that allows you to easily enable the plugin in a certain controller. So, how do you want to activate the plugin? Right, you just call 'acts_as_exportable' from the controller, or optionally, you add the name of the model you want to use. +
      acts_as_exportable
      +acts_as_exportable :book
      +The vendor/plugins/acts_as_exportable/lib/acts_as_exportable.rb contains a module that's named after our plugin: +
      module ActsAsExportable
      +end
      +Next, we add a module named 'ClassMethods'. These class methods will be added to ActionController::Base when the plugin is loaded (we'll take care of that in a moment), and enable the functionality described above. +
      module ActsAsExportable
      +  def self.included(base)
      +    base.extend(ClassMethods)
      +  end
      +
      +  class Config
      +    attr_reader :model
      +    attr_reader :model_id
      +
      +    def initialize(model_id)
      +      @model_id = model_id
      +      @model = model_id.to_s.camelize.constantize
      +    end
      +
      +    def model_name
      +      @model_id.to_s
      +    end
      +  end
      +
      +  module ClassMethods
      +	  def acts_as_exportable(model_id = nil)
      +      # converts Foo::BarController to 'bar' and FooBarsController
      +      # to 'foo_bar' and AddressController to 'address'
      +      model_id = self.to_s.split('::').last.sub(/Controller$/, '').\
      + pluralize.singularize.underscore unless model_id
      +
      +      @acts_as_exportable_config = ActsAsExportable::Config.\
      + new(model_id)
      +      include ActsAsExportable::InstanceMethods
      +    end
      +
      +    # Make the @acts_as_exportable_config class variable easily
      +    # accessable from the instance methods.
      +    def acts_as_exportable_config
      +      @acts_as_exportable_config || self.superclass.\
      + instance_variable_get('@acts_as_exportable_config')
      +    end
      +  end
      +end
      +So, what happened? The first method you see extends the current class (that's every one of your controllers with the methods from the ClassMethods module). + +Every class now has the 'acts_as_exportable' method available. What does it do? The plugin automatically grabs the name of the model associated (by convention) with the controller you use, unless you specify something else. + +Next, we create a new configuration object that contains information about the model we're working with. Later on this can contain more detailed information like what attributes to include or exclude from the export. + +Finally we include the module InstanceMethods, which we still have to define. The instance methods are only included when we enable the plugin. In our case, the instance methods include the 'export_to_xml' and 'conditions_for_collection' methods. We can simply copy/paste them into your plugin. +
      module InstanceMethods
      +  def export_to_xml
      +    data = Book.find(:all, :order => 'title', :conditions => conditions_for_collection)
      +    send_data data.to_xml,
      +      :type => 'text/xml; charset=UTF-8;',
      +      :disposition => "attachment; filename=books.xml"
      +  end
      +
      +  # Empty conditions. You can override this in your controller
      +  def	conditions_for_collection
      +  end
      +end
      +Take note that we don't want to define any default conditions, because we don't know what model we're using here. By adding an empty method, the method is available and no conditions are used. Another developer can define 'conditions_for_collection' in his controller to override the one we write here. + +In the 'export_to_xml' there are a few changes as well. First of all, I generalized 'books' to 'data'. + +The most important step is yet to come. We have still application specific code in your plugin, namely the Book model. This is where the Config class and @acts_as_exportable_config come in. + +We have added a class variable to the controller named @acts_as_exportable_config. By default, this variable is not accessable by instance methods, so we need a little work around: +
      self.class.acts_as_exportable_config
      +This will call the class method 'acts_as_exportable_config' we defined in ClassMethods and return the value of @acts_as_exportable_config. + +Note that we store the configuration in each seperate controller. This allows acts_as_exportable to be used with more than one controller at the same time. + +With the model name made application independent, the whole plugin code looks like: +
      module ActsAsExportable
      +  def self.included(base)
      +    base.extend(ClassMethods)
      +  end
      +
      +  class Config
      +    attr_reader :model
      +    attr_reader :model_id
      +
      +    def initialize(model_id)
      +      @model_id = model_id
      +      @model = model_id.to_s.camelize.constantize
      +    end
      +
      +    def model_name
      +      @model_id.to_s
      +    end
      +  end
      +
      +  module ClassMethods
      +	  def acts_as_exportable(model_id = nil)
      +      # converts Foo::BarController to 'bar' and FooBarsController to 'foo_bar'
      +      # and AddressController to 'address'
      +      model_id = self.to_s.split('::').last.sub(/Controller$/, '').\
      + pluralize.singularize.underscore unless model_id
      +
      +      @acts_as_exportable_config = ActsAsExportable::Config.new(model_id)
      +      include ActsAsExportable::InstanceMethods
      +    end
      +
      +    # Make the @acts_as_exportable_config class variable easily
      +    # accessable from the instance methods.
      +    def acts_as_exportable_config
      +      @acts_as_exportable_config || self.superclass.\
      + instance_variable_get('@acts_as_exportable_config')
      +    end
      +  end
      +
      +	module InstanceMethods
      +	  def export_to_xml
      +	    data = self.class.acts_as_exportable_config.model.find(:all,
      +              :order => 'title',
      +              :conditions => conditions_for_collection)
      +	    send_data data.to_xml,
      +	      :type => 'text/xml; charset=UTF-8;',
      +	      :disposition => "attachment; filename=\
      + #{self.class.acts_as_exportable_config.model_name.pluralize}.xml"
      +	  end
      +
      +	  # Empty conditions. You can override this in your controller
      +    def	conditions_for_collection
      +    end
      +  end
      +end
      +Add the following line to your BooksController and restart your web server. (Oh, and make sure to remove the export_to_xml method from the controller as well) +
      acts_as_exportable
      +Done! – Or not? +

      Enabling the plugin by default

      +We have a very nice plugin now, but it is not loaded by default! If you take a look at your plugin directory, you'll find a file named 'init.rb'. This file is executed when you (re)start your web server. This is the perfect place to add our class methods to the ActionController::Base. Just add the following three lines of code to 'init.rb': +
      ActionController::Base.class_eval do
      +  include ActsAsExportable
      +end
      +When we include our module, the 'self.included' method is called, and the ClassMethods module is added, thus enabling the acts_as_exportable method. + +That's all! Happy plugin writing! + +Feel free to comment on this post and write about any of your own plugin (for controllers) experiences. \ No newline at end of file diff --git a/content/posts/2007-07-06-some-dns-problems-with-ariejannet.md b/content/posts/2007-07-06-some-dns-problems-with-ariejannet.md new file mode 100644 index 0000000..4a180ed --- /dev/null +++ b/content/posts/2007-07-06-some-dns-problems-with-ariejannet.md @@ -0,0 +1,16 @@ +--- +title: "Some DNS problems with Ariejan.net" +kind: article +slug: some-dns-problems-with-ariejannet +created_at: 2007-07-06 +tags: + - General + - Ariejan.net + - Blog +--- + +This is just a quick note to let you know there are currently some issues with DNS for Ariejan.net. This basically means that Trac and SVN are currently not available. I've made the appropriate changes and things should be working again in a few hours. + +I'm very sorry for the inconvenience. + +Update: It appears that things are working again. If you have any issues with Trac, please let me know. \ No newline at end of file diff --git a/content/posts/2007-07-07-clear-dns-cache-on-your-router.md b/content/posts/2007-07-07-clear-dns-cache-on-your-router.md new file mode 100644 index 0000000..d2fe32b --- /dev/null +++ b/content/posts/2007-07-07-clear-dns-cache-on-your-router.md @@ -0,0 +1,30 @@ +--- +title: "Clear DNS Cache on your router" +kind: article +slug: clear-dns-cache-on-your-router +created_at: 2007-07-07 +tags: + - General + - Features + - Linux +--- + +I currently have a Linksys router at home that has the DD-WRT Firmware on it. I've been using it for quite some time now, and I'm very happy with it. + +In my previous post I mentioned there was some trouble with DNS for Ariejan.net. I've changed nameservers and there's always something that goes wrong. + +Anyway, my router runs DNSMasq, a caching nameserver for my local network. (What this does is, it stores DNS queries and when the same request is made later on, the response is already here (on my network), and my ISP's nameservers don't have to be queried. This makes for a great speed optimalization!) + +The problem was, that my router was caching parts from the old and parts from the new nameservers for Ariejan.net. I could have waited 24-48 hours to let DNSMasq figure it all out by itself, but I'm not that patient. What I needed to was reset the cache DNSMasq had built. + +What you need to do is login to the admin panel of your router and enable SSH access (for your own network, not for the outside world!). + +You SSH to your route, probably as the 'root' or 'admin' user. In this case the IP is your routers IP address. +
      $ ssh -l admin 10.0.0.1
      +Once you're in, you can issue the killall command to issue the 'HUP' signal to DNSMasq. This does not kill your DNSMasq, but tells it to flush the cache an reread its configuration, thus starting over with a clean slate. + +The 'HUP' signal has the number '1' so you can run the following command once logged in: +
      $ killall -1 dnsmasq
      +Now you can logout, and notice that your cache was indeed cleared. + +This method should work on every Linux based router that uses DNSMasq and support SSH logins. \ No newline at end of file diff --git a/content/posts/2007-07-16-ariejannet-whats-next.md b/content/posts/2007-07-16-ariejannet-whats-next.md new file mode 100644 index 0000000..f8d3419 --- /dev/null +++ b/content/posts/2007-07-16-ariejannet-whats-next.md @@ -0,0 +1,37 @@ +--- +title: "Ariejan.net - What’s next?" +kind: article +slug: ariejannet-whats-next +created_at: 2007-07-16 +tags: + - General + - Ariejan.net + - Blog +--- + +Vote now! What's Next for Ariejan.net? Your opinion counts! + +After a few days (or were it weeks?) of silence, an update on Ariejan.net. What's been happening? + +First of all, I recently graduated and am now officially a Bachelor of ICT. Since my current employer didn't have the same views regarding my future as I did, I decided to go "shopping" for a new job. + +I found that new job with a small business named Kabisa ICT. The people at Kabisa and I found common ground very quickly with Ruby on Rails and Agile Development. This, to my surprise, resulted in a job offer very quickly... and I accepted. + +The next two weeks I've got a holiday planned to the French Alps with Laura. We hope to get some rest there while enjoying the beautiful scenery. + +When I get back from France, I start my new job at Kabisa. I hope to learn a lot of new stuff while working there. Hopefully I'll be able to share that experience with your here, at Ariejan.net, which leads me to the following question. + +What's Next? + +What's next for Ariejan.net? Since all the Web 2.0 buzz is all about communities and all that, I want your opinion on what to do with Ariejan.net. + +Please vote in the poll below and let me know what your thoughts are. When I get back from my (well earned) vacation, I'll check back here and see what you, my guests, really want. + +You have the option of adding new stuff. Use it wisely, please! + + + +
      {democracy:1}
      + + +Thanks for voting! It's greatly appreciated! \ No newline at end of file diff --git a/content/posts/2007-08-01-im-back.md b/content/posts/2007-08-01-im-back.md new file mode 100644 index 0000000..c36a1d9 --- /dev/null +++ b/content/posts/2007-08-01-im-back.md @@ -0,0 +1,22 @@ +--- +title: "I’m Back!" +kind: article +slug: im-back +created_at: 2007-08-01 +tags: + - General + - Ariejan.net + - Blog +--- + +All right, I'm back! + +Yes, my holiday was nice. Thanks for asking. Now, I've been looking at the poll in my previous post and I've made up my mind. + +1. I'm going to look into writing a blogging system in Ruby on Rails. I don't want to copy typo or Mephisto or anything, but just put my own thoughts together, add a bit of spice you guys (and girls?) have to offer and release it some time. + +Just to be clear (and prevent myself from getting a lot of mail about this): I'm going to see if I can make time to work on a project like this. That's all. If I can, I'll let you know. If I can't.. well too bad, I guess :) + +2. I'm going to, as I did before, write a lot of articles about Subversion and Ruby on Rails. Maybe I'll add some new topics too, but those will pop up when they do. Just keep an eye out for news here at Ariejan.net (tip: grab my RSS Feed). + +Also, I'm now available to do Ruby on Rails projects for real! Just contact me and we can discuss details at your leisure. \ No newline at end of file diff --git a/content/posts/2007-08-20-im-back-in-business.md b/content/posts/2007-08-20-im-back-in-business.md new file mode 100644 index 0000000..a8432c3 --- /dev/null +++ b/content/posts/2007-08-20-im-back-in-business.md @@ -0,0 +1,18 @@ +--- +title: "I’m back in business!" +kind: article +slug: im-back-in-business +created_at: 2007-08-20 +tags: + - General + - Ariejan.net + - Blog +--- + +Ariejan.net is (almost) back in business. With some minor cosmetic improvements to the theme, I'm ready for today. + +Tomorrow I will reorganise my tags/categories a bit and add some new fancy plugins that will help searching visitors a hand in finding what they need. (More about that later, I promise). + +I hope you like the improved style of Ariejan.net. (If you don't, feel free to make a redesign and mail it to me.) + +That's all for now. Bye bye. \ No newline at end of file diff --git a/content/posts/2007-08-20-kabisa-railsconf-europe-and-ariejannet.md b/content/posts/2007-08-20-kabisa-railsconf-europe-and-ariejannet.md new file mode 100644 index 0000000..a1bdbfb --- /dev/null +++ b/content/posts/2007-08-20-kabisa-railsconf-europe-and-ariejannet.md @@ -0,0 +1,18 @@ +--- +title: "Kabisa, RailsConf Europe and Ariejan.net" +kind: article +slug: kabisa-railsconf-europe-and-ariejannet +created_at: 2007-08-20 +tags: + - General + - Everything + - Blog +--- + +Hello there! Just a quick post to let you know I'm still alive and kicking! Two weeks ago I started my job at Kabisa and I've been very busy with that. I really like working at Kabisa and we have some great things planned for the near future. (We're currently working on a few client projects that need our attention.) + +Rails Conf Europe 2007I'd also like to let you know that I'll be attending RailsConf Europe 2007 this year! I'm really looking forward to the conference and the tutorial day on monday. If you're attending, please let me know, so we can meet in person. + +Then I have a small announcement regarding Ariejan.net I'm going to reorganise my VPS tonight which may cause Ariejan.net to be off the air for a few moments. I've been considering moving Ariejan.net to Mephisto, but Wordpress is just a way better solution at the moment. (I do have to migrate 125+ posts, comments and reformat an reorganise everything when moving to Mephisto.) + +Suffice it to say that I've been a very busy bee the past few weeks. I hope to show you some of my Rails work in the near future when we've rounded up some of the client projects were working on. For now – stay tuned! \ No newline at end of file diff --git a/content/posts/2007-08-21-using-iconv-to-convert-utf-8-to-ascii-on-linux.md b/content/posts/2007-08-21-using-iconv-to-convert-utf-8-to-ascii-on-linux.md new file mode 100644 index 0000000..90f82f1 --- /dev/null +++ b/content/posts/2007-08-21-using-iconv-to-convert-utf-8-to-ascii-on-linux.md @@ -0,0 +1,36 @@ +--- +title: "Using Iconv to convert UTF-8 to ASCII (on Linux)" +kind: article +slug: using-iconv-to-convert-utf-8-to-ascii-on-linux +created_at: 2007-08-21 +tags: + - General + - RubyOnRails + - Features + - Ruby +--- + +There are situations where you want to remove all the UTF-8 goodness from a string (mostly because of legacy systems you're working with). Now, this is rather easy to do. I'll give you an example: +
      çéß
      +Should be converted to +
      cess
      +On my mac, I can simply use the following snippet to convert the string: +
      s = "çéß"
      +s = Iconv.iconv('ascii//translit', 'utf-8', s).to_s # returns "c'ess"
      +s.gsub(/\W/, '') # return "cess"
      +Very nice and all, but when I deploy to my Debian 4.0 linux system, the I get an error that tells me that invalid characters were present. Why? Because the Mac has unicode goodness built-in. Linux does not (in most cases). + +So, how do you go about solving this? Easy! Get unicode support! +
      sudo apt-get install unicode
      +Now, try again. + +Bonus + +If you want to convert a sentence (or anything else with spaces in it), you'll notice that spaces are removed by the gsub command. I solve this by splitting up the string first into words. Convert the words and then joining the words together again. +
      words = s.split(" ")
      +words = words.collect do |word|
      +    word = Iconv.iconv('ascii//translit', 'utf-8', word).to_s
      +    word = word.gsub(/\W/,'')
      +end
      +words.join(" ")
      +Like this? Why not write a mix-in for String? \ No newline at end of file diff --git a/content/posts/2007-08-24-super-simple-authentication-plugin-and-generator.md b/content/posts/2007-08-24-super-simple-authentication-plugin-and-generator.md new file mode 100644 index 0000000..dc38024 --- /dev/null +++ b/content/posts/2007-08-24-super-simple-authentication-plugin-and-generator.md @@ -0,0 +1,42 @@ +--- +title: "Super Simple Authentication Plugin and Generator" +kind: article +slug: super-simple-authentication-plugin-and-generator +created_at: 2007-08-24 +tags: + - General + - RubyOnRails + - Features +--- + +I hereby proudly announce my Super Simple Authentication plugin and generator. + +All right, what does it do? Sometimes you need to protect your actions and controllers, but you don't want to go about installing restful_authentication or anything like that. Adding a simple password for certain actions would suffice. So, I wrote a little plugin that can generate some code for you that allows you to easily protect your app with a simple password. + +To get started, you must first install the plugin in your rails application: +
      script/plugin install http://svn.ariejan.net/plugins/super_simple_authentication
      +When the plugin is installed, you may generate your SSA controller. This controller verifies your password and makes sure you stay authenticated for the duration of your visit. +
      script/generate super_simple_authentication sessions
      +Your password is located in config/super_simple_authentication.yml. Change it. + +In the SessionsController, you'll find an include statement. Move this include to your application controller: +
      include SuperSimpleAuthenticationSystem
      +The generator automatically added routes to your config/routes.rb file. If you want easy access to login and logout functionality, add these two lines to your config/routes.rb file as well: +
      map.login  '/login',  :controller => 'sessions', :action => 'new'
      +map.logout '/logout', :controller => 'sessions', :action => 'destroy', :method => :delete
      +You can now protect you actions and controllers with a before_filter: +
      # Protect all actions in the controller
      +before_filter :authorization_required
      +
      +# Protect all actions, except :index and :recent
      +before_filter :authorization_required, :except => [:index, :recent]
      +
      +# Protect only :destroy
      +before_filter :authorization_required, :only => :destroy
      +In your views, you can check if you are authorized or not with authorized? E.g. +
      <% if authorized? %>
      +    # ... do secret admin stuff
      +<% end %>
      +Please visit http://trac.ariejan.net to report bugs. Ariejan.net will keep you updated on new major version. Please subscribe to the RSS Feed. + +I hope you enjoy this plugin. Please post a comment if you use it in your project, or if you just like it. Bugs, feature requests and support requests should go into Trac \ No newline at end of file diff --git a/content/posts/2007-08-27-blueprintcss-rails-generator.md b/content/posts/2007-08-27-blueprintcss-rails-generator.md new file mode 100644 index 0000000..ccd34d3 --- /dev/null +++ b/content/posts/2007-08-27-blueprintcss-rails-generator.md @@ -0,0 +1,26 @@ +--- +title: "BlueprintCSS Rails Generator" +kind: article +slug: blueprintcss-rails-generator +created_at: 2007-08-27 +tags: + - General + - RubyOnRails + - Features +--- + +This plugin is no longer available. Blueprint nowadays ships with a very good 'compress' script that allows you to generate all kinds of nice BluePrint layouts. Having a plugin to just copy some files seems a bit excessive. + +I think that, if you're a web developer, you've seen the BlueprintCSS framework. BlueprintCSS offers quite a bit of CSS code that allows you to quickly and easily build a grid-based layout, using pure CSS. + +That's, of course, all very nice, but you should be able to plug it in into your Rails app. And now you can! + +Install my plugin: +
      ./script/plugin install http://svn.ariejan.net/plugins/blueprint
      +And then generate as many BlueprintCSS layouts as you'd like. To create a layout for your posts controller, simply run the following command: +
      ./script/generate blueprint posts
      +This will create a posts.rhtml template in app/views/layouts, and add the proper CSS and images to your application. That's all! + +Note 1: You may remove a few lines of inline CSS from your layout to remove the supporting background images. + +Note 2: Bugs and feature requests go into Trac. \ No newline at end of file diff --git a/content/posts/2007-08-31-blueprint-05-rails-plugin-released.md b/content/posts/2007-08-31-blueprint-05-rails-plugin-released.md new file mode 100644 index 0000000..b22214f --- /dev/null +++ b/content/posts/2007-08-31-blueprint-05-rails-plugin-released.md @@ -0,0 +1,14 @@ +--- +title: "Blueprint 0.5 Rails Plugin released" +kind: article +slug: blueprint-05-rails-plugin-released +created_at: 2007-08-31 +tags: + - General + - RubyOnRails + - Features +--- + +A few days ago BlueprintCSS 0.5 was released (read the Olav's posts here). I've updated the plugin accordingly. The most important change is the use of 24 (!) instead of 14 columns. + +Installation and usage of the plugin have not changed. See my original announcement for more information. \ No newline at end of file diff --git a/content/posts/2007-09-01-content_for-yield-and-making-sure-something-gets-displayed.md b/content/posts/2007-09-01-content_for-yield-and-making-sure-something-gets-displayed.md new file mode 100644 index 0000000..8b5c6d7 --- /dev/null +++ b/content/posts/2007-09-01-content_for-yield-and-making-sure-something-gets-displayed.md @@ -0,0 +1,27 @@ +--- +title: "Content_for, yield and making sure something gets displayed" +kind: article +slug: content_for-yield-and-making-sure-something-gets-displayed +created_at: 2007-09-01 +tags: + - General + - RubyOnRails + - Features +--- + +You may have heard of a very nice Rails technique that used content_for and yield to stuff custom blocks of content into a layout. For example, in a view you could add a block like this: +
      <% content_for :sidebar do %>
      +  This goes into the sidebar when viewing this action!
      +<% end %>
      + +It doesn't matter where you put it in the view, because, as you may notice, the content within the content_for block is not shown in the view. The layout can pull the content for :sidebar and show it in a custom place, mostly the sidebar: +
      <%= yield :sidebar %>
      +Nice, you now don't have to hard-code the sidebar (and it's layout elements) in your views everytime. You can even move the sidebar to another place, without breaking any of your views. How great is that? + +What does break your views, however, is not assigning anything to :sidebar. If you don't assign anything to the :sidebar, nothing will be shown, which might break your layout. (Empty div's tend to do that.) + +So, how can you solve this? Quite easily, actually. What you want, probably, is display a default sidebar when no custom sidebar has been assigned. you can do this with the following line of über sexy Ruby code: +
      <%= (sidebar = yield :sidebar) ? sidebar : render(:partial => 'shared/sidebar') %>
      +First, you grab the :sidebar content and stuff it into a variable named, you guessed it, 'sidebar'. If this sidebar containts anything, show it. If it doesn't, you render a partial in the app/views/shared directory named _sidebar.rhtml. + +Well, there you are. Go enjoy yourself now. \ No newline at end of file diff --git a/content/posts/2007-09-11-10-reasons-why-microsofts-10-reasons-not-to-use-google-apps-suck.md b/content/posts/2007-09-11-10-reasons-why-microsofts-10-reasons-not-to-use-google-apps-suck.md new file mode 100644 index 0000000..5463c57 --- /dev/null +++ b/content/posts/2007-09-11-10-reasons-why-microsofts-10-reasons-not-to-use-google-apps-suck.md @@ -0,0 +1,62 @@ +--- +title: "10 reasons why Microsoft's 10 reasons not to use Google Apps suck" +kind: article +slug: 10-reasons-why-microsofts-10-reasons-not-to-use-google-apps-suck +created_at: 2007-09-11 +tags: + - General + - Blog + - Google + - Discussion +--- + +You may have already read the 10 reasons why Microsoft thinks the enterprise should not use Google Apps. Well, here's my response: + +1. Google touts having enterprise level customers but how many “USERS” of their applications truly exist within the enterprise? + +Does this matter? There are a lot of private users on Google Apps for sure, but there are quite a few large companies (with 10.000+ users) using Google Apps - and they are happy with it! How many enterprise users can say the same about Microsoft Windows and Office in the enterprise? + +2. Google has a history of releasing incomplete products, calling them beta software, and issuing updates on a “known only to Google” schedule – this flies in the face of what enterprises want and need in their technology partners – what is Google doing that indicates they are in lock step with customer needs? + +This one makes me laugh. Google is being honest about their software not being finished. And they don't get hopes up by setting a release date that they can't meet. I don't think I have to go into a discussion about how Windows Vista doesn't include all the promised features and the several delays before it's final release. + +3. Google touts the low cost of their apps –not only price but the absence of need for hardware, storage or maintenance for Google Apps. BUT if GAPE is indeed a complement to MSFT Office, the costs actually become greater for a company as they now have two IT systems to run and manage and maintain. Doesn’t this result in increased complexity and increased costs? + +Very funny, again. There is no longer the need for Microsoft Office or the services that provide e-mail services like Exchange. There may even not be a need for Microsoft Windows anymore, because Google Apps works perfectly on Macs and Linux. And I think that managing Google Apps is a lot easier than managing a cluster of Microsoft servers. + +4. Google’s primary focus is on ad funded search. Their enterprise focus and now apps exist on the very fringe and in combination with other fringe services only account for 1% of the company’s revenue. What happens if Google executes poorly? Do they shut down given it will them in a minimal and short term way? Should customers trust that this won’t happen? + +This reason is actually the only one that might be valid. But, isn't this true for any company? Google does generate a lot of revenue from it's ad system. What Google tends to do with it's Google Apps suite is not only providing an alternative for Microsoft's office, but also binding users to Google. A Google Apps users will be more likely to use Google Search than a non-apps users. + +5. Google’s apps only work if an enterprise has no power users, employees are always online, enterprises haven’t built custom Office apps – doesn’t this equal a very small % of global information workers today? –On a feature comparison basis, it’s not surprising that Microsoft has a huge lead. + +Microsoft is still ignoring that "less is more". It's not about who has the most features anymore. It's about ease of use and service. Most users -are- always online with broadband internet connections and WiFi. Office does have a lot of nifty features, but only a small % of power users use these features (if they even know the features exist and know how to use them properly). + +6. Google apps don’t have essential document creation features like support for headers, footers, tables of content, footnotes, etc. Additionally, while customers can collaborate on basic docs without the above noted features, to collaborate on detailed docs, a company must implement a two part process – work together on the basic doc, save it to Word or Excel and then send via email for final edits. Yes they have a $50 price tag, but with the inefficiencies created by just this one cycle, how much do GAPE really cost – and can you afford the fidelity loss? + + +Again, Microsoft thinks we can't do without it. Google Apps is not about creating huge reports and stuff. It's about collaboration. Users collaborate, and when the document is cleared for release (or whenever you need the fancy stuff), you can use any tool (Pages? Adobe InDesign?) to make it all look sexy. Just a simple question: how many of the documents and spreadsheets you make are for pubishing and which are just quick doc for gathering and sharing thoughts with your co-workers? Personally, I use Google Docs for writing all my documents. When it needs to go to press, I export it and format it properly with Pages. No problem. + +7. Enterprise companies have to constantly think about government regulations and standards – while Google can store a lot of data for enterprises on Google servers, there is no easy to use, automated way for enterprises to regularly delete data, issue a legal hold for specific docs or bring copies into the corp. What happens if a company needs to respond to government regulations bodies? Google touts 99.9% uptime for their apps but what few people realize that promise is for Gmail only. Equally alarming is the definition Google has for “downtime” – ten consecutive minutes of downtime. What happens if throughout the day Google is down 7 minutes each hour? What does 7 minutes each hour for a full work day that cost an enterprise? + +Again, this makes me laugh! How many minutes a day do you spend restarting, scanning for viruses and spyware on Windows? How often do you need to make backups because you need to restore/reinstall Windows? What is the up-time guarantee of an Exchange server (yes, including possible hardware failure)? There are government rules everywhere, but in my opinion this is a policy thing. + +8. In the world of business, it is always on and always connected. As such, having access to technical support 24/7 is essential. If a company deploys Google Apps and there is a technical issue at 8pm PST, Sorry. Google’s tech support is open M-F 1AM-6PM PST – are these the new hours of global business? And if a customer’s “designated administrator” is not available (a requirement) does business just stop? + +"Always on and always connected". Nice. In reason #5 Microsoft points out that users are not always online. What about that? How about the companies internet provider? Are they on call 24 hours a day? Just a note, when I want telephone support from Microsoft I can call them from 6am - 6pm (Pacific) and it'll cost me $245. If you call after-hours, you're set back $490. How much did you say Google's support cost? + +By the way, Google does monitor its services/servers very well. If there's a problem, it will be fixed rather quickly. With Microsoft, you'll have to go and fix it yourself (even when you pay $490). + +(link to Micorsoft's support page) + +9. Google says that enterprise customers use only 10% of the features in today’s productivity applications which implies that EVERYONE needs the SAME 10% of the feature when in fact it is very clear that in each company there are specific roles people play that demands access to specific information – how does Google’s generic strategy address role specific needs? + +Google offers a flexible service. In stead of customizing the crap out of the application for every possible role a user could have, they offer a generic application. It's the 'one size - fits all' principle. + +10. With Google apps in perpetual beta and Google controlling when and if they rollout specific features and functionality, customers have minimal if any control over the timing of product rollouts and features – how do 1) I know how to strategically plan and train and 2) get the features and functionality I have specifically requested? How much money does not knowing cost? + +This just point 2 rephrased. To be more specific: do you have to plan? No - Google does the planning and testing for you. Training? Google keeps it's apps simple and easy to understand. In terms of planning, you probably don't know when something is released, but the same can be said for Microsoft. People expected WinFS in Vista, where is it? How many delays did Windows Vista see? How much did knowing when it was released (and when it got delayed, again) cost? + +I have written this post not to bash Microsoft, but to illustrate how single minded some companies can be. It's not just 10 questions enterprises need to ask themselves, it's indeed 10 reasons not to use Google Apps. Although for some of these reasons may be valid for some companies, the majority of enterprises and users can do their daily work with Google Apps, and in my opinion, they should. + +Currently I use Google Apps (free edition) to manage Ariejan.net. At Kabisa ICT we use Google Apps premier with great pleasure. We have not encountered any issues at all, and we really like the collaboration features included. \ No newline at end of file diff --git a/content/posts/2007-09-17-railsconf-europe-2007.md b/content/posts/2007-09-17-railsconf-europe-2007.md new file mode 100644 index 0000000..8503e46 --- /dev/null +++ b/content/posts/2007-09-17-railsconf-europe-2007.md @@ -0,0 +1,15 @@ +--- +title: "RailsConf Europe 2007!" +kind: article +slug: railsconf-europe-2007 +created_at: 2007-09-17 +tags: + - General + - Blog + - Personal + - RubyOnRails +--- + +Well, after a long trip, I arrived at my hotel yesterday in Berlin. Today I, and my mates from Kabisa, have joined RailsConf Europe 2007. I don't think I'll be giving a full, in-detail report of everything that happens, but I will let you know anything worth your (and my) time. + +If you happen to be in Berlin these days, feel free to drop me an e-mail at ariejan at ariejan.net and maybe we can have chat or something. I hope to hear from you! \ No newline at end of file diff --git a/content/posts/2007-09-24-rails-20-new-features.md b/content/posts/2007-09-24-rails-20-new-features.md new file mode 100644 index 0000000..d69780a --- /dev/null +++ b/content/posts/2007-09-24-rails-20-new-features.md @@ -0,0 +1,87 @@ +--- +title: "Rails 2.0 New Features" +kind: article +slug: rails-20-new-features +created_at: 2007-09-24 +tags: + - General + - Features +--- + +As David Heinemeier Hansson already told us all during his RailsConfEurope 2007 keynote, it's time to take off the party hats. It's no longer at time to celebrate all the new stuff we get. It's time to celebrate what we have already. + +With this statement DHH ends the revolution of Rails. During the past three years a lot of new and exiting features were added to Rails. However, now the time has come to evolve Rails further. No more new and exiting stuff, but fine tuning. Making things even better than they already are. + +So, Rails 2.0 will not contain any major new features. But don't despair, there are quite a few nifty changes that you'll like to know about. + +1. HTTP Authentication + +HTTP Authentication is a great way to limit access to specific areas of your application that regular users won't access. It might even be a good second layer of security for your administrator interface. Rails 2.0 will include easy methods to get HTTP authentication working for you. + +2. HTTP Performance + +The performance of a web application goes down the drain when you add too much JavaScript and stylesheets. Each and every file must be downloaded separately. Rails 2.0 will be able to take all the javascript files, stuff 'em together, compress that one file and sent that to the client, where it will be cached. + +
      <%= javascript_include_tag :all, :cache => true %>
      +<%= stylesheet_link_tag :all, :cache => true %>
      + +3. Asset Server + +If you have a large (and busy) site, serving static files can be quite a performance issue. Rails 2.0 adds the notion of an "asset server". An asset server (in combination with item #2) will serve static content quickly, allowing your app to respond even faster to a user's request. To enable asset hosts, add the following line to your configuration: + +
      config.action_controller.asset_host = 'assets%d.example.com'
      + +You can even cycle through multiple asset servers by simply creating the appropriate CNAME records in DNS. Neat, eh? + +3. The Debugger + +"The Debugger" is here! Simple add 'debugger' to your app, and it will stop just there for you to step in and debug your application. This is a very powerful way debugging your apps quickly and with ease. + +4. Query Cache + +Query Caching is something that normally happens in a database server, not now. ActiveRecord keeps a cache of queries and uses it when the same query is made again. You don't have to worry about anything, not even expiring your cache, because this is done automatically when you insert, update or delete any data that's related to the cache. + +5. Environment Configuration + +If you have ever developed a serious Rails application, you know that your environment.rb can become a mess very quickly. Rails 2.0 allows you to split up your configuration into multiple files to keep things clean. (And you can also reuse your common configuration files for other apps as well.) + +6. ActionTemplate Renderer + +Say good bye to the trusty old rhtml and rxml files! There are new conventions for templates that will tell you more about what type of file the generate and which parser is used to render the templates. + +Normal HTML views will get the .html.erb extension from now on (formerly .rhtml). If you want to build an XML file with "Builder", you would use: template.xml.builder. + +7. Sexy Migrations + +Sexy Migrations have been around for some time as a plugin. Some people love them, others don't. I still have my doubts about it, but at least it's less code to write, and it keeps things more readable, which is good, I guess. + +Let's take an old migration: + +
      create_table :people do |t|
      +  t.column :first_name, :string, :null => false
      +  t.column :last_name, :string, :null => false
      +  t.column :group_id, :integer
      +  t.column :description, :text
      +  t.column :created_at, :datetime
      +  t.column :updated_at, :datetime
      +end
      + + We now have this: +
      create_table :people do |t|
      +  t.integer :group_id
      +  t.string :first_name, :last_name, :null => false
      +  t.text :description
      +  t.timestamps
      +end
      + +8. Plugin Mania + +A lot of methods/features have been deprecated in Rails 2.0. Some non-core components like in-place editing have been moved to separate plugins. You will also find plugins like resource_feeder and open_id_authentication. Let's hope that OpenID finally takes off now. + +9. Thou shalt use MIT + +This one I hadn't heard at the keynote, but had to read it on the internet. + +"Due to licensing confusion for people using plugins, the new plugin generator now creates a license file by default. The assumption is that you are distributing under the MIT license otherwise you have to modify the file to meet your needs." + +I think Rails 2.0 will be quite a nice. Apps will be a bit more manageable and performance will be better also. Now, it's just waiting for DHH to release the "2.0 Preview Release" he promised. \ No newline at end of file diff --git a/content/posts/2007-09-24-whooop-here-it-is-the-new-ariejannet.md b/content/posts/2007-09-24-whooop-here-it-is-the-new-ariejannet.md new file mode 100644 index 0000000..c6ab9e4 --- /dev/null +++ b/content/posts/2007-09-24-whooop-here-it-is-the-new-ariejannet.md @@ -0,0 +1,25 @@ +--- +title: "Whooop - here it is! The new Ariejan.net!" +kind: article +slug: whooop-here-it-is-the-new-ariejannet +created_at: 2007-09-24 +tags: + - General + - Blog +--- + +Wow! Here it is then! The fully restyled, revamped and repimped Ariejan.net! (I hope you like it). So, "what's changed?", you may ask. + +
        +
      • I've upgraded to WordPress 2.3, which has very sexy tagging support.
      • +
      • A new template with a bit more of my personal taste included (blue!)
      • +
      • A major division of articles: Blog posts (which are regular messages from me to the world) and featured articles (which are the good stuff you come for).
      • +
      • I've finally written the about page (although a photo is still missing)
      • +
      • I've got Flickr support lined up - ready to go when I can afford myself a Canon 400D SLR Camera (or Rebel XTi for you American folk). (help me out?)
      • +
      + +My goal is to publish a bit more on the advanced Rails topics I encounter these days at work. Also, I'm still working on my Subversion Kickstart e-book, which will be published before the end of this year. (Thanks to Josh Blair for pointing out I was still working on it.) + +If you have any Rails related questions (especially if you live in Europe) please let me know. We at Kabisa are always happy to free up some of our time to help you out. + +Thanks for reading! Please grab my RSS Feed to stay up to date auotma \ No newline at end of file diff --git a/content/posts/2007-09-25-the-glorious-canon-eos-400d-digital.md b/content/posts/2007-09-25-the-glorious-canon-eos-400d-digital.md new file mode 100644 index 0000000..4c6d9e9 --- /dev/null +++ b/content/posts/2007-09-25-the-glorious-canon-eos-400d-digital.md @@ -0,0 +1,18 @@ +--- +title: "The Glorious Canon EOS 400D Digital" +kind: article +slug: the-glorious-canon-eos-400d-digital +created_at: 2007-09-25 +tags: + - Blog + - Blogroll + - Photography + - Flickr + - Canon +--- + +Well, I finally made the big leap into (semi-)professional photography. I bought a Canon EOS 400D Digital SLR Camera. (That's the XTi Rebel for you American folk) This nifty machine allows me to take very beautiful pictures, and I already had a little practice yesterday. + +With it's 18-55mm lens, I can make quite nice close-up photo's, as you can see in the example below. +

      Close-up Flower

      +You can check out my personal Flickr page to see what new pictures I've taken lately. (No, I'm not currently available for photo shoots, thank you very much.) \ No newline at end of file diff --git a/content/posts/2007-09-25-wordpress-23-released.md b/content/posts/2007-09-25-wordpress-23-released.md new file mode 100644 index 0000000..8112ce9 --- /dev/null +++ b/content/posts/2007-09-25-wordpress-23-released.md @@ -0,0 +1,23 @@ +--- +title: "Wordpress 2.3 released!" +kind: article +slug: wordpress-23-released +created_at: 2007-09-25 +tags: + - General + - Wordpress + - Blog + - Releases +--- + +Today Wordpress 2.3 was released (see the release notes). I've already updated Ariejan.net, of course, and I'm now able to use all of the following neat new features: +
        +
      • Tagging (you can convert your categories to tags if you like)
      • +
      • Update Notifications when new versions of your plugins or WordPress are released
      • +
      • Improved post and draft management
      • +
      • Mark articles as 'pending review'
      • +
      • A new and improved visual editor (which doesn't suck! Wow!)
      • +
      +I highly recommend you check out the the current WordPress release from SVN. This will make future updates very easy. Read more on that here. + +So, go ahead, grab that new WordPress! \ No newline at end of file diff --git a/content/posts/2007-09-26-flash-not-clearing-after-a-request.md b/content/posts/2007-09-26-flash-not-clearing-after-a-request.md new file mode 100644 index 0000000..e0f2b43 --- /dev/null +++ b/content/posts/2007-09-26-flash-not-clearing-after-a-request.md @@ -0,0 +1,21 @@ +--- +title: "Flash not clearing after a request?" +kind: article +slug: flash-not-clearing-after-a-request +created_at: 2007-09-26 +tags: + - General + - RubyOnRails + - Features + - Tips +--- + +We all know "The Flash" to be a very useful tool in almost every application we write. What does "The Flash" actually do? +
      The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed to the very next action and then cleared out.
      +Well, that's all nice, but what if you notice that your flash is not cleared, and is shown in one or more subsequent requests as well? + +The reason your flash is not cleared is that it will only be cleared if you render the current action, or if you redirect to another action. If you render another action or template, the flash will not be cleared out. + +Solving this is quite easy, actually. You only need to know how to do it. Use the following code in combination with render to show it only for the current action: +
      flash.now[:notice] = "Render, render! This will only show up once."
      +That's all there is to it really. \ No newline at end of file diff --git a/content/posts/2007-10-08-google-increases-storage.md b/content/posts/2007-10-08-google-increases-storage.md new file mode 100644 index 0000000..c561663 --- /dev/null +++ b/content/posts/2007-10-08-google-increases-storage.md @@ -0,0 +1,18 @@ +--- +title: "Google increases storage!" +kind: article +slug: google-increases-storage +created_at: 2007-10-08 +tags: + - General + - Blog + - Google + - Apps + - GMail +--- + +Google today increased the storage available to Google App users. + +Before free app users (like me) has 2Gb available, and premium users could store up to 10Gb of mail and documents. + +Today, Google upgraded the premium accounts to 25Gb! Store for the free version of Google Apps has been leveled with the current Gmail service (2910Mb at the moment). \ No newline at end of file diff --git a/content/posts/2007-10-17-party-time.md b/content/posts/2007-10-17-party-time.md new file mode 100644 index 0000000..b4f1a41 --- /dev/null +++ b/content/posts/2007-10-17-party-time.md @@ -0,0 +1,18 @@ +--- +title: "Party time!" +kind: article +slug: party-time +created_at: 2007-10-17 +tags: + - General + - Blog + - birthday +--- + +It's that time of the year again... my birthday. I'm turning 27 today and I'll be celebrating this today with friends and family. + +As to let you know what I've been up to the past few weeks: Flickr Phototset, FilmBASE, Linked-in and some "classified" stuff I can't link to. + +Feel free to congratulate me with a comment, or drop me an e-mail. + +And oh, me and Kabisa are accepting Rails projects, if you have any. \ No newline at end of file diff --git a/content/posts/2007-11-15-bash-it-number-of-messages-in-postfix-queue.md b/content/posts/2007-11-15-bash-it-number-of-messages-in-postfix-queue.md new file mode 100644 index 0000000..71a8a65 --- /dev/null +++ b/content/posts/2007-11-15-bash-it-number-of-messages-in-postfix-queue.md @@ -0,0 +1,26 @@ +--- +title: "Bash it! - Number of messages in Postfix queue" +kind: article +slug: bash-it-number-of-messages-in-postfix-queue +created_at: 2007-11-15 +tags: + - Blog + - Features + - Linux + - BASH + - Bash IT + - shell + - postfix + - scripting + - mail + - server + - queue + - email + - messages +--- + +Got bash? Here's a nice snippet that will return the number of messages currently in the postfix queue. + +
      postqueue -p | tail -n 1 | cut -d' ' -f5
      + +Feel free to post any updates or improvements. \ No newline at end of file diff --git a/content/posts/2007-11-27-railsjobsnl-ruby-on-rails-jobs-in-the-netherlands.md b/content/posts/2007-11-27-railsjobsnl-ruby-on-rails-jobs-in-the-netherlands.md new file mode 100644 index 0000000..c4186f4 --- /dev/null +++ b/content/posts/2007-11-27-railsjobsnl-ruby-on-rails-jobs-in-the-netherlands.md @@ -0,0 +1,24 @@ +--- +title: "RailsJobs.nl - Ruby on Rails Jobs in The Netherlands" +kind: article +slug: railsjobsnl-ruby-on-rails-jobs-in-the-netherlands +created_at: 2007-11-27 +tags: + - General + - Blog + - Ruby + - Ruby on Rails + - Rails + - Jobs + - RailsJobs.nl +--- + +The time is now ripe to announce RailsJobs.nl - A shiny new jobboard for Ruby on Rails developers who are seeking a job in the Netherlands or Belgium. I started the site a few days ago and business and job seekers have started to find RailsJobs.nl. + +If you're a business looking for Rails developers, feel free to add your job now. Use the code 'RJINTRO' to get a free 30-day trial period. + +RailsJobs.nl is a private site, which I developed during the weekend. At first I planned the site to be free for all, but I've decided to charge businesses for posting jobs. This will ensure no spam, and only quality job offerings. It will also help me cover hosting bills and I can invest some of the money in advertising the site on quality websites. + +Well, I'm not sure what to say more. Just hop over to RailsJobs.nl and see for yourself. + +Oh, I have implemented a handy RSS Feed as well, so you can easily stay up to date on new job offerings. \ No newline at end of file diff --git a/content/posts/2007-11-30-mysql-reset-the-auto-increment-value-of-a-table.md b/content/posts/2007-11-30-mysql-reset-the-auto-increment-value-of-a-table.md new file mode 100644 index 0000000..86304eb --- /dev/null +++ b/content/posts/2007-11-30-mysql-reset-the-auto-increment-value-of-a-table.md @@ -0,0 +1,24 @@ +--- +title: "MySQL: (Re)set the auto-increment value of a table" +kind: article +slug: mysql-reset-the-auto-increment-value-of-a-table +created_at: 2007-11-30 +tags: + - General + - Blog + - Databases + - MySQL + - development +--- + +Sometimes it's necessary to set the starting point of a MySQL auto-increment value. + +Normally, MySQL starts auto-incrementing at 1. But let's say you want to start at 10.000, because you want at least a five figure number. You can use the following query to set the MySQL auto-index: + +
      ALTER TABLE some_table AUTO_INCREMENT=10000
      + +If you want to delete all records from your table and restart auto-index at 1, you might be tempted to run a DELETE query, followed by the above example, setting the auto increment value to 1. There is a shortcut, however: + +
      TRUNCATE TABLE some_table
      + +This will basically reset the table, deleting all data and resetting the auto increment index. Do not that the truncate command is a hard-reset option. For instance, any triggers "ON DELETE" will not be fired when using truncate. \ No newline at end of file diff --git a/content/posts/2007-12-06-rails-calculated-column-caching.md b/content/posts/2007-12-06-rails-calculated-column-caching.md new file mode 100644 index 0000000..3a319c4 --- /dev/null +++ b/content/posts/2007-12-06-rails-calculated-column-caching.md @@ -0,0 +1,49 @@ +--- +title: "Rails: calculated column caching" +kind: article +slug: rails-calculated-column-caching +created_at: 2007-12-06 +tags: + - Blog + - Features + - Ruby + - Ruby on Rails + - Rails + - cache + - sort + - order + - find + - select + - join +--- + +Sometimes you're working on a Rails project and you think: "hey! This should be easy!". Well, most of the time it is. I'm working on a project that allows people to rate objects (what they really are doesn't matter at all). + +I'm using the acts_as_rateable plugin which creates an extra database table containing all ratings. I also have a table with my objects. Using the plugin I'm now able to do the following: + +
      obj = Object.find(:first)
      +obj.add_rating Rating.new(:rating => 4)
      +obj.add_rating Rating.new(:rating => 5)
      +obj.rating
      +=> 4.5
      + +This works all perfectly, until you want to sort objects by rating. You could construct a huge SQL query to join the two tables, but that's not really efficient, especially when your database gets bigger. + +The solution is very easy and even more elegant. Use a cache! For this, you'll first have to add a new field to the objects table. Do this in a migration: + +
      add_column :objects, :rating_cache, :float
      + +Now, in the Object model, add the following method: + +
      def rate_with(rating)
      +  add_rating(rating)
      +  update_attribute('rating_cache', self.rating)
      +end
      + +You'll need to change your controller from using #add_rating to #rate_with. The syntax is exactly the same. Now, when you add a rating, we also store the average rating in the rating_cache column. + +To get back to the sorting problem, you can now use the rating_cache column to sort Objects. + +
      Object.find(:all, :order => 'rating_cache DESC')
      + +Of course, you can use this trick on all sorts of relations. Have fun with it. \ No newline at end of file diff --git a/content/posts/2007-12-07-wil-jij-mijn-collega-zijn-kabisa-is-hiring.md b/content/posts/2007-12-07-wil-jij-mijn-collega-zijn-kabisa-is-hiring.md new file mode 100644 index 0000000..b1bfd1d --- /dev/null +++ b/content/posts/2007-12-07-wil-jij-mijn-collega-zijn-kabisa-is-hiring.md @@ -0,0 +1,35 @@ +--- +title: "Wil jij mijn collega zijn? - Kabisa is hiring!" +kind: article +slug: wil-jij-mijn-collega-zijn-kabisa-is-hiring +created_at: 2007-12-07 +tags: + - General + - Blog + - Kabisa + - promo +--- + +Ken jij een ICT’er? + +Zoals je waarschijnlijk al wel weet, werk ik al een tijdje bij Kabisa, en ik ben op zoek naar nieuwe collega's! + +Ben jij een een ICT'er (liefst met Java of Ruby ervaring, maar dat is niet vereist), laat dan even je gegevens achter op jobs.kabisa.nl. + +Ook als je iemand kent die ICT'er is, en toe is aan een nieuwe uitdaging, kun je deze persoon opgeven op jobs.kabisa.nl. + +De eerste 100 aanmeldingen ontvangen een Nationale Bioscoopbon. Bovendien wordt tussen alle aanmeldingen op 29 februari 2008 een Apple iPod Touch verloot!! + +Heb je vragen over Kabisa? Neem gerust even contact op met me op. + +English version, for the non-Dutch folk + +As you may already know, I've been working at Kabisa for quite some time. Now, I'm looking for new colleagues. + +Are you a programmer (with Java or Ruby experience, but it's not required), please leave your credentials at jobs.kabisa.nl. + +If you know a programmer who is ready for a new challenge, drop his or her credentials (and your own) at jobs.kabisa.nl. + +The first 100 applications will be rewarded with a national cinema voucher. At February 29th, 2008 we will also pick one of the applications and reward it with an Apple iPod Touch! + +Do you have any questions about working at Kabisa? Feel free to contact me now. \ No newline at end of file diff --git a/content/posts/2007-12-12-how-to-install-mysql-on-ubuntudebian.md b/content/posts/2007-12-12-how-to-install-mysql-on-ubuntudebian.md new file mode 100644 index 0000000..2083c7e --- /dev/null +++ b/content/posts/2007-12-12-how-to-install-mysql-on-ubuntudebian.md @@ -0,0 +1,45 @@ +--- +title: "How to install MySQL on Ubuntu/Debian" +kind: article +slug: how-to-install-mysql-on-ubuntudebian +created_at: 2007-12-12 +tags: + - Ubuntu + - MySQL + - debian +--- +It may seem easy for some, but for others, installing MySQL on Ubuntu or Debian Linux is not an easy task. This article explains to you how to install the MySQL Server and Client packages on a Ubuntu/Debian system. + +First of all, make sure your package management tools are up-to-date. Also make sure you install all the latest software available. + + sudo apt-get update + sudo apt-get dist-upgrade + +After a few moments (or minutes, depending on the state of your system), you're ready to install MySQL. +~ +By default, recent Ubuntu/Debian systems install a MySQL Server from the 5-branch. This is a good thing, so don't worry. + +First, install the MySQL server and client packages: + + sudo apt-get install mysql-server mysql-client + +When done, you have a MySQL database read to rock 'n roll. However, there's more to do. + +You need to set a root password, for starters. MySQL has it's own user accounts, which are not related to the user accounts on your Linux machine. By default, the root account of the MySQL Server is empty. You need to set it. Please replace 'mypassword' with your actual password and myhostname with your actual hostname. + + sudo mysqladmin -u root -h localhost password 'mypassword' + sudo mysqladmin -u root -h myhostname password 'mypassword' + +Now, you probably don't want just the MySQL Server. Most likely you have Apache+PHP already installed, and want MySQL to go with that. Here are some libraries you need to install to make MySQL available to PHP: + + sudo apt-get install php5-mysql + +Or for Ruby: + + sudo apt-get install libmysql-ruby + +You can now access your MySQL server like this: + + mysql -u root -p + +Have fun using MySQL Server. diff --git a/content/posts/2007-12-12-run-internet-explorer-5-55-6-and-7-natively-on-mac-os-x-leopard-or-tiger.md b/content/posts/2007-12-12-run-internet-explorer-5-55-6-and-7-natively-on-mac-os-x-leopard-or-tiger.md new file mode 100644 index 0000000..2de1293 --- /dev/null +++ b/content/posts/2007-12-12-run-internet-explorer-5-55-6-and-7-natively-on-mac-os-x-leopard-or-tiger.md @@ -0,0 +1,33 @@ +--- +title: "Run Internet Explorer 5, 5.5, 6 and 7 natively on Mac OS X Leopard or Tiger" +kind: article +slug: run-internet-explorer-5-55-6-and-7-natively-on-mac-os-x-leopard-or-tiger +created_at: 2007-12-12 +tags: + - Blog + - Features + - Apple + - Mac OS X + - MacOSX + - osx + - leopard + - tiger + - mac + - windows + - internetexplorer + - explorer + - IE + - IE7 + - IE6 + - parallels +--- + +As a web developer, you probably know all about browsers. They suck. Well, some more than others. But, if you develop apps for Windows users, you'll have to test your app with Internet Explorer. + +ies4osxNow, as a good Rails developer, I'm using a Mac. I can test apps with FireFox, Safari and Opera without problems. But Internet Explorer is always a problem. I used Parallels for a while to run an instance of Windows XP to test with IE. But this in itself presented me with a problem: you can't, without nasty hacks and a lot of trouble, run IE6 en IE7 side-by-side. + +My only option seemed to run two Parallels sessions, one with IE6, the other with IE7. Since every images takes about 5 Gb of disk space, I just wasted 10 Gb of disk space to test web apps with Internet Explorer. This is kind of ridiculous. + +But, now there is a very, very nice solution to this problem that every Mac-oriented web developer should know about: ies4osx This solution uses darwine (±70Mb), a wine version for OS X, using X11 and allows you to install IE 5, 5.5, 6 and even IE7. All at once, right in Mac OS X. You can run any of these browsers, from the same system, all at once in parallel! + +Although this allows me to use Internet Explorer the way I want it to, I'm still not a fan... \ No newline at end of file diff --git a/content/posts/2007-12-20-for-you-merry-christmas-and-a-happy-2008.md b/content/posts/2007-12-20-for-you-merry-christmas-and-a-happy-2008.md new file mode 100644 index 0000000..46ef6dc --- /dev/null +++ b/content/posts/2007-12-20-for-you-merry-christmas-and-a-happy-2008.md @@ -0,0 +1,15 @@ +--- +title: "For You: Merry Christmas and a Happy 2008!!!" +kind: article +slug: for-you-merry-christmas-and-a-happy-2008 +created_at: 2007-12-20 +tags: + - General + - Blog +--- + +Merry Christmas - Happy 2008!!!’ + +We (Laura and I) wish you a very merry Christmas and a happy 2008! + +I hope to welcome you back on Ariejan.net in 2008. Happy Holidays! \ No newline at end of file diff --git a/content/posts/2007-12-31-write-a-dvd-video-from-the-linux-console.md b/content/posts/2007-12-31-write-a-dvd-video-from-the-linux-console.md new file mode 100644 index 0000000..d744b87 --- /dev/null +++ b/content/posts/2007-12-31-write-a-dvd-video-from-the-linux-console.md @@ -0,0 +1,43 @@ +--- +title: "Write a DVD-Video from the Linux console" +kind: article +slug: write-a-dvd-video-from-the-linux-console +created_at: 2007-12-31 +tags: + - Blog + - DVD + - Linux + - Blogroll + - Video + - burn + - burning + - writing +--- + +This is probably my last post for this year, and it's not about Ruby on Rails! Or web development! It's about how to burn a DVD-Video from the Linux console. + +I know, there are tons of fancy apps (with a decent GUI) that allow you to create DVD-Video disks from Gnome or KDE. But, my Linux machine has no monitor attached and I don't care for hogging up resources by running X. + +All you really need to burn a DVD-Video is one packages: dvd+rw-tools. If you're on Debian or Ubuntu, just run this: + +
      apt-get install dvd+rw-tools
      + +So, I have this VIDEO_TS folder and a blank DVD in my DVD driver (which is known to my system as /dev/sr0). To make a correct DVD, you'll also need an AUDIO_TS folder. It doesn't matter if it's empty, it should be there. + +I'm assuming you're all doing this as root. If not, just add 'sudo' in front of every command you perform + +
      mkdir AUDIO_TS
      + +Next, we need to set the correct permissions on the VIDEO_TS, AUDIO_TS and the files contained in these directories. + +
      chown -R root:root VIDEO_TS AUDIO_TS
      +chmod 500 VIDEO_TS AUDIO_TS
      +chmod 400 VIDEO_TS/*
      + +Well, you already have all your files prepped, and you're good to go. + +The following command will burn you a nice video DVD. Make sure a blank DVD is inserted into your drive. As I said before, my drive is located at /dev/sr0. You system is probably different. Check your boot log (dmesg) for messages of a dvd driver. + +
      growisofs -dvd-compat -Z /dev/sr0 -dvd-video .
      + +It's very important not to forget the tailing dot, it tells growisofs that you want to use the current directory as the source for your DVD. Depending on the speed of your burner, you'll have a new DVD to watch in a few minutes. Enjoy! And a happy 2008! \ No newline at end of file diff --git a/content/posts/2008-01-03-review-parking-london.md b/content/posts/2008-01-03-review-parking-london.md new file mode 100644 index 0000000..eafe15b --- /dev/null +++ b/content/posts/2008-01-03-review-parking-london.md @@ -0,0 +1,26 @@ +--- +title: "Review: Parking London" +kind: article +slug: review-parking-london +created_at: 2008-01-03 +tags: + - Reviews +--- + +This is a paid review + +I've been asked to review a service named Parking London. What this service does is two things, really. Firstly, it allows owners of garages and parking spaces in London to offer their spots to others. A location can be specified, with a small description. Of course, the price for the monthly tenancy or sale is added. + +The service doesn't look very special at a first glance. Just a listing of available parking spots in London. When I took a closer look, I noticed a link "Map view". When I clicked it, a Google map popped up of the UK, and I was able to zoom in and see where all the available parking spaces where. Hovering over the pin points shows the monthly cost for them. Great! + +Being a good reviewer, I setup a scenario where I was a potential buyer who's looking for garage space in London. I have a budget of 250 pounds a month, and I want the garage to be in the north of the river Thames. + +This proved very difficult indeed. With a service like this, I expect to be able to specify some search criteria. I can enter a 'filter' parameter, but this didn't seem to do anything useful. + +There is an option, however to search for locations and postcodes. I gave it a try, but again, I found the results next to useless to me. + +I left the location issues for what they were, and focussed on the pricing. I'm not sure what a reasonable price for a garage is, but I set myself an imaginary budget of 250 pounds. I was not able to find any way to select items by price, or sort the listing by price to simplify my search. + +In my opinion, this service is really basic. A lot of time went into making the Google map work correctly, and it really does. It's easy to locate a parking space near a location you want. + +Finding items with a certain pricing, or any other form of search was pretty much impossible. With a little work, this site might be a very useful tool for garage-seeking Londoners. \ No newline at end of file diff --git a/content/posts/2008-01-13-kabisa-blog.md b/content/posts/2008-01-13-kabisa-blog.md new file mode 100644 index 0000000..28c29df --- /dev/null +++ b/content/posts/2008-01-13-kabisa-blog.md @@ -0,0 +1,15 @@ +--- +title: "Kabisa Blog" +kind: article +slug: kabisa-blog +created_at: 2008-01-13 +tags: + - General + - Blog + - Kabisa + - Announcement +--- + +Well, we've got our blog up and running at Kabisa now! Our entire team will be posting interesting Ruby, Rails, Java and Subversion articles that may be of very good use to you. + +Check out the blog now on http://blog.kabisa.nl or grab the RSS Feed to stay up-to-date. \ No newline at end of file diff --git a/content/posts/2008-01-15-attack-of-the-killer-bunnies.md b/content/posts/2008-01-15-attack-of-the-killer-bunnies.md new file mode 100644 index 0000000..36ef754 --- /dev/null +++ b/content/posts/2008-01-15-attack-of-the-killer-bunnies.md @@ -0,0 +1,20 @@ +--- +title: "Attack of the Killer Bunnies" +kind: article +slug: attack-of-the-killer-bunnies +created_at: 2008-01-15 +tags: + - General +--- + +
      + + + Bunny killing the garden + Originally uploaded by ariejan + +
      +Well, here they go then! Just swooping in, eating all the green stuff from your garden!! Someone stop them!
      +
      +This was taken in the back-yard at Laura's parents house. The neighbours have about a dozen rabbits, but they are allowed to walk around as they wish, occasionally finding their way into other people's gardens. +
      \ No newline at end of file diff --git a/content/posts/2008-01-19-roles-admins-pretending-to-be-users.md b/content/posts/2008-01-19-roles-admins-pretending-to-be-users.md new file mode 100644 index 0000000..55f1441 --- /dev/null +++ b/content/posts/2008-01-19-roles-admins-pretending-to-be-users.md @@ -0,0 +1,14 @@ +--- +title: "Roles: Admins pretending to be users!" +kind: article +slug: roles-admins-pretending-to-be-users +created_at: 2008-01-19 +tags: + - Blog +--- + +Most applications have some sort of role system. You have administrators, who can do basically everything, semi-administrators and "regular Joe" users. + +Now, sometimes you want, as an administrator, be able to login as a user to solve some problem, or manage settings for a user, or whatever. + +Harm did a very nice write-up on how we did this in a Rails project of ours. You can read the article here. \ No newline at end of file diff --git a/content/posts/2008-01-22-your-help-is-needed-railsjobsnl.md b/content/posts/2008-01-22-your-help-is-needed-railsjobsnl.md new file mode 100644 index 0000000..706e2bc --- /dev/null +++ b/content/posts/2008-01-22-your-help-is-needed-railsjobsnl.md @@ -0,0 +1,13 @@ +--- +title: "Your help is needed! - Railsjobs.nl" +kind: article +slug: your-help-is-needed-railsjobsnl +created_at: 2008-01-22 +tags: + - Blog +--- + +Since I started Railsjobs.nl some time ago, I'd like to ask you a simple question. Thanks for putting in your $0.02 :) + + +
      {democracy:2}
      diff --git a/content/posts/2008-01-25-the-presidents-of-the-united-states-of-america-live.md b/content/posts/2008-01-25-the-presidents-of-the-united-states-of-america-live.md new file mode 100644 index 0000000..cd0ae50 --- /dev/null +++ b/content/posts/2008-01-25-the-presidents-of-the-united-states-of-america-live.md @@ -0,0 +1,16 @@ +--- +title: "The Presidents of the United States of America Live" +kind: article +slug: the-presidents-of-the-united-states-of-america-live +created_at: 2008-01-25 +tags: + - Blog + - PUSA + - Presidents +--- + +PUSA (C) Presidentsrock.com + +Yeah! I got myself some tickets to The Presidents of the United States of America, live at Effenaar, Eindhoven. + +To be honest with you, I've never been to a real concert before, so this is going to be *very* much fun for me! Among the few albums I bought back in the year 1995 was PUSA. \ No newline at end of file diff --git a/content/posts/2008-02-07-ruby-on-rails-plugin-throttler.md b/content/posts/2008-02-07-ruby-on-rails-plugin-throttler.md new file mode 100644 index 0000000..e147112 --- /dev/null +++ b/content/posts/2008-02-07-ruby-on-rails-plugin-throttler.md @@ -0,0 +1,19 @@ +--- +title: "Ruby on Rails plugin: Throttler" +kind: article +slug: ruby-on-rails-plugin-throttler +created_at: 2008-02-07 +tags: + - Blog + - Ruby + - Ruby on Rails + - Rails + - Plugins + - Throttling + - Throttler +--- + +For those of you who have missed it: I've released a plugin yesterday that allows you to throttle your Rails app. + +Read the original announcement and installation/usage instructions +Read how you can put Throttler to good use in your app \ No newline at end of file diff --git a/content/posts/2008-04-09-enabling-trac-email-notifications.md b/content/posts/2008-04-09-enabling-trac-email-notifications.md new file mode 100644 index 0000000..c09ad1c --- /dev/null +++ b/content/posts/2008-04-09-enabling-trac-email-notifications.md @@ -0,0 +1,35 @@ +--- +title: "Enabling Trac Email notifications" +kind: article +slug: enabling-trac-email-notifications +created_at: 2008-04-09 +tags: + - General + - Trac + - email + - notifications + - tricks +--- + +If you've ever reported a but to Ruby on Rails, you'll have noticed that their Trac has nice email notification feature. And I bet you want that in your Trac as well! + +Now, email notification are nothing exotic. You don't need any plugins, just an outgoing SMTP server and access to your trac.ini file. + +In your trac directory open up conf/trac.ini and look for the [notification] header. Make sure you have at least the following settings. Of course, make sure you enter valid values for your situation. + +
      always_notify_owner = true
      +always_notify_reporter = true
      +always_notify_updater = true
      +smtp_always_cc = trac-updates@mailinglist # Optional if you want to archive outgoing emails
      +smtp_enabled = true
      +smtp_from = trac@ariejan.net
      +smtp_from_name = Ariejan.net Trac
      +smtp_replyto = noreply@ariejan.net
      +smtp_user =
      +smtp_password =
      +smtp_server = localhost
      +smtp_port = 25
      + +There are a few more options like 'ignore_domains' if you don't want to send emails to certain domains. + +Update: good news for you GMail users! There is a 'use_tls' attribute. I think that if you set it to true, you can then specify GMail's SMTP server to send out mails. Of course, a better solution would be to setup Postfix to relay mail to GMail, and just leave Trac to it's default 'localhost' settings. \ No newline at end of file diff --git a/content/posts/2008-04-09-here-we-go-again-wordpress-25.md b/content/posts/2008-04-09-here-we-go-again-wordpress-25.md new file mode 100644 index 0000000..fe34aa3 --- /dev/null +++ b/content/posts/2008-04-09-here-we-go-again-wordpress-25.md @@ -0,0 +1,22 @@ +--- +title: "Here we go again: WordPress 2.5" +kind: article +slug: here-we-go-again-wordpress-25 +created_at: 2008-04-09 +tags: + - General + - Wordpress + - updates +--- + +Okay, here we go then. I've managed to drop Mephisto after only a few weeks of service. + +As a Rails developer, I liked the idea of running my own blog on something Rails. However, Mephisto was a big disappointment. Especially compared to WordPress 2.5. Mephisto has been on life support for quite a while now, and it's just too complex to be easy to hack. Now, don't get me wrong. I love hacking Ruby, but not too much on my blog. It "should just work". + +The reason I skipped to Mephisto in the first place was that my Wordpress 2.3 blog was very slow. I managed to track down the problem to the Twitter plugin, which was next to useless anyway. + +Now, with all the new funky stuff in 2.5, I switched back. I haven't managed to write anything substantial in the past weeks anyway, so it wasn't a problem to loose one post. + +There are many reasons for my not posting substantial stuff lately. One of them being my new hobby: photography (link). Check out my Flickr page and let me know what you think of my more recent photos. + +So long for now. Expect more from me soon. \ No newline at end of file diff --git a/content/posts/2008-04-09-rails-snippet-caching-expensive-calls.md b/content/posts/2008-04-09-rails-snippet-caching-expensive-calls.md new file mode 100644 index 0000000..cef124d --- /dev/null +++ b/content/posts/2008-04-09-rails-snippet-caching-expensive-calls.md @@ -0,0 +1,41 @@ +--- +title: "Rails Snippet: Caching expensive calls" +kind: article +slug: rails-snippet-caching-expensive-calls +created_at: 2008-04-09 +tags: + - General + - RubyOnRails + - Ruby + - Rails + - snippet +--- + +In Rails, from time to time, you may encounter you have a method you call several times, but which returns always the same result. For example, have the following: + +
      class Person < ActiveRecord::Base
      +  has_many :articles
      +
      +  def get_approved_articles
      +    self.articles.find(:all, :conditions => {:approved => true}, :order => 'approved_on DESC')
      +  end
      +end
      + +A query is fired every time you call Person#get_approved_articles. To cache the result of the query during this request, just add a bit of magic + +
      class Person < ActiveRecord::Base
      +  has_many :articles
      +
      +  def get_approved_articles
      +    @approved_articles ||= self.articles.find(:all, :conditions => {:approved => true}, :order => 'approved_on DESC')
      +  end
      +end
      + +This will return the @approved_articles value if it exists. If it doesn't, which is the first time you access the method, the query is run and stored in @approved_articles for later use. + +Note: I know it's much easier to define this kind of behaviour, but it's just an illustration. + +
      class Person < ActiveRecord::Base
      +  has_many :articles
      +  has_many :approved_articles, :class_name => "Article", :conditions => {:approved => true}, :order => 'approved_on DESC'
      +end
      \ No newline at end of file diff --git a/content/posts/2008-04-10-debian-etch-rmagick-loaderror.md b/content/posts/2008-04-10-debian-etch-rmagick-loaderror.md new file mode 100644 index 0000000..130f9be --- /dev/null +++ b/content/posts/2008-04-10-debian-etch-rmagick-loaderror.md @@ -0,0 +1,34 @@ +--- +title: "Debian Etch: RMagick LoadError" +kind: article +slug: debian-etch-rmagick-loaderror +created_at: 2008-04-10 +tags: + - General + - RubyOnRails + - Ruby + - Ruby on Rails + - Rails + - imagemagick + - libMagickWand + - magick + - rmagick +--- + +If you're on Debian Etch, you may encounter the following error + +
      libMagickWand.so.1: cannot open shared object file: No such file or directory - /usr/lib/ruby/gems/1.8/gems/rmagick-2.3.0/lib/RMagick2.so
      + +This basically means that the libMagickWand.so.1 file cannot be found. However, it is available on your system. All you need to do to fix it, is tell your box to look in the right place for the file. + +To fix this issue once and for all, open up /etc/ld.so.conf.d/whatever_file_is_here. The whatever_file_is_here is named after the kernel you have installed. + +In this file, add the following line at the bottom + +
      /usr/local/lib
      + +Save the file and next run the 'ldconfig' command. This will reread the configuration file you just edited. Now, restart your Rails app and you'll notice the error is gone and all is good again. + +
      ldconfig
      + +This change will be kept after you reboot, so you won't encounter this error any time soon again. \ No newline at end of file diff --git a/content/posts/2008-04-15-permanently-redirect-wordpress-pages.md b/content/posts/2008-04-15-permanently-redirect-wordpress-pages.md new file mode 100644 index 0000000..f152676 --- /dev/null +++ b/content/posts/2008-04-15-permanently-redirect-wordpress-pages.md @@ -0,0 +1,62 @@ +--- +title: "Permanently redirect WordPress pages" +kind: article +slug: permanently-redirect-wordpress-pages +created_at: 2008-04-15 +tags: + - General + - Wordpress + - apache + - apache2 + - rewrite + - mod_rewrite + - permalinks +--- + +After my trip to Mephisto some time back, I noticed that some pages were accessible from different URLs. After moving back to WordPress, these permalinks no longer work. + +I'm running WordPress with Apache2, so it shouldn't be too hard redirect those old permalinks to their new locations. (That's what rewriting is all about anyway). + +Here is the default .htaccess file generated by WordPress: + +
      # BEGIN WordPress
      +
      +RewriteEngine On
      +RewriteBase /
      +RewriteCond %{REQUEST_FILENAME} !-f
      +RewriteCond %{REQUEST_FILENAME} !-d
      +RewriteRule . /index.php [L]
      +
      +
      +# END WordPress
      + +As I said, it's generated by WordPress. It would be very unwise to edit this, because our changes might get lost in the future. + +The solution is simple. Add another block above the generated one that does permanent redirects. In this example I rewrite pages/svnsheet to http://ariejan.net/svncheatsheet. + +
      
      +RewriteEngine On
      +RewriteBase /
      +RewriteCond %{REQUEST_FILENAME} !-f
      +RewriteCond %{REQUEST_FILENAME} !-d
      +RewriteRule ^pages/svnsheet$ http://ariejan.net/svncheatsheet [R=301,L]
      +
      +
      +# BEGIN WordPress
      +
      +RewriteEngine On
      +RewriteBase /
      +RewriteCond %{REQUEST_FILENAME} !-f
      +RewriteCond %{REQUEST_FILENAME} !-d
      +RewriteRule . /index.php [L]
      +
      +
      +# END WordPress
      + +FYI, the R=301 means the browser (or search bot) receives a "Moved Permanently" message. L means this is the last rewrite rule to read. + +Of course, you can go nuts and add all sorts of funky regular expressions to rewrite URLs. + +The nice thing is that WordPress will only replaces the block between the BEGIN and END tags, keeping your rewrites in tact. + +Note: don't forget to restart apache if your rewrites don't seem to work. \ No newline at end of file diff --git a/content/posts/2008-04-23-git-using-the-stash.md b/content/posts/2008-04-23-git-using-the-stash.md new file mode 100644 index 0000000..42f8eab --- /dev/null +++ b/content/posts/2008-04-23-git-using-the-stash.md @@ -0,0 +1,58 @@ +--- +title: "GIT: Using the stash" +kind: article +slug: git-using-the-stash +created_at: 2008-04-23 +tags: + - Blog + - git + - stash +--- + +I bet the following has happened to you: you are happily working on a project and are in the middle of something. You are not ready to commit your changes, because you your tests don't pass yet. Then your client calls with a bug report that needs to be fixed right now. (You know how clients can be.) + +So, what do you do? Throw away your current changes to make the patch? Checkout a clean copy of your project to make the changes? No! You just stash your changes away, and make the patch! Afterward you grab your changes back and continue work. + +Git features The Stash, which is as much as a good place to store uncommitted changes. When you stash you changes, the will be stored, and your working copy will be reverted to HEAD (the last commit revision) of your code. + +When you restore your stash, you changes are reapplied and you continue working on your code. + +Stash your current changes + +
      $ git stash save 
      +Saved "WIP on master: e71813e..."
      + +List current stashes + +Yes, you can have more than one!! The stash works like a stack. Every time you save a new stash, it's put on top of the stack. + +
      $ git stash list
      +stash@{0}: WIP on master: e71813e..."
      + +Note the stash@{0} part? That's your stash ID, you'll need it to restore it later on. Let's do that right now. The stash ID changes with every stash you make. stash@{0} refers to the last stash you made. + +Apply a stash + +
      $ git stash apply stash@{0}
      + +You may notice the stash is still there after you have applied it. You can drop it if you don't need it any more. + +
      $ git stash drop stash@{0}
      + +Or, because the stash acts like a stack, you can pop off the last stash you saved: + +
      $ git stash pop
      + +If you want to wipe all your stashes away, run the 'clear' command: + +
      $ git stash clear
      + +It may very well be that you don't use stashes that often. If you just want to quickly stash your changes to restore them later, you can leave out the stash ID. + +
      $ git stash
      +...
      +$ git stash pop
      + +Feel free to experiment with the stash before using it on some really important work. + +Please leave a comment if you like this article, and would like to see more Git related stuff here on Ariejan.net. \ No newline at end of file diff --git a/content/posts/2008-05-04-how-to-compile-packages-on-debianubuntu-by-hand.md b/content/posts/2008-05-04-how-to-compile-packages-on-debianubuntu-by-hand.md new file mode 100644 index 0000000..f713f83 --- /dev/null +++ b/content/posts/2008-05-04-how-to-compile-packages-on-debianubuntu-by-hand.md @@ -0,0 +1,46 @@ +--- +title: "How to: Compile packages on Debian/Ubuntu by hand" +kind: article +slug: how-to-compile-packages-on-debianubuntu-by-hand +created_at: 2008-05-04 +tags: + - Blog + - Ubuntu + - debian + - source + - code + - src + - package + - packages + - deb +--- + +In some very rare situations you may find yourself in the need to recompile a Debian (or Ubuntu) package. Luckily for all of use, the great Debian packaging system makes this a piece of cake. + +Let's say we want to recompile mod_python for apache 2 to hook in to python 2.5, instead of the default 2.4. + +First, get every thing installed you may need to build the libapache2-mod-python package. + +
      $ apt-get build-dep libapache2-mod-python
      + +Okay, next let's grab the source for the package. The source will be unpacked to your current working directory, so it may be a good idea to create a seperate directory for this. + +
      $ mkdir src
      +$ cd src
      +$ apt-get source libapache2-mod-python
      + +In the case of this example, you don't need to do anything special to get python 2.5 linked. Just install the python2.5 and python2.5-dev packages. + +If you need to apply patches to the source or need to make any other modifications, this is the time! + +Okay, now go to the source directory and build the package. This is the tricky command here: + +
      $ dpkg-buildpackage -rfakeroot -b
      + +This will build the package. You will get some warnings and errors about missing GPG keys. This is okay. You are not the package maintainer, so your packages should not be marked as 'original'. + +You're now ready to install your compiled package. + +
      dpkg -i ../libapache2-mod-python-3.3.1-3-i386.deb
      + +That's all! You compiled and installed a package from source! \ No newline at end of file diff --git a/content/posts/2008-05-06-the-migration-that-cannot-be-undone-irreversible-migration.md b/content/posts/2008-05-06-the-migration-that-cannot-be-undone-irreversible-migration.md new file mode 100644 index 0000000..c163ec6 --- /dev/null +++ b/content/posts/2008-05-06-the-migration-that-cannot-be-undone-irreversible-migration.md @@ -0,0 +1,35 @@ +--- +title: "The migration that cannot be undone: Irreversible Migration" +kind: article +slug: the-migration-that-cannot-be-undone-irreversible-migration +created_at: 2008-05-06 +tags: + - General +--- + +Migrations have up and down methods, as we all know. But in some cases, your up method does things you can't undo in your down method. + +For example: + +
      def self.up
      +  # Change the zipcode from the current :integer to a :string type.
      +  change_column :address, :zipcode, :string
      +end
      + +Now, converting integers to strings will always work. But, you feel it coming, converting a string into an integer will not always be possible. In other words, we can't reverse this migration. + +That's why we should raise ActiveRecord::IrreversibleMigration in the down method. + +
      def self.down
      +  raise ActiveRecord::IrreversibleMigration
      +end
      + +Now, if you run your migration (upwards), you'll see it being applied like it shoud. However, if you try to go back, you'll see rake aborting with ActiveRecord::IrreversibleMigration. + +
      $ rake db:migrate VERSION=4
      +-- Database is migrated
      +$ rake db:migrate VERSION=3
      +-- Rake aborted!
      +-- ActiveRecord::IrreversibleMigration
      + +So, if you have things you can't undo, raise ActiveRecord::IrreversibleMigration in your migration's down method. \ No newline at end of file diff --git a/content/posts/2008-05-30-the-best-it-books-hand-picked-for-you.md b/content/posts/2008-05-30-the-best-it-books-hand-picked-for-you.md new file mode 100644 index 0000000..1318335 --- /dev/null +++ b/content/posts/2008-05-30-the-best-it-books-hand-picked-for-you.md @@ -0,0 +1,18 @@ +--- +title: "The best IT books hand-picked for you!" +kind: article +slug: the-best-it-books-hand-picked-for-you +created_at: 2008-05-30 +tags: + - Blog +--- + +Are you ready to dive into Rails? Want to familiarize yourself with the deeper dungeons of Ruby? Are you an aspiring game developer? Or maybe you just want to learn how to use Git or Subversion effectively? + +In any case, I've opened up a little book shop with a hand-picked selection of books on a variety of subjects, included Ruby and Rails, Game Development (with Java) and Version Control. + +Please, feel free to take a look around. All books are priced the same as Amazon.com, and you'd be supporting Ariejan.net in the process. + +I'm also open to suggestions for other categories with some hand-picked books. I'm already planning on Java and Databases categories. Feel free to post a comment with your tips. + +Thanks for shopping a Ariejan's Book Shop! \ No newline at end of file diff --git a/content/posts/2008-06-06-zoek-jij-n-uitdagende-baan.md b/content/posts/2008-06-06-zoek-jij-n-uitdagende-baan.md new file mode 100644 index 0000000..9b5f923 --- /dev/null +++ b/content/posts/2008-06-06-zoek-jij-n-uitdagende-baan.md @@ -0,0 +1,28 @@ +--- +title: "Zoek jij 'n uitdagende baan?? " +kind: article +slug: zoek-jij-n-uitdagende-baan +created_at: 2008-06-06 +tags: + - General + - RubyOnRails + - Ruby + - Rails + - baan + - banen + - rubyenrails +--- + +First off, sorry to all the English-reading people, but this post is intended for my Dutch audience. + +Even wat updates voor mijn Nederlandstalige publiek. Ik heb wat nieuwtjes voor jullie, dus lees snel verder! + +Allereerst wil ik even melden dat ik a.s. dinsdag (10 juni) te vinden zal zijn op RubyEnRails 2008. Dus, ben jij er ook, laat 't me even weten! Laat even 'n commentaartje achter, of nog beter: bel/sms me even dinsdag op 06-17103624, dan spreken we elkaar zeker! + +Dan nog even een oproep voor alle Nederlandse Ruby, Java en PHP developers. Als jij echt coole webapps kan (of wilt gaan) maken, dan moeten we praten! Kabisa zoekt namelijk op korte termijn Ruby en Java developers. Ik kan er zelf over meepraten: bij Kabisa werken is leuk, afwisselend en het biedt je alles wat je van een goede ICT job mag verwachten. + +Spreek me dinsdag even aan (zie m'n nummer hierboven) als je meer wilt weten over werken bij Kabisa! Of maak gewoon voor de lol even 'n praatje! + +Ben jij 'n Ruby, Java of PHP developer - en ben jij toe aan een leuke ICT baan? Neem dan 'ns contact met me op, want ik ben op zoek naar collega's! + +Tot dinsdag bij Ruby en Rails 2008!!! \ No newline at end of file diff --git a/content/posts/2008-07-08-ariejannet-link-party-07082008.md b/content/posts/2008-07-08-ariejannet-link-party-07082008.md new file mode 100644 index 0000000..7892f28 --- /dev/null +++ b/content/posts/2008-07-08-ariejannet-link-party-07082008.md @@ -0,0 +1,10 @@ +--- +title: "Ariejan.net Link Party 07/08/2008" +kind: article +slug: ariejannet-link-party-07082008 +created_at: 2008-07-08 +tags: + - Blog +--- + + \ No newline at end of file diff --git a/content/posts/2008-07-09-how-to-digg-proof-your-wordpress-blog.md b/content/posts/2008-07-09-how-to-digg-proof-your-wordpress-blog.md new file mode 100644 index 0000000..644e9fd --- /dev/null +++ b/content/posts/2008-07-09-how-to-digg-proof-your-wordpress-blog.md @@ -0,0 +1,94 @@ +--- +title: "How to digg-proof your WordPress blog" +kind: article +slug: how-to-digg-proof-your-wordpress-blog +created_at: 2008-07-09 +tags: + - General + - Wordpress + - performance + - digg + - caching +--- + +Every blogger hopes to be able to say some day to his friends: "I got Dugg!". + +It means you've written something special that a lot of other people are interested in and it gives you a big ego boost. But there won't be much to enjoy when your server can't handle the extra work. + +In this post I'll try to explain some measures you can take to be ready for when "The Big Digg" arrives at your blog's door step. I'll focus on WordPress in particular here, because that's what I'm using. However, the concepts I'll show you can be applied to any web site. + +There are a few things you can do to survive when you get Dugg. The key to my tips here is that you can implement them at (almost) no cost in advance of getting dugg. + +It's important to understand that once you're blog get's dugg, you don't have time to install a plugin or make any other sort of change. The only thing you can do when you're not prepared is take down your web server (including any other apps than your blog). + +Trust me, you don't want to take down your blog when you got dugg. + +Another obvious constraint is upgrading hardware. You simply can't move to a new server or upgrade your RAM when you're getting dugg. + +The key is to be prepared. Here are some tips that will help you get ready for when the day comes... + +Caching + +WordPress, by default, is most likely not able to survive the Digg-effect. This is mainly because every page served requires WP to query your database a few dozen times and compile it all together in that nice looking template of yours. + +So, the most straight forward measure you can take to improve performance is caching. By caching your blog, you basically create static HTML files that will be served to visitors instead of the generated page mentioned earlier. + +Web servers are very good at serving static files. It's ultra-fast. Also, it reduces the load on your server because you're database doesn't have to be woken on every request and there's no PHP code that needs to be executed. + +Of course, your blog is dynamic (at least if you post new things occasionally). This means that you'll need to update those static HTML files regularly to keep them up-to-date. + +To implement caching in your WordPress blog you don't have to do all that much work, really. All you need is the WP Super Cache plugin. + +The plugin takes care of generating static HTML files for all your blog pages and make sure Apache (which is the most commonly used webserver for WordPress) serves these pages *very fast*. + +Compression + +Another measure you can take, in combination with caching, is compressing the static HTML files. Because compressing is a one-time action when the page gets cached, you get a very small file that can be served by your web server even faster with minimal bandwidth usage. + +As you already suspect, WP Super Cache has this feature included. Nice ;-) + +As an added bonus, WP Super Cache will not perform caching for logged in users, users who've left a comment or users who've accessed password protected pages. This means that if a user leaves a comment, he will not see the 'static' cached file (which does not include the comment yet), but the generated version of your blog with the comment. + +Hardware and platform + +Another, very obvious, factor to take into account is your hardware and platform. + +If you're on a shared hosting account you won't survive a Digg. Your provider will shut down your site quite quickly. They do this because there are quite a few users (think hundreds) on the same server as your blog. When dugg, your site will take up a very large portion of the available server capacity, leaving all other users with an usable blog as well. + +My advice is, if you're serious about your blog, to host it on a VPS or dedicated server. These forms of hosting guarantee you the resources you paid for and will only cause trouble to your own web sites. + +For most people a dedicated server is not an option, simply because it's too expensive for running a sole blog. + +A VPS (Virtual Private Server) can be a good compromise between shared hosting and a dedicated server. You are guaranteed a certain amount of memory, disk space and processing power and your VPS won't interfere with other VPS'es on the same server (unless your hosting provider uses crappy VPS software. Always look for Xen VPS hosting). + +The most important factor in choosing a VPS is the available, or guaranteed, memory you get. You can get 'slices' ranging from 128Mb up to 4Gb of RAM. For an average blog I think 256 - 512Mb should be sufficient for running Apache smoothly and handling large amounts of traffic on the static files. + +I can't help to plug SliceHost here. I recently moved Ariejan.net to a slice (a 512Mb one, I also run some Ruby on Rails apps) and I've been very happy with them ever since. So go buy a slice now! I'd make me happy ;-) + +Anyway, it's important to make sure you run your blog on a platform that can handle a peak in traffic and run your blog smoothly all the time. + +What else? Remove all the cruft + +What else can you do to improve performance on your WordPress blog. Well, it would be to remove everything that's not really needed there. Dynamic JavaScripts that retrieve and parse a remote RSS Feed with every request are a classic example of cruft. + +There are a lot of nice plugins available for WordPress and there are even more 'widgets', 'badges' and 'buttons' you can put on your blog to show off. But every one of these will slow down the loading off your site. Especially the ones that come in the form of WordPress plugins. + +Almost every plugin that adds stuff to your sidebar will include its own stylesheets, javascript files and images that need to be downloaded from your server. Even with a cached, static HTML file these need to be downloaded. + +The point is, add a few of these nice plugins, and you're web server has to handle up to 15x the amount of files per page view. + +My advise is to keep plugins and usage of javascript, stylesheet and most importantly images to an absolute minimum. I don't want to encourage you to write a plain text file as a blog, but keep things functional. + +Do you have any other tips? + +Sure, lots. + +Besides keeping your site up-and-running while getting dugg, it's important to keep records. I personally like Goole Analytics a lot, because it gives a lot of information I haven't seen from any other log or traffic analyzer. By gathering these statistics you can analyze later what page(s) on your blog got dugg, where all those people came from and what else they did on your site. + +Another tip is: make backups! This may sound as a stupid tip to most, but you won't believe how many people run a blog without ever making a backup. This hasn't much to do with getting dugg, but when terror strikes, you'll be happy to have a spare copy of all your posts and comments. + +Famous last words... + +Well, to test out all my tips, please digg this story to see if we can get Ariejan.net dugg for once ;-) + +Thank you for reading. Cheers. \ No newline at end of file diff --git a/content/posts/2008-07-10-ariejannet-link-party-07102008.md b/content/posts/2008-07-10-ariejannet-link-party-07102008.md new file mode 100644 index 0000000..aa585c4 --- /dev/null +++ b/content/posts/2008-07-10-ariejannet-link-party-07102008.md @@ -0,0 +1,11 @@ +--- +title: "Ariejan.net Link Party 07/10/2008" +kind: article +slug: ariejannet-link-party-07102008 +created_at: 2008-07-10 +tags: + - Blog + - Links +--- + + \ No newline at end of file diff --git a/content/posts/2008-07-11-photography-heaven.md b/content/posts/2008-07-11-photography-heaven.md new file mode 100644 index 0000000..6246d81 --- /dev/null +++ b/content/posts/2008-07-11-photography-heaven.md @@ -0,0 +1,23 @@ +--- +title: "Photography Heaven" +kind: article +slug: photography-heaven +created_at: 2008-07-11 +tags: + - General + - Blogroll + - Photography + - Flickr +--- + +Photography HeavenFor those who didn't know yet, I'm on Flickr - and I just reorganized my entire photo collection. + +Back in the day when I signed up with Flickr, my idea was to store all my photo's there and be done with it. Now, I realize that I only want to upload the good stuff, and keep the rest for myself. + +Please, hop by at http://flickr.com/photos/ariejan. Feel free to add me to your contacts or to leave a comment. + +For those interested, I'm currently shooting with a Canon EOS 400D Digital in combination with the EF-S 18-55mm 1:3.5-5.6 II kit lens, an old and battered EF 75-300mm 1:4-5.6 and very new and shiny EF 50mm 1:1.8 II Prime lens. + +I am looking to buy an EF 17-40mm L or EF 16-35mm L lens, so if you have one that's just sitting there, please let me know. + +That's all. Hope to see from you on Flicrk! \ No newline at end of file diff --git a/content/posts/2008-07-17-ariejannet-link-party-07172008.md b/content/posts/2008-07-17-ariejannet-link-party-07172008.md new file mode 100644 index 0000000..a0c37e0 --- /dev/null +++ b/content/posts/2008-07-17-ariejannet-link-party-07172008.md @@ -0,0 +1,10 @@ +--- +title: "Ariejan.net Link Party 07/17/2008" +kind: article +slug: ariejannet-link-party-07172008 +created_at: 2008-07-17 +tags: + - Blog +--- + + \ No newline at end of file diff --git a/content/posts/2008-07-21-ariejannet-link-party-07212008.md b/content/posts/2008-07-21-ariejannet-link-party-07212008.md new file mode 100644 index 0000000..22417a6 --- /dev/null +++ b/content/posts/2008-07-21-ariejannet-link-party-07212008.md @@ -0,0 +1,10 @@ +--- +title: "Ariejan.net Link Party 07/21/2008" +kind: article +slug: ariejannet-link-party-07212008 +created_at: 2008-07-21 +tags: + - Blog +--- + + \ No newline at end of file diff --git a/content/posts/2008-08-12-ruby-on-rails-uuid-as-your-activerecord-primary-key.md b/content/posts/2008-08-12-ruby-on-rails-uuid-as-your-activerecord-primary-key.md new file mode 100644 index 0000000..4a93e30 --- /dev/null +++ b/content/posts/2008-08-12-ruby-on-rails-uuid-as-your-activerecord-primary-key.md @@ -0,0 +1,56 @@ +--- +title: "Ruby on Rails: UUID as your ActiveRecord primary key" +kind: article +slug: ruby-on-rails-uuid-as-your-activerecord-primary-key +created_at: 2008-08-12 +tags: + - General +--- + +Sometimes, using the good old 'auto increment' from your database just isn't good enough. If you really require that all your objects have unique ID, even across systems and different databases there's only one way go: UUID or Universally Unique IDentifier. + +A UUID is generated in such a way that every generated UUID in the world is unique. For example: 12f186e6-687e-11ad-843e-001b632783f1. This string is randomly generated based on several factors that guarantee it's uniqueness. + +Anyway, you want to replace the default integer-based primary keys in your model with a UUID. This is quite easy, but there are some caveats. + +First off, you should have a column in your database table that holds the UUID. You may be tempted to just change the column definition for id from integer to string and be done with it. But this won't work as expected. For your development, and maybe even your production system, this may work fine, but you might be in for some unexpected surprises. + +The best example of such a surprise is RSpec. RSpec uses 'rake db:schema:dump' to create a sql dump to quickly load the database with. However, the 'schema:dump' does not look at the id column in your database, but instead adds the default primary key definition from the ActiveRecord adapter. + +The solution is to disable the id column and create a primary key column named uuid instead. + +
      create_table :posts, :id => false do |t|
      +  t.string :uuid, :limit => 36, :primary => true
      +end
      + +In your Post model you should then set the name of this new primary key column. + +
      class Post < ActiveRecord::Base
      +  set_primary_key "uuid"
      +end
      + +The next step is to create the UUID itself. We'll have to do this the Rails app, because most databases don't support UUID out of the box. + +First install the uuidtools gem + +
      sudo gem install uuidtools
      + +Create a file like lib/uuid_helper.rb and add the following content. + +
      require 'rubygems'
      +require 'uuidtools'
      +
      +module UUIDHelper
      +  def before_create()
      +    self.uuid = UUID.timestamp_create().to_s
      +  end
      +end
      + +Then, include this module in all UUID-enabled models, like Post in this example. + +
      class Post < ActiveRecord::Base
      +  set_primary_key "uuid"
      +  include UUIDHelper
      +end
      + +Now, when you save a new Post object, the uuid field is automatically filled with a Universally Unique Identifier. What else could you wish for? \ No newline at end of file diff --git a/content/posts/2008-08-14-useless-ruby-gems-for-your-pleasure.md b/content/posts/2008-08-14-useless-ruby-gems-for-your-pleasure.md new file mode 100644 index 0000000..fba811d --- /dev/null +++ b/content/posts/2008-08-14-useless-ruby-gems-for-your-pleasure.md @@ -0,0 +1,65 @@ +--- +title: "Useless Ruby Gems for your pleasure" +kind: article +slug: useless-ruby-gems-for-your-pleasure +created_at: 2008-08-14 +tags: + - Blog + - Ruby on Rails +--- + +The past few days I'v taken some time to find out how to create a Ruby Gem. This has been on my to-do list for quite a while, but now I'm able to tick it off. + +Well, what did I make? + +The first Gem can also be used as a Ruby on Rails plugin and is called ActsAsGold. If you've ever played World of Warcraft, you'll know how the money system works. You have Copper. A 100 Copper is worth 1 Silver. And 100 Silver is worth 1 Gold. + +The ActsAsGold Gem allows you to extend your ActiveRecord model with this money system. All you need on your model is an attribute that stores a single integer value. + +Let me give you a small tour. + +
      class Player < ActiveRecord::Base
      +  acts_as_gold :column => :money
      +end
      +
      +# This will be store like @player.money => 2003652
      +@player.gold => 200
      +@player.silver => 36
      +@player.copper => 52
      +
      +# You can also easily earn or spend money
      +@player.earn(10.gold + 75.silver)
      +@player.spend(1.gold + 10.silver + 95.copper)
      + +Read more about the Gem, or install the gem right now: + +
      sudo gem install ariejan-acts_as_gold --source http://gems.github.com
      + +The other gem is WarcraftArmory, which is still in early development, so new stuff can and will be added in the future. + +WA (WarcraftArmory) allows you to easily retrieve character information from the World of Warcraft Armory. Currently it can retrieve: + +
        +
      • Name of the character
      • +
      • Name of the characters guild
      • +
      • Level
      • +
      • Race
      • +
      • Class
      • +
      + +It works for both EU and US warcraft servers. + +
      require 'warcraft_armory'
      +
      +character = WarcraftArmory.find(:eu, 'Aszune', 'Nosius')
      +
      +character.race => "Human"
      +character.level => 15
      + +Again, simply install the plugin and use it like any other gem or read the README first. + +
      sudo gem install ariejan-warcraft_armory --source http://gems.github.com
      + +Of course, these gems are released under the MIT license. The code is on Gitub (acts_as_gold, warcraft_armory) and patches with fixes and new features are gladly accepted. + +Please let me know if you find these gems useful or if you use them in one of your projects. \ No newline at end of file diff --git a/content/posts/2008-08-17-activerecord-read-only-models.md b/content/posts/2008-08-17-activerecord-read-only-models.md new file mode 100644 index 0000000..ce11b78 --- /dev/null +++ b/content/posts/2008-08-17-activerecord-read-only-models.md @@ -0,0 +1,47 @@ +--- +title: "ActiveRecord Read Only Model" +kind: article +slug: activerecord-read-only-models +created_at: 2008-08-17 +tags: + - General + - Blog + - Ruby + - Rails + - ror + - activerecord +--- + +ActiveRecord is great in providing CRUD for your data models. In some cases, however, it's necessary to prevent write access to these models. The data may be provided by an external source and should only be used as a reference in your application, for example. + +I'm going to show you how you can easily mark a Model as read only all the time. In this example I have a Item model like this: + +
      class Item < ActiveRecord::Base
      +end
      + +ActiveRecord::Base provides two methods that may be of interest here: + +
      def readonly!
      +  @readonly = true
      +end
      +
      +def readonly?
      +  defined?(@readonly) && @readonly == true
      +end
      + +The first method sets the record to read only. This is great, but we don't want to set the read only property every time we load a model. The second, readonly?, return true if the object is read only or false if it isn't. + +So, if we return true on the readonly? method, our object is marked as read only. Great! + +
      class Item < ActiveRecord::Base
      +  def readonly?
      +    true
      +  end
      +end
      + +That is all! All Item objects are now marked as read only all the time. If you try to write to the model, you'll receive an error. + +
      item = Item.find(:first)
      +item.update_attributes(:name => 'Some item name')
      +=> ActiveRecord::RecordReadOnly
      + diff --git a/content/posts/2008-08-17-skinny-controllers-and-overweight-models.md b/content/posts/2008-08-17-skinny-controllers-and-overweight-models.md new file mode 100644 index 0000000..65c9b57 --- /dev/null +++ b/content/posts/2008-08-17-skinny-controllers-and-overweight-models.md @@ -0,0 +1,60 @@ +--- +title: "Skinny Controllers and Overweight Models" +kind: article +slug: skinny-controllers-and-overweight-models +created_at: 2008-08-17 +tags: + - Blog + - Ruby + - Rails + - controllers + - models +--- +All Rails developers know the slogan "Skinny Controllers, Fat Models" and I heartily agree with it. Every conference you go to, you hear it. But there's a problem! My Fat models got overweight! + +What happened? By stuffing all applications logic in the Models, they become fat, very fat. Although this is supposed to be a good thing, I don't like it. My models get so fat that it takes me forever to scroll through it and find the method I'm working on. There must be a better way! + +Well, yes there is: create modules! Normally you'd write a module to reuse your code in different places, but there's no rule that says you may not use a module only once. + +So, I package all related code (e.g. Authentication, state management, managing associated objects, etc) into different modules and place them in the /lib directory. Let's say you have a a bunch of methods to handle keep a counter on your User model + + :::ruby + Class User < ActiveRecord::Base + attr_accessor :counter + + def up + counter += 1 + end + + def down + counter -= 1 + end + + def reset + counter = 0 + end + end + +You could create a new file lib/counter.rb and include that module in your User model. + + :::ruby + Class User < ActiveRecord::Base + attr_accessor :counter + include Counter + end + + module Counter + def up + counter += 1 + end + + def down + counter -= 1 + end + + def reset + counter = 0 + end + end + +As you can see, this keeps your fat User model clean and makes it easier for you to find code that applies to a certain function. \ No newline at end of file diff --git a/content/posts/2008-09-01-leaving-for-railsconf-europe-2008.md b/content/posts/2008-09-01-leaving-for-railsconf-europe-2008.md new file mode 100644 index 0000000..3db0fe0 --- /dev/null +++ b/content/posts/2008-09-01-leaving-for-railsconf-europe-2008.md @@ -0,0 +1,17 @@ +--- +title: "Leaving for RailsConf Europe 2008" +kind: article +slug: leaving-for-railsconf-europe-2008 +created_at: 2008-09-01 +tags: + - General + - railsconfeurope2008 +--- + +Today a delegation of Kabisa is leaving for RailsConf Europe 2008! We're going by train this year and we hope to arrive this afternoon around 5. + +I'll keep regular updates here on Ariejan.net about RailsConf and all the things I've seen and done. + +If you're going to visit RailsConf Europe yourself, feel free to seek us out and have a chat! + +See you at RailsConf! \ No newline at end of file diff --git a/content/posts/2008-09-02-jruby-with-nick-sieger.md b/content/posts/2008-09-02-jruby-with-nick-sieger.md new file mode 100644 index 0000000..34e3f6c --- /dev/null +++ b/content/posts/2008-09-02-jruby-with-nick-sieger.md @@ -0,0 +1,28 @@ +--- +title: "JRuby with Nick Sieger" +kind: article +slug: jruby-with-nick-sieger +created_at: 2008-09-02 +tags: + - General + - railsconfeurope2008 + - jruby + - nicksieger +--- + +Welcome to RailsConfA tutorial on JRuby with Nick Sieger holding your hand is just great. This guy knows JRuby inside-out and he has an answer to even the most difficult of questions. + +After a short 15 minute introduction, Nick left us with our Rails app to start it with JRuby on a mongrel. I've been toying around with JRuby a bit before, and this was rather easy. Some people, however, encounter some issues with their apps because of incompatible gems, like OpenSSL or RMagic which are not available for the Java platform. Luckily, there are some nice Java alternatives like JRuby OpenSSL and ImageVooDoo. My app didn't suffer from these issues, but from what I've heard, these alternatives work great. + +Next for the big work: creating a WAR file and deploying it. I've done this before, and it's dead easy. In stall the Warble gem, warble your Rails app and deploy the resulting WAR file. + +Nick also talked about the differences between using the JVM and Unix. The biggest differences are memory management and the creation of new threads/processes. + +There are quite a few things that Rails and a Java app server have in common, like logging and session handling. In his sheets (I'll link to those later), you can see how you can tune your Rails app to make use of these features and improve your apps performance (by not logging and keeping sessions in two different places). + +Continuing on the performance of Glassfish/JRuby, Nick shows several graphs on how well JRuby performs. A lot of attention goes to Rails 2.2, which will be thread-safe and what it means for JRuby. + +JRuby can run with 1 runtime and fill your CPU with threads to handle requests using native Java threads. To handle database connections, Nick Sieger has written a connection pooling component for ActiveRecord (to prevent every thread having it's own connection to your database). An unreleased version of warbler was distributed that will allow you to set max_instances to 1. This will create 1 Rails instance and use threading in Glassfish. Of course, this is only useful with Rails 2.2. + +This was altogether a great tutorial afternoon. Besides the basic warble-your-app-and-deploy-it, Nick provided a lot of interesting facts and tips about deploying to Glassfish. I already was enthusiastic about JRuby, but now I really have to get my hands on a decent server that will run Glassfish comfortably. This means at least at least 1Gb of RAM, so I think I have found at least one drawback of using JRuby. + diff --git a/content/posts/2008-09-02-railsconfeurope-the-first-tutorial.md b/content/posts/2008-09-02-railsconfeurope-the-first-tutorial.md new file mode 100644 index 0000000..55bda21 --- /dev/null +++ b/content/posts/2008-09-02-railsconfeurope-the-first-tutorial.md @@ -0,0 +1,21 @@ +--- +title: "RailsConfEurope: The first tutorial" +kind: article +slug: railsconfeurope-the-first-tutorial +created_at: 2008-09-02 +tags: + - General + - railsconfeurope2008 +--- + +Today, RailsConfEurope 2008 started! Well, at least if you signed up for Tutorial Day. + +The first tutorial I'm attending is about Hacking Rails Internals. At first I thought about this as a great way to enhance my own apps and be able to easily integrate other apps like Radiant or Mephisto. However, the more I saw of the demo (it really is more a demo than a tutorial really), the more I disliked hacking Rails Internals. + +Basically, you hook your own application code directly into the Rails code to make it jump through hoops. But, this comes at a big price. First of all, your app code is very dependent on a specific version of Rails, so upgrading Rails will most likely break your app and cost a lot of time (and money) to upgrade. + +Next, you *are* overriding internal Rails code. You just don't know if any other code (either yours, Rails or a plugin) depends on the behaviour of that code. So you should probably expect strange stack traces. + +So, although hacking Rails to include your own or others code directly is great, it has some major drawbacks you should consider. To quote the presenter "It's brutish, but it's how to do it". + +I'm really looking forward to Nick Sieger and his JRuby tutorial this afternoon. I'm all set with Glassfish installed and my Rails app ready. diff --git a/content/posts/2008-09-03-jruby-with-thomas-enebo.md b/content/posts/2008-09-03-jruby-with-thomas-enebo.md new file mode 100644 index 0000000..7a49998 --- /dev/null +++ b/content/posts/2008-09-03-jruby-with-thomas-enebo.md @@ -0,0 +1,17 @@ +--- +title: "JRuby with Thomas Enebo" +kind: article +slug: jruby-with-thomas-enebo +created_at: 2008-09-03 +tags: + - General + - railsconfeurope2008 +--- + +This morning I attended another JRuby talk, this time with Thomas Enebo. It turned out to be, almost default, Sun JRuby talk. + +There was one interesting difference, though. Normally we are shown how to run Ruby on Java and how to use Java components in our Ruby apps. Thomas took JMonkeyEngine (a java 3d scenegraph/game engine) and showed that he coded a simple game in Ruby, steering clear of the rather complex Java code. + +This is, of course, another great feature of JRuby, you can code a "Java" app, and in certain places actually use Ruby to write cleaner or "easier" code. + +Well worth the time! \ No newline at end of file diff --git a/content/posts/2008-09-03-panel-discussion-with-dhh-and-rails-core-members.md b/content/posts/2008-09-03-panel-discussion-with-dhh-and-rails-core-members.md new file mode 100644 index 0000000..0d3c589 --- /dev/null +++ b/content/posts/2008-09-03-panel-discussion-with-dhh-and-rails-core-members.md @@ -0,0 +1,15 @@ +--- +title: "Panel Discussion with DHH and Rails Core Members" +kind: article +slug: panel-discussion-with-dhh-and-rails-core-members +created_at: 2008-09-03 +tags: + - General + - railsconfeurope2008 +--- + +Yesterday evening we attended a panel discussion with DHH and Rails Core Members Jeremy Kemper and Michael Koziarski. + +DHH elaborated on default choices (like database, templating system and test suite) after being asked if Rails would switch over to RSpec instead of TestUnit. The answer was that Rails offers several defaults, which should suffice for new and basic users, who don't know about all options and just want to get started. More experienced users will generate a taste for different components and Rails should provide for easy integration of these components if needed. + +All in all there wasn't any really new stuff to be heard, but a nice gathering non the less. \ No newline at end of file diff --git a/content/posts/2008-09-28-baseapp-a-quick-start-for-your-rails-app.md b/content/posts/2008-09-28-baseapp-a-quick-start-for-your-rails-app.md new file mode 100644 index 0000000..a59c99e --- /dev/null +++ b/content/posts/2008-09-28-baseapp-a-quick-start-for-your-rails-app.md @@ -0,0 +1,40 @@ +--- +title: "BaseApp: a quick start for your Rails App" +kind: article +slug: baseapp-a-quick-start-for-your-rails-app +created_at: 2008-09-28 +tags: + - General + - Ruby + - Ruby on Rails + - Rails + - BaseApp +--- + +BaseApp is no longer maintained. There is a very good alternative called bort. + +For the impatient: http://github.com/ariejan/baseapp + +Got issues? Feature requests or patches? http://baseapp.lighthouseapp.com/ + +Every Rails developer has at least once developed an application that needed user authentication and some basic UI features like tabs and a sidebar. Ask yourself now: "how often have you installed and extended the restful_authentication plugin?". + +Yes, I have done it quite a few times and everytime I find myself writing the same code over and over again. User login, password reset, 'forgot password' functionality. I've build the same basic UI over and over again. Added administrator users and roles. + +Are you tired of doing the same old things over and over again? I was! So, I created BaseApp. + +BaseApp is a Ruby on Rails application which contains a lot of code you want in your project by default. To give you an idea of what is does out of the box: + +
        +
      • User Authentication including password recovery, account activation and account suspensio.
      • +
      • Admin interface where the admin user can easily manage users and tweak app settings
      • +
      • Default CSS-based UI with tabs and a sidebar. Very acceptable by default and easy to customise.
      • +
      + +BaseApp is currently based on Rails 2.1.1. And although it's a pretty complete package and ready to be used for your next project, it still needs a bit of work. Check out the README for features that should be in BaseApp. + +Of course, BaseApp is open source so fork a copy at the GitHub and send me those patches (of pull requests)! + +There are tons of feaures that can be included into BaseApp, so the next big thing is to include some sort of configuration that allows you to disable/enable certain BaseApp features. + +So, go right ahead! Use it! Fork it! Send me those pull requests! \ No newline at end of file diff --git a/content/posts/2008-11-05-rspecing-with-timenow.md b/content/posts/2008-11-05-rspecing-with-timenow.md new file mode 100644 index 0000000..c03383d --- /dev/null +++ b/content/posts/2008-11-05-rspecing-with-timenow.md @@ -0,0 +1,39 @@ +--- +title: "RSpec'ing with Time.now" +kind: article +slug: rspecing-with-timenow +created_at: 2008-11-05 +tags: + - General + - Ruby + - Rails + - rspec + - Test + - time +--- +I'm currently writing some RSpec tests that use Time.now. + +I want my model to calculate a duration and store the future time in the database. I've already specced the calculation of the duration, but I also want to spec that everything gets saved correctly. Here's my first spec: + + :::ruby + it "should do stuff" do + m = Model.create() + m.expires_at.should eql(Time.now + some_value) + end + +This fails. + +It fails because Time.now is quite accurate and some milliseconds have passed between the two calls. + +So how do you test this kind of behaviour? I was not going to let this one beat me. Get out your gloves, because we're going to start stubbing! + +What you need to do is stub out Time#now to return a constant value within this test. This way, both calls will use the same Time.now value and thus yield the same result. This in turn makes your test pass (if the saving goes well, of course). + + :::ruby + it "should do stuff" do + @time_now = Time.parse("Feb 24 1981") + Time.stub!(:now).and_return(@time_now) + + m = Model.create() + m.expires_at.should eql(Time.now + some_value) + end diff --git a/content/posts/2008-11-14-sql-ordering-with-null-values.md b/content/posts/2008-11-14-sql-ordering-with-null-values.md new file mode 100644 index 0000000..4c97cfb --- /dev/null +++ b/content/posts/2008-11-14-sql-ordering-with-null-values.md @@ -0,0 +1,18 @@ +--- +title: "SQL: Ordering with NULL values" +kind: article +slug: sql-ordering-with-null-values +created_at: 2008-11-14 +tags: + - General + - order + - database + - sql + - null +--- + +First seen at Kabisa Blog: SQL: Ordering with NULL values + +This post tells you how to sort NULL values in a column to the bottom and sort the remaining non-NULL values. + +This is really great in combination with LEFT JOIN queries that may yield NULL values. \ No newline at end of file diff --git a/content/posts/2008-11-27-export-csv-directly-from-mysql.md b/content/posts/2008-11-27-export-csv-directly-from-mysql.md new file mode 100644 index 0000000..8545f19 --- /dev/null +++ b/content/posts/2008-11-27-export-csv-directly-from-mysql.md @@ -0,0 +1,26 @@ +--- +title: "Export CSV directly from MySQL" +kind: article +slug: export-csv-directly-from-mysql +created_at: 2008-11-27 +tags: + - General + - MySQL + - csv + - quick + - trick +--- + +How ofter were you asked by a client for a CSV (or excel) file with data from their app? I get asked that question quite often, so I wanted make the process as easy as possible. And guess what? You can create CSV files directly from MySQL with just one query! + +Let's say you want to export the id, name and email fields from your users table to a CSV file. Here is your code: + +
      SELECT id, name, email INTO OUTFILE '/tmp/result.csv'
      +FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
      +ESCAPED BY ‘\\’
      +LINES TERMINATED BY '\n'
      +FROM users WHERE 1
      + +Well, if you know MySQL, you'll know how to customize this query to spit out the the right data. Your csv file can be found in /tmp/result.csv + +Make sure your MySQL server has write permissions to the location where you want to store your results file. \ No newline at end of file diff --git a/content/posts/2008-12-03-google-friendconnect-now-on-ariejannet.md b/content/posts/2008-12-03-google-friendconnect-now-on-ariejannet.md new file mode 100644 index 0000000..0cef155 --- /dev/null +++ b/content/posts/2008-12-03-google-friendconnect-now-on-ariejannet.md @@ -0,0 +1,22 @@ +--- +title: "Google FriendConnect now on Ariejan.net" +kind: article +slug: google-friendconnect-now-on-ariejannet +created_at: 2008-12-03 +tags: + - General +--- + +Well, I just got invited for Google FriendConnect! Let me tell you a bit about FriendConnect. + +With FriendConnect you can easily join Ariejan.net. See of it as a pool of Ariejan.net visitors or fans if you like. + +Once you've joined up (with your google account, for example) you'll be able to interact more closely with Ariejan.net. In the case of Ariejan.net you can share your thoughts with other Ariejan.net visitors. Just vent your frustration about Linux or let others know what Rails project you're working on. + +Another benefit for you is that you can see what sites other people have signed up for as well. This way you'll be able to get a list of sites that Ariejan.net visitors like as well. You might compare it with Amazon's "other people also bought.." feature. + +The big benefit for me is that's it's nice to see the list of fans/visitors growing. After all, that's what I'm keeping a blog for after all. + +In the future I'll be able to add other widgets to Ariejan.net. This allows for other features to appear on Ariejan.net and a better over-all user experience. + +You may want to read more about Google FriendConnect and OpenSocial. \ No newline at end of file diff --git a/content/posts/2008-12-27-twitterlicious.md b/content/posts/2008-12-27-twitterlicious.md new file mode 100644 index 0000000..56ebd8a --- /dev/null +++ b/content/posts/2008-12-27-twitterlicious.md @@ -0,0 +1,15 @@ +--- +title: "Twitterlicious!" +kind: article +slug: twitterlicious +created_at: 2008-12-27 +tags: + - General + - twitter +--- + +A bit late, but "Merry Christmas" everyone! + +I'm on Twitter lately, so feel free to check and follow. It's a also a nice way to keep up-to-date about new articles on Ariejan.net. + +Hey, that's all for now. And if you don't come back here this year: "Happy New Year!" - Cheers! \ No newline at end of file diff --git a/content/posts/2009-01-04-how-to-start-a-rails-edge-app-the-easy-way.md b/content/posts/2009-01-04-how-to-start-a-rails-edge-app-the-easy-way.md new file mode 100644 index 0000000..a9247a2 --- /dev/null +++ b/content/posts/2009-01-04-how-to-start-a-rails-edge-app-the-easy-way.md @@ -0,0 +1,67 @@ +--- +title: "How To Start A Rails Edge App The Easy Way" +kind: article +slug: how-to-start-a-rails-edge-app-the-easy-way +created_at: 2009-01-04 +tags: + - General + - RubyOnRails + - Rails + - git + - ror + - edge + - submodule +--- + +There's a lot of cool stuff pooring in about what's new in Rails Edge (which will become Rails 2.3 and/or Rails 3). + +Most likely you can't wait to get started with these new features, especially when you're about to start a new project, which doesn't have to be stable yet, but will be by the time 2.3/3.0 come out. This post shows you the way to create a new Rails app based on the most current Rails code, also called Edge Rails. + +Let's go... + +
      +mkdir -p myapp/vendor
      +cd myapp
      +git init
      +git submodule add git://github.com/rails/rails.git vendor/rails
      +git commit -m "Frozen Rails Edge as submodule"
      +ruby vendor/rails/railties/bin/rails .
      +# Add generated files to git, and code on...
      + +First, you create a new directory for your app, including the vendor directory. Easy, right? + +Next, you initialize a Git repository for your empty project. We'll be using Git to track the remote Rails Edge code. Stay with me. + +By adding a Git submodule we tell git to clone the code from git://github.com/rails/rails.git into the vendor/rails directory. Nice! If you check the current git status with git status you see git has already staged two files for you, .gitmodules and vendor/rails. Commit them now to attach the submodule to your local git repository. + +Git will not automatically update your submodule, you'll have to do that by hand. I'll show you this in a minute. + +With vendor/rails containing Rails Edge, you can now generate your Rails Edge application. In you project directory (myapp/), you call ruby vendor/rails/railties/bin/rails .. This will generate a new Rails Edge application in the current directory. + +Now it's up to you to create a fitting .gitignore file and commit the files to your repository. + +That's all, you now have a new Rails Edge application. Try ruby script/server to see it all in action. Enjoy! + +Cloning your project + +At some point you'll push your myapp project to a remote git server. When you clone a fresh copy, you'll have to initialize the git submodules. This is quite easy: + +
      +git submodule init
      +git submodule update
      + +Updating Rails Edge + +As I said earlier, Git will not keep your submodules up-to-date for you, but will stick with the revision you added. To keep track of Rails Edge's progress, you'll need to update the submodule. This is done like this: + +
      cd myapp/vendor/rails
      +git remote update
      +git merge origin/master
      + +This will update your Rails Edge code. Make a commit, stating you updated the code! + +After updating Rails Edge, you may want to update your rails application (like javascript files, config files etc). + +
      rake rails:update
      + +Good luck! And happy coding! \ No newline at end of file diff --git a/content/posts/2009-01-30-pagerank-3-19k-hitsmonth.md b/content/posts/2009-01-30-pagerank-3-19k-hitsmonth.md new file mode 100644 index 0000000..cd760cc --- /dev/null +++ b/content/posts/2009-01-30-pagerank-3-19k-hitsmonth.md @@ -0,0 +1,21 @@ +--- +title: "Pagerank 3, 19k Hits/Month" +kind: article +slug: pagerank-3-19k-hitsmonth +created_at: 2009-01-30 +tags: + - General + - Ariejan.net + - Blog + - pagerank + - page views + - stats +--- + +I just found out Ariejan.net has a PageRank™ of 3! If you don't know what PageRank™ means, let Google explain: + +
      PageRank relies on the uniquely democratic nature of the web by using its vast link structure as an indicator of an individual page's value. In essence, Google interprets a link from page A to page B as a vote, by page A, for page B. But, Google looks at more than the sheer volume of votes, or links a page receives; it also analyzes the page that casts the vote. Votes cast by pages that are themselves "important" weigh more heavily and help to make other pages "important". - source
      + +To make a long story short, Ariejan.net is getting a lot of attention from around the web. Personally I think of it as a sign of appreciation for my articles. I'm also quite impressed with the number of page views my site is getting. Page views average out to 19k a month over 2008. Thanks all! + +I found out about all this because I updated my listing at BuySellAds.com. I suggest you check them out some time. \ No newline at end of file diff --git a/content/posts/2009-02-07-warcraft-armory-010-released.md b/content/posts/2009-02-07-warcraft-armory-010-released.md new file mode 100644 index 0000000..00e7c71 --- /dev/null +++ b/content/posts/2009-02-07-warcraft-armory-010-released.md @@ -0,0 +1,28 @@ +--- +title: "warcraft-armory 0.1.0 Released" +kind: article +slug: warcraft-armory-010-released +created_at: 2009-02-07 +tags: + - General + - Ruby + - warcraft-armory + - rubygems +--- + +Yay! warcraft-armory version 0.1.0 has been released! + +The warcraft-armory gem allows your application to easily access information from the World of Warcraft Armory site. + +This is an early version that allows the retrieval of character information from EU and US armories. But, more is in the making! + +Feel free to check-out the code, read the docs or just install the gem: + +
      sudo gem install warcraft-armory
      + +
      require 'warcraft-armory'
      +adries = WarcraftArmory::Character.find(:eu, :aszune, :adries)
      +adries.description
      +# => "Level 48 Human Warrior"
      + +It's my first gem, so useful comments are appreciated! Keep an eye out for updates! \ No newline at end of file diff --git a/content/posts/2009-03-31-may-14th-rubyfest.md b/content/posts/2009-03-31-may-14th-rubyfest.md new file mode 100644 index 0000000..4948199 --- /dev/null +++ b/content/posts/2009-03-31-may-14th-rubyfest.md @@ -0,0 +1,17 @@ +--- +title: "May 14th: RubyFest!" +kind: article +slug: may-14th-rubyfest +created_at: 2009-03-31 +tags: + - General + - rubyfest +--- + +Well, the time has come to announce RubyFest! RubyFest is an official meetup of Ruby developers and enthusiast which will be held on May 14th in Eindhoven, the Netherlands. + +Besides organising RubyFest, I'll also be one of two speakers at RubyFest. I'll be giving a talk about using git with your (open source) project effectively. The other speaker is yet to be announced, and we're still accepting proposals! + +So, head over to RubyFest and register today, because we only have limited seating! + +Follow me or RubyFest on Twitter to get the latest and greatest quickly. \ No newline at end of file diff --git a/content/posts/2009-04-05-macbook-pro-black-screen-of-death-or-is-it-just-faking.md b/content/posts/2009-04-05-macbook-pro-black-screen-of-death-or-is-it-just-faking.md new file mode 100644 index 0000000..de89fc7 --- /dev/null +++ b/content/posts/2009-04-05-macbook-pro-black-screen-of-death-or-is-it-just-faking.md @@ -0,0 +1,43 @@ +--- +title: "MacBook Pro: Black Screen of Death (or is it just faking?)" +kind: article +slug: macbook-pro-black-screen-of-death-or-is-it-just-faking +created_at: 2009-04-05 +tags: + - General + - Apple + - mac + - mbp + - macbook pro + - death + - black +--- + +Disclaimer: If you are reading this, chances are there is a hardware problem with your Mac. In my case it was a faulty logic board, which had to be replaced. + +Use this guide to get your Mac up and running again and create a full backup of you system as soon as possible. If the problem repeats itself, I recommend you take your Mac back to Apple for a check-up. + +Today I was happily working on some Java code, when I decided to relocate to a sunny spot in the backyard. I closed my MacBook Pro, walked outside and opened my MBP again to continue work: a black screen! + +The screen of my Mac stayed black, although it did not indicate to be in a sleeping state. WTF? I restarted the Mac, removed the battery, reset PRAM/NVRAM and the PMU, but nothing worked! ARGH! Then, I found a solution that worked. + +If you find yourself in a similar situation, do the following: + +
        +
      1. Shutdown your Mac entirely by holding the power button for 5 seconds.
      2. +
      3. Hook up your Mac to another one with a FireWire cable. (You have one, right?)
      4. +
      5. Hold the T key on your mac's internal keyboard and press the power button. When your MBP's disk will shows up as an external firewire disk on your HelperMac, release the T button.
      6. +
      7. Open finder and go to the /private/var/vm folder on your MacBook's hard drive. You may need to use Command-Shift-G in Finder, because this folder is hidden by default. Once in /private/var/vm remove the sleepfile you find there.
      8. +
      9. Next, go to /System/Library on you MBP's disk and remove Extensions.mkext.
      10. +
      11. Now, eject your MBP drive and shutdown your MBP by hitting its power button.
      12. +
      + +After these steps, you should be able to boot your MBP as normal again. + +Although I'm not sure of the problem at hand, it appears that the sleepfile gets corrupted in some way. + +Oh, didn't have that FireWire cable ready? Here's the one I used. + +Update: There are some common questions in the comments I'd like to answer here. You cannot use an USB cable to connect your Mac to another one, only Firewire 400 and 800 are supported. + +In my case, the MacBook Pro was returned to a Apple Dealer and repaired under warranty, because Apple apparently has acknowledged the problems with the video card on these machines. Your mileage may vary with your local Apple store. \ No newline at end of file diff --git a/content/posts/2009-04-19-how-to-create-a-dsa-openssl-certificate.md b/content/posts/2009-04-19-how-to-create-a-dsa-openssl-certificate.md new file mode 100644 index 0000000..4688976 --- /dev/null +++ b/content/posts/2009-04-19-how-to-create-a-dsa-openssl-certificate.md @@ -0,0 +1,22 @@ +--- +title: "How to create a DSA OpenSSL certificate" +kind: article +slug: how-to-create-a-dsa-openssl-certificate +created_at: 2009-04-19 +tags: + - General + - openssl + - dsa + - key + - public +--- + +I just needed an OpenSSL DSA public key. This is not really difficult, you just need to know the right commands. On my Mac I ran the following commands to obtain both private dsa_priv.pem and public dsa_pub.pem keys. + +
      +openssl dsaparam 1024 < /dev/random > dsaparam.pem
      +openssl gendsa dsaparam.pem -out dsa_priv.pem
      +openssl dsa -in dsa_priv.pem -pubout -out dsa_pub.pem
      +
      + +Needless to say, keep your private key in secure location and make sure you have a backup of it! \ No newline at end of file diff --git a/content/posts/2009-04-23-compacting-a-sqlite3-db-file.md b/content/posts/2009-04-23-compacting-a-sqlite3-db-file.md new file mode 100644 index 0000000..09051a9 --- /dev/null +++ b/content/posts/2009-04-23-compacting-a-sqlite3-db-file.md @@ -0,0 +1,21 @@ +--- +title: "Compacting a SQLite3 DB file" +kind: article +slug: compacting-a-sqlite3-db-file +created_at: 2009-04-23 +tags: + - General +--- + +If you have a lot of mutations in your SQLite3 database the file size of the db file will increase a lot over time. + +This can be annoying, so you'll need to clean up old indices and other cruft that's making your db heavy. + +The solution is über-easy: + +
      $ sqlite3 mystuff.db
      +SQLite version 3.6.6.2
      +sqlite> VACUUM;
      +sqlite> .quit
      + +Note that you need to use capitals here. This will clean your db file and reduce its file size dramatically (depending on the amount of cruft you have, of course). \ No newline at end of file diff --git a/content/posts/2009-04-28-available-for-iphone-development.md b/content/posts/2009-04-28-available-for-iphone-development.md new file mode 100644 index 0000000..7646d4d --- /dev/null +++ b/content/posts/2009-04-28-available-for-iphone-development.md @@ -0,0 +1,22 @@ +--- +title: "Available for iPhone Development" +kind: article +slug: available-for-iphone-development +created_at: 2009-04-28 +tags: + - General + - Apple + - iPod + - development + - mac + - iphone + - sdk + - hire + - available +--- + +Just to put it out there: I'm available for iPhone development (preferably in the Netherlands). + +If you're interested in having an iPhone app developed, feel free to contact me to discuss your options. + +I'm developing iPhone apps a an employee at Kabisa. \ No newline at end of file diff --git a/content/posts/2009-05-06-second-rubyfest-speaker-geoffrey-grosenbach.md b/content/posts/2009-05-06-second-rubyfest-speaker-geoffrey-grosenbach.md new file mode 100644 index 0000000..39e4b0d --- /dev/null +++ b/content/posts/2009-05-06-second-rubyfest-speaker-geoffrey-grosenbach.md @@ -0,0 +1,14 @@ +--- +title: "Second RubyFest Speaker: Geoffrey Grosenbach" +kind: article +slug: second-rubyfest-speaker-geoffrey-grosenbach +created_at: 2009-05-06 +tags: + - General +--- + +Geoffrey Grosenbach is going to deliver a talk about MacRuby at RubyFest on may 14th! He'll be joining us over a live video feed. Afterward Geoffrey will be available for a short Q&A session. + +More details about Geoffrey's talk are expected shortly. + +If you didn't get your tickets for RubyFest yet, be sure to head over to http://rubyfest.nl and get them right now. We've got limited seating for this first edition of RubyFest. (Oh, drinks are included) \ No newline at end of file diff --git a/content/posts/2009-05-22-speak-louder-i-cant-hear-you-over-the-sound-of-how-awesome-i-am.md b/content/posts/2009-05-22-speak-louder-i-cant-hear-you-over-the-sound-of-how-awesome-i-am.md new file mode 100644 index 0000000..31df4b3 --- /dev/null +++ b/content/posts/2009-05-22-speak-louder-i-cant-hear-you-over-the-sound-of-how-awesome-i-am.md @@ -0,0 +1,14 @@ +--- +title: "Speak louder! I can't hear you over the sound of how awesome I am!" +kind: article +slug: speak-louder-i-cant-hear-you-over-the-sound-of-how-awesome-i-am +created_at: 2009-05-22 +tags: + - General + - twitter + - awesome + - twitshirt + - tshirt +--- + +Get the t-shirt! \ No newline at end of file diff --git a/content/posts/2009-06-03-ruby-gem-imdb.md b/content/posts/2009-06-03-ruby-gem-imdb.md new file mode 100644 index 0000000..664c8ca --- /dev/null +++ b/content/posts/2009-06-03-ruby-gem-imdb.md @@ -0,0 +1,60 @@ +--- +title: "Ruby Gem: IMDB" +kind: article +slug: ruby-gem-imdb +created_at: 2009-06-03 +tags: + - General + - Ruby + - imdb + - gem + - api +--- +I just released version 0.1.0 of my IMDB gem which allows your app to search IMDB for IMDB movie ID's and access most data that's publicly available. + +## Installation + + sudo gem install imdb + +This will also install the dependencies Hpricot and HTTParty. + +## Usage + +In your project, include the gem (and possibly rubygems as well). + + :::ruby + require 'rubygems' + require 'imdb' + + search = Imdb::Search.new('Star Trek') + => # + + puts search.movies[0..3].collect{ |m| [m.id, m.title].join(" - ") }.join("\n") + => 0060028 - "Star Trek" (1966) (TV series) + 0796366 - Star Trek (2009) + 0092455 - "Star Trek: The Next Generation" (1987) (TV series) + 0112178 - "Star Trek: Voyager" (1995) (TV series) + + st = Imdb::Movie.new("0796366") + => # + + st.title + => "Star Trek" + st.year + => 2009 + st.rating + => 8.4 + st.cast_members[0..2].join(", ") + => "Chris Pine, Zachary Quinto, Leonard Nimoy" + +As you can see, both `Imdb::Search` and `Imdb::Movie` are lazy loading, only doing a HTTP request when you actually request data. Also, the remote HTTP data is cached trhough-out the life span of your Imdb::Movie object. + +## Documentation + +Generated RDoc documentation can be found at [http://ariejan.github.com/imdb/][1]. + +[1]: http://ariejan.github.com/imdb/ + +## Issues, feature requests, patches, the works + +Please use https://github.com/ariejan/imdb to supply patches (preferably through a pull-request) and to report issues. \ No newline at end of file diff --git a/content/posts/2009-06-05-install-hpricot-on-ubuntu.md b/content/posts/2009-06-05-install-hpricot-on-ubuntu.md new file mode 100644 index 0000000..c6dbd7a --- /dev/null +++ b/content/posts/2009-06-05-install-hpricot-on-ubuntu.md @@ -0,0 +1,26 @@ +--- +title: "Install Hpricot on Ubuntu" +kind: article +slug: install-hpricot-on-ubuntu +created_at: 2009-06-05 +tags: + - General +--- + +It's quite easy. Make sure you have RubyGems and Ruby installed first, of course. + +The problem: + +
      $ sudo gem install hpricot
      +Building native extensions.  This could take a while...
      +ERROR:  Error installing hpricot:
      +	ERROR: Failed to build gem native extension.
      +
      +/usr/bin/ruby1.8 extconf.rb
      +extconf.rb:1:in `require': no such file to load -- mkmf (LoadError)
      +	from extconf.rb:1
      + +The solution: + +
      sudo apt-get install ruby1.8-dev build-essential
      +sudo gem install hpricot
      \ No newline at end of file diff --git a/content/posts/2009-06-07-activerecord-skipping-callbacks-like-after_save-or-after_update.md b/content/posts/2009-06-07-activerecord-skipping-callbacks-like-after_save-or-after_update.md new file mode 100644 index 0000000..2f5df65 --- /dev/null +++ b/content/posts/2009-06-07-activerecord-skipping-callbacks-like-after_save-or-after_update.md @@ -0,0 +1,47 @@ +--- +title: "ActiveRecord: Skipping callbacks like after_save or after_update" +kind: article +slug: activerecord-skipping-callbacks-like-after_save-or-after_update +created_at: 2009-06-07 +tags: + - General +--- + +Active Records provides callbacks, which is great is you want to perform extra business logic after (or before) saving, creating or destroying an instance of that model. + +However, there are situations where you can easily fall into the trap of creating an infinite loop. + +
      class Beer < ActiveRecord::Base
      +  def after_save
      +    x = some_magic_method(self)
      +    update_attribute(:my_attribute, x)
      +  end
      +end
      + +The above will give you a nice infinite loop (which doesn't scale). It's possible to update your model, without calling the callbacks and without resorting to SQL. + +
      class Beer < ActiveRecord::Base
      +  def after_save
      +    x = some_magic_method(self)
      +    Beer.update_all("my_attribute = #{x}", { :id => self.id })
      +  end
      +end
      + +This is a bit unconventional, but it works nicely. You can use all the following ActiveRecord methods to update your model without calling callbacks: + +
        +
      • decrement
      • +
      • decrement_counter
      • +
      • delete
      • +
      • delete_all
      • +
      • find_by_sql
      • +
      • increment
      • +
      • increment_counter
      • +
      • toggle
      • +
      • update_all
      • +
      • update_counters
      • +
      + +An important warning: These methods don't do all the nice SQL injection protection stuff you're used to. In the example, the value of x will be inserted straight into the SQL. I recommend you only use these methods if you're absolutely sure you've cleaned the values you're inserting. + +Check out the rails documentation on how to use these methods. diff --git a/content/posts/2009-06-07-has_one-find-all-that-have-no-associated-object.md b/content/posts/2009-06-07-has_one-find-all-that-have-no-associated-object.md new file mode 100644 index 0000000..b0f0ef3 --- /dev/null +++ b/content/posts/2009-06-07-has_one-find-all-that-have-no-associated-object.md @@ -0,0 +1,41 @@ +--- +title: "has_one - find all that have no associated object" +kind: article +slug: has_one-find-all-that-have-no-associated-object +created_at: 2009-06-07 +tags: + - General + - Ruby + - Rails + - has_one + - has_many + - belongs_to +--- + +Let me pose a typical Rails situation: + +
      class Person < ActiveRecord::Base
      +  has_one :fancy_hat
      +end
      +
      +class FancyHat < ActiveRecord::Base
      +  belongs_to :person
      +end
      + +Now, how can you get all the people that don't have a fancy hat? + +
      class Person < ActiveRecord::Base
      +  has_one :fancy_hat
      +
      +  named_scope :hatless, :joins => 'LEFT JOIN fancy_hats ON fancy_hats.person_id = people.id', :conditions => 'fancy_hats.person_id IS NULL'
      +end
      + +Now you can find all the hatless people you want. + +FYI: Finding fancy hats that have no one to wear them is a lot easier, because the foreign key is stored in the fancy_hats table. + +
      class FancyHat < ActiveRecord::Base
      +  belongs_to :person
      +
      +  named_scope :wearerless, :conditions => { :person_id => nil }
      +end
      \ No newline at end of file diff --git a/content/posts/2009-06-07-imdb-0-3-0-now-including-console-utility-query-imdb-from-your-console.md b/content/posts/2009-06-07-imdb-0-3-0-now-including-console-utility-query-imdb-from-your-console.md new file mode 100644 index 0000000..29f5991 --- /dev/null +++ b/content/posts/2009-06-07-imdb-0-3-0-now-including-console-utility-query-imdb-from-your-console.md @@ -0,0 +1,68 @@ +--- +title: "IMDB 0.3.0 now including console utility - query IMDB from your console" +kind: article +slug: imdb-0-3-0-now-including-console-utility-query-imdb-from-your-console +created_at: 2009-06-07 +tags: + - General + - imdb + - gem + - console +--- + +With the release of IMDB 0.3.0, a command-line utility is included! + +Why is this awesome for you? Basically, because you can now query IMDB and process the results with any of the great GNU tools available to you like grep. + +Let me show you: + +Search IMDB + +
      $ imdb Star Trek
      +>> Searching for "Star Trek"
      + > 0060028 | Star Trek (1966) (TV series)
      + > 0796366 | Star Trek (2009)
      + > 0092455 | Star Trek: The Next Generation (1987) (TV series)
      + > 0112178 | Star Trek: Voyager (1995) (TV series)
      + > 0106145 | Star Trek: Deep Space Nine (1993) (TV series)
      + > 0117731 | Star Trek: First Contact (1996)
      + > 0084726 | Star Trek: The Wrath of Khan (1982)
      + > 0092007 | Star Trek IV: The Voyage Home (1986)
      + > 0079945 | Star Trek: The Motion Picture (1979)
      + > 0244365 | Enterprise (2001) (TV series)
      + +For clarity, only the ten first search results are shown. I'm thinking of including an option to set the number of returned titles. Let me know what you think about that. + +Getting movie details + +So, let's pick a movie we want to get details about. + +
      $ imdb 0796366
      +>> Fetching movie 0796366
      +
      +Star Trek (2009)
      +========================================================================
      +Rating: 8.4
      +Duration: 127 minutes
      +Directed by: J.J. Abrams
      +Cast: Chris Pine, Zachary Quinto, Leonard Nimoy, Eric Bana, Bruce Greenwood
      +Genre: Action, Adventure, Sci-Fi
      +A chronicle of the early days of James T. Kirk and his fellow USS Enterprise crew members. |
      +========================================================================
      + +Combine with GNU commands + +Or, as mentioned earlier, you can combine it with other console commands. So to get the rating of a movie, do this: + +
      $ imdb 0796366 | grep "^Rating" | cut -d" " -f 2
      +8.4
      + +Installation, source and issues + +I'm still working on perfecting the console output, so if you have any tips, please report it in the form of an issue. + +You can get this as a Ruby Gem with + +
      sudo gem install imdb
      + +The source is also available at http://github.com/ariejan/imdb. \ No newline at end of file diff --git a/content/posts/2009-06-08-best-practice-the-git-development-cycle.md b/content/posts/2009-06-08-best-practice-the-git-development-cycle.md new file mode 100644 index 0000000..2c3fd54 --- /dev/null +++ b/content/posts/2009-06-08-best-practice-the-git-development-cycle.md @@ -0,0 +1,56 @@ +--- +title: "Best Practice - The Git Development Cycle" +kind: article +slug: best-practice-the-git-development-cycle +created_at: 2009-06-08 +tags: + - General + - git + - rebase + - merge + - branch + - svn + - best-practice +--- + +Git is quite an awesome version control system. Why? Because it's lightning fast, even for large projects (among other reasons). + +But, how do you use Git effectively for development on a daily basis? Let me explain to you. + +Branches + +With git you normally have a 'master' branch. This is also the branch you use to sync your code with other repositories. That is also the reason why you should never code in the 'master' branch. Always create a new branch and develop your code there. + +
      $ git checkout -b new_feature
      +# add, commit, repeat
      + +Rebase + +Now, while you are working hard on your new feature, other developers complete theirs and push their changes to the remote master branch. When you're done with your project, you need to first get the most recent version of the project's code. + +
      $ git checkout master
      +$ git pull
      + +Now, to make merging your new feature easy, you should rebase your new_feature_branch. What this does is add all the commits you just pulled in to your new_feature branch. Any conflicts that arise will happen in your new_feature branch as well, leaving your master branch clean and in order. + +
      $ git checkout new_feature
      +$ git rebase master
      + +Merge + +Now, you have resolved all (if any) conflicts with the current code and your new_feature, you can now merge your new_feature into the project's master branch without any problems. + +
      $ git checkout master
      +$ git merge new_feature
      + +This will create a new commit, containing your new_feature. Now is also the time to push your changes to the remote repository. + +
      $ git push origin master
      + +What's next? + +More often than not, you'll encounter conflicts when running rebase. That's okay, but you'll need to know how to approach a situation like that. I'll spend another blog post on that topic later. + +Need Git training? + +I'm currently available to provide on-location Git training. Please contact me for more info. \ No newline at end of file diff --git a/content/posts/2009-06-14-imdb-ruby-gem-0-4-0-now-available-at-rubyforge.md b/content/posts/2009-06-14-imdb-ruby-gem-0-4-0-now-available-at-rubyforge.md new file mode 100644 index 0000000..ec844a4 --- /dev/null +++ b/content/posts/2009-06-14-imdb-ruby-gem-0-4-0-now-available-at-rubyforge.md @@ -0,0 +1,31 @@ +--- +title: "IMDB Ruby Gem 0.4.0 Now available at RubyForge!" +kind: article +slug: imdb-ruby-gem-0-4-0-now-available-at-rubyforge +created_at: 2009-06-14 +tags: + - General + - Ruby + - imdb + - gem +--- + +I just released version 0.4.0 of the IMDB Ruby Gem into the wild. There are only a few minor updates: + +Changes in 0.4.0 + +
      • Updates to the console 'imdb' utility
      • +
          +
        • Show the IMDB ID
        • +
        • Show the full IMDB URL
        • +
        +
      + +Installation or upgrade + +
      $ sudo gem install imdb
      +
      $ sudo gem update imdb
      + +Issues, source or contributions + +You can find the code at github, as well as the issue tracker. Feel free to fork and contribute! \ No newline at end of file diff --git a/content/posts/2009-06-14-speaking-at-rails-underground.md b/content/posts/2009-06-14-speaking-at-rails-underground.md new file mode 100644 index 0000000..5593909 --- /dev/null +++ b/content/posts/2009-06-14-speaking-at-rails-underground.md @@ -0,0 +1,21 @@ +--- +title: "Speaking at Rails Underground" +kind: article +slug: speaking-at-rails-underground +created_at: 2009-06-14 +tags: + - General + - Ruby + - Rails + - git + - rails-underground + - speaking +--- + +Speaking at Rails UndergroundI haven't seen a schedule yet, but I've been told by Mark that I'll be speaking at Rails Underground this year. + +My talk will be on the topic of Git. In about 45 minutes time I'll show you all the basic git features you'll need on a daily basis. Not only that, but I'll also explain how git manages all those commits and branches so you can be on your way to become a git power user. + +Are you attending or speaking at Rails Underground? Please let leave a comment or find me on Twitter (@ariejan). + +Oh, and does anyone have a clue if Mark has the schedule ready yet? ;-) \ No newline at end of file diff --git a/content/posts/2009-08-20-once-and-for-all-rails-migrations-integer-limit-option.md b/content/posts/2009-08-20-once-and-for-all-rails-migrations-integer-limit-option.md new file mode 100644 index 0000000..07161d7 --- /dev/null +++ b/content/posts/2009-08-20-once-and-for-all-rails-migrations-integer-limit-option.md @@ -0,0 +1,63 @@ +--- +title: "Once and for all: Rails migrations integer :limit option" +kind: article +slug: once-and-for-all-rails-migrations-integer-limit-option +created_at: 2009-08-20 +tags: + - MySQL + - Ruby on Rails + - Rails + - sql + - migrations + - integer + - limit +--- + +I literally always have to look up the meaning of :limit in migrations when it comes to integer values. Here's an overview. Now let's memorise it (oh, this works for MySQL, other databases may work differently): + +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :limitNumeric TypeColumn SizeMax value
    1tinyint1 byte127
    2smallint2 bytes32767
    3mediumint3 byte8388607
    nil, 4, 11int(11)4 byte2147483647
    5..8bigint8 byte9223372036854775807
    + +Note: by default MySQL uses signed integers and Rails has no way (that I know of) to change this behaviour. Subsequently, the max. values noted are for signed integers. diff --git a/content/posts/2009-09-01-jinput-mac-os-x-64-bit-natives.md b/content/posts/2009-09-01-jinput-mac-os-x-64-bit-natives.md new file mode 100644 index 0000000..c42fae1 --- /dev/null +++ b/content/posts/2009-09-01-jinput-mac-os-x-64-bit-natives.md @@ -0,0 +1,44 @@ +--- +title: "JInput Mac OS X 64 bit natives" +kind: article +slug: jinput-mac-os-x-64-bit-natives +created_at: 2009-09-01 +tags: + - Java + - 64bit + - lwjgl + - jinput + - slick2d + - jme +--- + +Yesterday I ran into a little problem running Slick 2D on Java 6 64bit on my Mac. It's a MacPro, which has a 64 bit processor and is running Leopard. The problem I encountered was related to the native libraries provided by LWJGL. + +If you're a user of LWJGL, you'll be using JInput as well. Unfortunately, JInput does not currently have any 64 bit native libraries as it is only providing for PPC and i386 (32bit). This is a problem because Mac OS X users are somewhat bound to Java 6 64bit. There is no 32 bit version of Java 6 for Mac OS X. + +
    +$ file libjinput-osx.jnilib
    +libjinput-osx.jnilib: Mach-O universal binary with 2 architectures
    +libjinput-osx.jnilib (for architecture ppc):	Mach-O dynamically linked shared library ppc
    +libjinput-osx.jnilib (for architecture i386):	Mach-O dynamically linked shared library i386
    +
    + +Thus, the default JInput natives provided by LWJGL, and subsequently by projects like Slick 2D and jME are of no use on a system running Java 6 64 bit. Also games and other apps based on those libraries won't run on Java 6 64 bit! + +To solve this, I recompiled the JInput natives from CVS with 64 bit support: + +
    +$ file libjinput-osx.jnilib
    +libjinput-osx.jnilib: Mach-O universal binary with 3 architectures
    +libjinput-osx.jnilib (for architecture ppc):	Mach-O dynamically linked shared library ppc
    +libjinput-osx.jnilib (for architecture i386):	Mach-O dynamically linked shared library i386
    +libjinput-osx.jnilib (for architecture x86_64):	Mach-O 64-bit dynamically linked shared library x86_64
    +
    + +I packaged the Mac OS X native (libjinput-osx.jnilib) and the resulting jinput.jar and jinput-test.jar up and you can download them here: + +jinput-20090901-osx+ppc+i386+x86_64.tar + +Note: I'm not running Linux or Windows at the moment, so I can't provide any recompiled binaries for those platforms. + +Question: now, how do I package this up nicely so I can use it in my Slice 2D based app? \ No newline at end of file diff --git a/content/posts/2009-09-03-rails-mysql-case-sensitive-strings-in-your-database.md b/content/posts/2009-09-03-rails-mysql-case-sensitive-strings-in-your-database.md new file mode 100644 index 0000000..a83a3ee --- /dev/null +++ b/content/posts/2009-09-03-rails-mysql-case-sensitive-strings-in-your-database.md @@ -0,0 +1,21 @@ +--- +title: "Rails + MySQL: Case-Sensitive strings in your database" +kind: article +slug: rails-mysql-case-sensitive-strings-in-your-database +created_at: 2009-09-03 +tags: + - Ruby on Rails +--- +When using Rails + MySQL, you'll find that normal string (or varchar(255)) fields are case insensitive. This can be quite a nuisance, but it's easy to resolve. You need to set your table to the utf8_bin collation. By using the binary variant, you're basically enabling case sensitivity. + + :::ruby + create_table :posts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin' do |t| + t.string :title, :limit => 100 + end + +That's all. The title field is now case sensitive. + +Another question I get a lot is how to change the collation and charset for an existing column. That's easy with the following query. Just make sure to pick the right column and data type: + + :::sql + ALTER TABLE posts MODIFY `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/content/posts/2009-09-04-git-tag-mini-cheat-sheet.md b/content/posts/2009-09-04-git-tag-mini-cheat-sheet.md new file mode 100644 index 0000000..9c1f292 --- /dev/null +++ b/content/posts/2009-09-04-git-tag-mini-cheat-sheet.md @@ -0,0 +1,34 @@ +--- +title: "Git Tag Mini Cheat Sheet" +kind: article +slug: git-tag-mini-cheat-sheet +created_at: 2009-09-04 +tags: + - git + - cheat sheet + - tags +--- + +Just as a kind of mini cheat sheet for using git tags: + +Adding a tag: + +
      +
    • git tag tag_name
    • +
    • git tag
      +Should show your new tag.
    • +
    • git push origin --tags or git push origin :tag_name
      +Because git push doesn't push tags.
    • +
    + +Removing a tag: + +
      +
    • git tag -d tag_name
    • +
    • git tag
      +Should no longer show your tag.
    • +
    • git push origin :refs/tags/tag_name
      +Because git push --tags doesn't push deleted tags.
    • +
    + +Hope that helps. \ No newline at end of file diff --git a/content/posts/2009-09-05-git-tag-mini-cheat-sheet-revisited.md b/content/posts/2009-09-05-git-tag-mini-cheat-sheet-revisited.md new file mode 100644 index 0000000..91eaf5f --- /dev/null +++ b/content/posts/2009-09-05-git-tag-mini-cheat-sheet-revisited.md @@ -0,0 +1,84 @@ +--- +title: "Git Tag Mini Cheat Sheet Revisited" +kind: article +slug: git-tag-mini-cheat-sheet-revisited +created_at: 2009-09-05 +tags: + - git + - cheat sheet + - tags +--- + +Just as a kind of mini cheat sheet for using git tags. Jörg Mittag had some great additions that weren't in the original post which warrant a new post. + +Git has three different type of tags: + +
      +
    • Lightweight tags
    • +
    • Annotated tags
    • +
    • Signed tags
    • +
    + +Let's start with lightweight tags. + +Lightweight tags + +In the previous cheat sheet only the lightweight local tags were discussed. A lightweight tag is nothing more than a reference to a particular revision or SHA1 object name in the repository. This kind of tag is quick and easy and very usable for local development to mark places in your commit history. + +Creating a lightweight tag is easy: + +git tag tag_name + +Viewing available tags is done with -l: + +git tag -l + +Annotated tags + +Annotated tags are almost like lightweight tags, the big difference is that they contain a message. Normally this message indicated why this tag is interesting. Use the -a option to create an annotated tag. + +git tag -a tag_name + +Since a message is required for annotated tags, you will be prompted with an editor to enter a message, or you can use the -m option to specify one directly. + +git tag -a -m "Tagging release 1.0" v1.0 + +To view annotated tags you can use the same -l option as before, but you have to instruct git to show the annotation messages as well: + +git tag -l -n1 + +This will not only show the messages for the annotated tags, it will also show the commit message of the revisions tagged with lightweight tags as well. Quite useful! + +Signed tags + +Signed tags take annotated tags a step further, they include an OpenPG signature to provide trust. While gits SHA1 tags provide integrity for the repository, the OpenPG signature makes sure that a trustworthy person created the tag. + +To create a signed tag you'll need to have GPG or some other OpenPG tool setup and use the -s option to sign the tag: + +git tag -s -m "Tagging release 2.0" v2.0 + +The -s options implies the -a option, so here too a message is required. + +To verify a signed tag you can run the following: + +git tag -v v2.0 + +Deleting tags + +There are times when you want to remove tags as well. This quite easy: + +git tag -d tag_name + +To remove a tag on a remote repository, you should do a special push: + +git push origin :refs/tags/tag_name + +Pushing tags + +Jörg Mittag claims that annotated and signed tags are pushed and fetched automatically. I have never seen a tag of any kind being pushed automatically, so I hope Jörg can clarify this further. + +To push your tags to a remote repository, use the following command to push all tags: + +git push origin --tags + +Happy tagging! \ No newline at end of file diff --git a/content/posts/2009-09-08-codaset-com-github-but-better.md b/content/posts/2009-09-08-codaset-com-github-but-better.md new file mode 100644 index 0000000..6482038 --- /dev/null +++ b/content/posts/2009-09-08-codaset-com-github-but-better.md @@ -0,0 +1,49 @@ +--- +title: "Codaset.com: Github, but better" +kind: article +slug: codaset-com-github-but-better +created_at: 2009-09-08 +tags: + - git + - codaset + - github +--- + +Today I had the very pleasure of giving Codaset.com a try! Codaset is being developed by Joel Moss in Ruby on Rails and could be a real Github killer! + +When I asked Joel what Codaset was and how he come to his idea he answered this: + +
    Codaset [...] was born out of my experiences with several tools and services. +
      +
    • I've used Trac for years, and admire its powerful issue management and milestone features.
    • +
    • I've used Github from the start, and love how social it makes open source software (as it should be).
    • +
    • I have fully embraced Git, and have no love for any other [...]. It makes for fast and efficient source control.
    • +
    • And I have used lots of other tools that help me in my day to day coding work.
    • + +Codaset is a tool that I created because I wanted the powerful issue and milestone management of Trac; with the social features of Github; mixed in with a heavy dose of Git [...] but I have so much more to include.
    + +Sounds like a brilliant app to me? I was invited into the beta, and this is what I saw: + +First of all, Codaset looks very slick. I'm no designer, but Codaset adds just enough visual sugar to make the app more usable, without distracting you. It just feels right. + +When compared to Github.com there are similarities and differences. Let me focus on those difference, since in my view those make Codaset really cool: + +As you create a new project you have the option of forking an existing repository. Not just a Codaset repository, no any git repository you have access to! I had no trouble creating a new project an importing my existing github project for example. + +The next thing I noticed when setting up a project is the public and private settings. No surprises there, but there was a third option: semi-private. This option will make your project closed-source, but expose the other features like the issue tracker, wiki and blog. It's not something an open-source developer would get excited about, but it is quite nice to have this option. + +If you're looking to add collaborators to your project, Codaset provides a nice role/permission based system for this. It allows you to give more fine-grained permissions to users, which is always a good thing. + +Codaset has, as Joel Moss tells you, great similarities with Trac when it comes to tickets. First you'll notice that you can define milestones in your project. To these milestones you can add tickets which in turn can have customized fields and states. Ticket-spam is battled with a built-in spam filter for tickets. The only thing missing is tags, but Joel tells me those are in the making. + +Just like github.com, codaset provides web space for you and your project. Setting this up is quite easy. Besides web space, every project has a blog as well. Hopefully this blog can be used to automatically post news about new tags in your git repository etc. + +Another nice touch is your personal dashboard. This dashboard show you your own and bookmarked projects and recent activity, but also an overview of open and assigned tickets for you. Really handy! + +The best thing ever is Twitter integration (which I have to try out), but it promises to push project activity to twitter, which would be a great way to keep people informed about your project. + +The only things I'm missing are tags for tickets and rubygem building/publishing. But, Joel Moss is still very hard at work, so I'm looking forward all the new great things he has to offer. + +For now, check out Codaset.com (and blog) or sign-up for a beta invite. Also make sure to checkout Joel Moss' blog. + +I haven't published any images or screenshots from Codaset in this post, because I've not yet asked Joel Moss for permission that. \ No newline at end of file diff --git a/content/posts/2009-10-13-epic-vs-awesome.md b/content/posts/2009-10-13-epic-vs-awesome.md new file mode 100644 index 0000000..dc878a6 --- /dev/null +++ b/content/posts/2009-10-13-epic-vs-awesome.md @@ -0,0 +1,21 @@ +--- +title: "Epic vs. Awesome" +kind: article +slug: epic-vs-awesome +created_at: 2009-10-13 +tags: + - General + - Fun + - awesome + - epic +--- + +There's bit of a discussion between me and @ludooo about which word has the most significance when measuring the greatness of something. + +I say epic is bigger than awesome. @ludooo says awesome is bigger than epic (he's not right, of course). + +Please help us decide! + + \ No newline at end of file diff --git a/content/posts/2009-10-13-valerii-32-base-string-encoder-and-decoder.md b/content/posts/2009-10-13-valerii-32-base-string-encoder-and-decoder.md new file mode 100644 index 0000000..ac3cf85 --- /dev/null +++ b/content/posts/2009-10-13-valerii-32-base-string-encoder-and-decoder.md @@ -0,0 +1,76 @@ +--- +title: "Valerii: 32-base string encoder and decoder" +kind: article +slug: valerii-32-base-string-encoder-and-decoder +created_at: 2009-10-13 +tags: + - Ruby + - gem + - 32base + - valerii + - math + - url shortner +--- + +You have probably seen URL shorteners that use short, seemingly random strings to identify sites. These strings are not random, they are encoded integer values. My valerii gem allows you to easily and quickly encode and decode integer values. Let me show you. + +To understand the shortening of integer values to strings you first need to know how integers work. + +We use a base-10 system. This means that for any number, every digit can hold 10 different values: 0 through 9. If you want to express more than 10 values, you need to add a digit: 26 means (6 * 1) + (2 * 10). + +You may be familiar with hexadecimal numbers. Hexadecimal has a base of 16, meaning every digit can hold 16 distinct values. 0 through 9, a through f. + +Converting an integer to a 32-base string + +The key to shortening a number to a string is that you need to store as many different values in a single digit. Because we want the digits to be part of an URL, we can only use valid URL characters. Characters like '/' and '?' are a no-go in this situation. + +Another reason for sticking with 32 (and not 52 (a-z, A-Z, 0-9)) is that I'm going to use bit encoding. But, first we need to define an alphabet to use to represent the 32 different values: + +
    ENCODE_CHARS =
    +  %w( B C D F G H J K
    +      M N P Q R S T V
    +      W Z b c d f h j
    +      k m n p r x t v )
    +
    + +For a value of 3 we'd use 'F'. Easy, right? + +Let's say we want to encode the number 123. What valerii does first is convert 123 to a binary string. + +
    123.to_s(2) # => "1111011"
    + +Cool, now split up this string in blocks of 5 bits. 5 bits can contain 32 different values. We need to start 'chopping' from the right to the left, so first we reverse the binary string and split it up. + +
    "1111011".reverse.scan(/.{1,5}/) # => ["11011", "11"]
    + +Now we convert the binary strings to 10-base numbers and map those to the characters defined in ENCODE_CHARS. The resulting characters are reversed again and joined to one string: + +
    123.to_s(2).reverse.scan(/.{1,5}/).map do |bits|
    +  ENCODE_CHARS[bits.reverse.to_i(2)]
    +end.reverse.join # => "Fp"
    + +Converting a 32-base string to an integer + +Converting a 32-base string back to its original integer value is quite easy now. The only trick is to create another hash that maps each character to its integer value: + +
    DECODE_MAP = ENCODE_CHARS.to_enum(:each_with_index).inject({}) do |h,(c,i)|
    +  h[c] = i; h
    +end
    + +This looks scary, but it's actually a common way to reverse key/values in a hash. + +Next is taking each character from the string and pushing the 5 bits we read in a variable. This variable is the original integer value. + +
    "Fp".split(//).map { |char|
    +  DECODE_MAP[char] or return nil
    +}.inject(0) { |result,val| (result << 5) + val } # => 123
    + +Notes + +Once you have established your way of encoding/decoding you should not change the alphabet you're using, since it redefines the meaning and value of the encoded strings. + +There are a lot of ways to store an integer value into a string. Another common methods is to use a 52 characters alphabet (a-z, A-Z, 0-9). You can't use the bit encoding here, but there are quite a few algorithms out there that let you do that. + +More info on how to get the valerii gem can be found at Gemcutter. + +Also checkout my other gems \ No newline at end of file diff --git a/content/posts/2009-10-15-git-problem-error-unable-to-create-temporary-sha1-filename.md b/content/posts/2009-10-15-git-problem-error-unable-to-create-temporary-sha1-filename.md new file mode 100644 index 0000000..589bf09 --- /dev/null +++ b/content/posts/2009-10-15-git-problem-error-unable-to-create-temporary-sha1-filename.md @@ -0,0 +1,22 @@ +--- +title: "Git problem: error: unable to create temporary sha1 filename" +kind: article +slug: git-problem-error-unable-to-create-temporary-sha1-filename +created_at: 2009-10-15 +tags: + - git + - prune + - repack + - fsck +--- +I got git problem: error: unable to create temporary sha1 filename when pushing to a remote repository. The fix is rather easy. +~ +On both your local and remote repositories perform the following magic: + + :::shell + git fsck + git prune + git repack + git fsck + +The last fsck should not report any problems. \ No newline at end of file diff --git a/content/posts/2009-10-25-they-are-just-tools-people.md b/content/posts/2009-10-25-they-are-just-tools-people.md new file mode 100644 index 0000000..213fc0e --- /dev/null +++ b/content/posts/2009-10-25-they-are-just-tools-people.md @@ -0,0 +1,52 @@ +--- +title: "They are just tools, people!" +kind: article +slug: they-are-just-tools-people +created_at: 2009-10-25 +tags: + - Blog + - codaset + - github + - basecamp + - 37signals + - tools + - perspective +--- + +Codaset is openly asking its users to comment on what pricing strategy they would like most. I've spotted this before, but again, there are two types of users. Those who see a great service and know that it will make their job easier, so they are willing to pay for it. There are also those who want a trillion repositories, unlimited disk space and what not for $1 a month (or less). This post is for the latter group of people. + +Some developers claim they need to use all of 37Signals' apps, have the biggest Github plan available and buy that new shiny 17" MacBook Pro (or that 27" iMac, I know). With all those tools and hardware available, how can your brilliant plan not succeed? All the successful people you've heard of use them. So, with all that setup, you're golden! Right? Then they check the price tag. It's huge! + +In my opinion, the problem started with shared hosting. Shared hosting was the business to be in about 5-10 years back. Everyone wanted it and everybody wanted it cheap. So, what you got (and still have) are big, over-selling hosting companies. You get 5 Terabyte of storage, unlimited bandwidth and you can host unlimited websites! The price? Only $1 a month! A bargain! + +Did you really think that there is a big server with 5 Terabyte of space is reserved for you? Or that you really have unlimited bandwidth? I can't remember how many people I've heard bitching about them hosting MP3's and then having their accounts shutdown for excessive bandwidth usage. + +Those companies were over-selling like crazy. They basically sold every megabyte of disk space they had available 50 times or more! Of course, they attracted vast numbers of customers and business was booming. With the number of customers rising, the quality of services went down the drain. Too often have I heard people complaining about their over selling shared hosting provider. Shame. + +But why do people choose for the $1 over selling package when for $7 they could have had better service and quality, albeit for a higher price? Simply because we like the feeling that we can grow big. Every one wants to be the new Google and store all his home-made (or other wise gathered) music online. + +Luckily, most "web 2.0" service providers (which 37Signals and Github really are) have come to another idea. Sell less features for a reasonable, maintainable price. Yes, Basecamp has less features than the competition, but at least the price is fair and service is good. + +So, here we are in 2009 and every small company can get good project management, code hosting, issue trackers, invoicing tools and what not for quite reasonable prices. Truly awesome! + +But, here come the people that want to "grow big" and become the next Google. These are mostly young developers who don't have a lot of business experience, and are still in school most of the time. They read and hear the great success stories and how those people used Basecamp and Github. Those must be magic tools! + +And look, every one is using a Mac and TextMate! I'll need those too! + +No offense to Github, Basecamp, TextMate or Apple, but that is not how it works. Businesses are not created by using a certain set of tools. The reason a successful business turns to Basecamp is because they have a problem with their current project management. When you start freelancing and you only have one or two client, you probably don't need the biggest Basecamp plan to get along. Oh, you are developing the next iPhone killer-app? Then you probably don't need Basecamp at all. + +The same goes for Github. When you're working on a project that you believe in, it's worth the $7 for the basic plan. + +At Codaset I read some user comments and the story is the same. Some serious freelancers or companies explain what's valuable to them and what they'd like to pay for it. All reasonable, because they know the cost of doing business. + +Then, there are also some people who want it all for free. Why, because they really need Codaset and all its features. + +People, sorry to burst your bubble, but they are just tools. It's like buying a 112-piece screw driver kit when all you want to do is switch the occasional light bulb. + +Here's my advice. Start your project. Just on your old Mac (or Dell) and work on it for a few weeks. Use the tools available to you to do your time tracking and invoicing. Push your git repo that old Pentium 4 Dell you have gathering dust in the basement. + +Then, when you find that communicating with your clients is getting difficult by phone and email only, then give Basecamp a try. When you feel that your code is getting so valuable that you don't want to risk putting it only on an old machine in the basement, store it at Github. + +All those web 2.0 apps are really just tools that you can use. Tools are there to solve a problem. If you don't have a problem, you don't need the tools. + +Only use the tools that you really need, and when you do, they'll be more than worth the price you pay for them. \ No newline at end of file diff --git a/content/posts/2009-10-26-how-to-create-and-apply-a-patch-with-git.md b/content/posts/2009-10-26-how-to-create-and-apply-a-patch-with-git.md new file mode 100644 index 0000000..879f390 --- /dev/null +++ b/content/posts/2009-10-26-how-to-create-and-apply-a-patch-with-git.md @@ -0,0 +1,84 @@ +--- +title: "How to create and apply a patch with Git" +kind: article +slug: how-to-create-and-apply-a-patch-with-git +created_at: 2009-10-26 +tags: + - git + - patch + - git apply + - git am + - apply + - am + - signed-off-by +--- +Git is quite common nowadays and a lot of people are asking me how they can create a patch file. Creating a patch file with git is quite easy to do, you just need to see how it's done a few times. + +This article will show you how to create a patch from the last few commits in your repository. Next, I'll also show you how you can correctly apply this patch to another repository. +~ +Before you start + +To make creating patches easier, there are some common git practices you should follow. It's not necessary, but it will make your life easier. + +If you fix a bug or create a new feature – do it in a separate branch! + +Let's say you want to create a patch for my imdb gem. You should clone my repository and create a new branch for the fix you have in mind. In this sample we'll do an imaginary fix for empty posters. + + git clone git://github.com/ariejan/imdb.git + cd imdb + git checkout -b fix_empty_poster + +Now, in the new fix_empty_poster branch you can hack whatever you need to fix. Write tests, update code etc. etc. + +When you're satisfied with all you changes, it's time to create your patch. FYI: I'm assuming you made a few commits in the fix_empty_poster branch and did not yet merge it back in to the master branch. + +Creating the patch + +Okay, I've made some commits, here's the git log for the fix_empty_poster branch: + + git log --pretty=oneline -3 + * ce30d1f - (fix_empty_poster) Added poster URL as part of cli output (7 minutes ago) + * 5998b80 - Added specs to test empty poster URL behaviour (12 minutes ago) + * aecb8cb - (REL-0.5.0, origin/master, origin/HEAD, master) Prepare release 0.5.0 (4 months ago) + +In GitX it would look like this: + +imdb_fix_empty_poster_01 + +Okay, now it's time to go and make a patch! All we really want are the two latest commits, stuff them in a file and send them to someone to apply them. But, since we created a separate branch, we don't have to worry about commits at all! + + git format-patch master --stdout > fix_empty_poster.patch + +This will create a new file fix_empty_poster.patch with all changes from the current (fix_empty_poster) against master. Normally, git would create a separate patch file for each commit, but that's not what we want. All we need is a single patch file. + +Now, you have a patch for the fix you wrote. Send it to the maintainer of the project ... + +Applying the patch + +... who will apply the patch you just sent! But, before you do that, there are some other steps you should take. + +First, take a look at what changes are in the patch. You can do this easily with git apply + + git apply --stat fix_empty_poster.patch + +Note that this command does not apply the patch, but only shows you the stats about what it'll do. After peeking into the patch file with your favorite editor, you can see what the actual changes are. + +Next, you're interested in how troublesome the patch is going to be. Git allows you to test the patch before you actually apply it. + + git apply --check fix_empty_poster.patch + +If you don't get any errors, the patch can be applied cleanly. Otherwise you may see what trouble you'll run into. To apply the patch, I'll use git am instead of git apply. The reason for this is that git am allows you to sign off an applied patch. This may be useful for later reference. + + git am --signoff < fix_empty_poster.patch + Applying: Added specs to test empty poster URL behaviour + Applying: Added poster URL as part of cli output + +Okay, patches were applied cleanly and you're master branch has been updated. Of course, run your tests again to make sure nothing got borked. + +In you git log, you'll find that the commit messages contain a "Signed-off-by" tag. This tag will be read by Github and others to provide useful info about how the commit ended up in the code. + +imdb_signed_off + +That's all folks! + +Are there any other git topics you'd like covered here? Please let me know! diff --git a/content/posts/2009-11-24-epic-textmate-theme.md b/content/posts/2009-11-24-epic-textmate-theme.md new file mode 100644 index 0000000..4776b65 --- /dev/null +++ b/content/posts/2009-11-24-epic-textmate-theme.md @@ -0,0 +1,28 @@ +--- +title: "Epic TextMate Theme" +kind: article +slug: epic-textmate-theme +created_at: 2009-11-24 +tags: + - General + - Textmate + - Ruby + - Ruby on Rails + - epic + - themes + - colours +--- + +Okay, I've wanted to make a custom TextMate theme since I first installed the thing on my MacBook in 2006. Today I present you with 'Epic'. + +Installation + +Grab the theme here: EpicBlue.tmTheme.zip (1.5k). Unzip it and open it with TextMate. That's all. Enjoy! + +Samples + + + + + + \ No newline at end of file diff --git a/content/posts/2010-01-17-the-epic-e-reading-experience-amazone-kindle.md b/content/posts/2010-01-17-the-epic-e-reading-experience-amazone-kindle.md new file mode 100644 index 0000000..db01238 --- /dev/null +++ b/content/posts/2010-01-17-the-epic-e-reading-experience-amazone-kindle.md @@ -0,0 +1,37 @@ +--- +title: "The epic e-reading experience: Amazone Kindle" +kind: article +slug: the-epic-e-reading-experience-amazone-kindle +created_at: 2010-01-17 +tags: + - General + - amazon + - kindle + - kindle2 + - e-reader + - ereader + - ebook + - books + - paperbacks + - reading +--- + +For some time I have been eyeballing Sony's e-reader in the local bookstore. I tried it a few times, but I didn't like it - actually I had serious doubts about e-books in general because of the experience. Sony's e-reader was not really easy to use with only one next-page button in a not-so-easy to access place. It also had a slow e-ink screen. It took a second to a second-and-a-half to show the next page. I didn't like it and had serious doubts about buying any e-reader at all. + +But, reading about all the "epic win"-stories of the Kindle by @johnnybusca I just had to give it a try - I bought one. + +Since receiving the Kindle last monday, I haven't felt any regret! The Kindle easy beautiful, fast with screen updates and of course integrates neatly with Amazon's Kindle Store. The whole product is designed to be easy to use - and it is. + + +I had already purchased The One That Got Away for Kindle for iPhone. Syncing it to my Kindle was done in about 30 seconds and I was ready to start reading! + +For me the Kindle has three big advantages: +
      +
    • The Kindle is easier to hold with one hand that a paperback. With a "Next page" button on either side of the device, it doesn't matter if you hold it with your right or left hand!
    • +
    • I don't need to lug around paperbacks. I can always have my stack of novels with me. Also, Kindle takes up a lot less space than all my paperbacks do now!
    • +
    • The built-in dictionary is very easy to access while reading. This gives me, as a non-native English speaker, the great benefit of quickly looking up words I don't yet know and actually learn them while reading.
    • +
    + +There is one little problem with Kindle, and that is the battery. You can use Kindle a long time on battery, even with wireless enabled, but eventually you'll have to recharge. It's not a huge task to hook up the Kindle to my Mac, but it might be a little troublesome on holidays where no computer is available. The US power connector is utterly useless in Europe. + +To make a long story short. If you're considering to buy an e-ready, go for Amazon's Kindle. Don't buy the Sony or any other product that might be cheaper, go for quality and ease of use! Now, enough with the blog writing, back to reading on my Kindle! \ No newline at end of file diff --git a/content/posts/2010-01-19-sign-the-petition-stop-eu-software-patents.md b/content/posts/2010-01-19-sign-the-petition-stop-eu-software-patents.md new file mode 100644 index 0000000..1d5e7c8 --- /dev/null +++ b/content/posts/2010-01-19-sign-the-petition-stop-eu-software-patents.md @@ -0,0 +1,34 @@ +--- +title: "Sign the petition: Stop EU Software Patents" +kind: article +slug: sign-the-petition-stop-eu-software-patents +created_at: 2010-01-19 +tags: + - General + - europe + - software patents + - petition + - eu +--- +From stopsoftwarepatents.eu: + +Our petition aims to unify the voices of concerned Europeans, associations and companies, and calls on our politicians in Europe to stop patents on software with legislative clarifications. + +The patent system is misused to restrain competition for the economical benefit of a few but fails to promote innovation. A software market environment is better off with no patents on software at all. Healthy competition forces market players to innovate. + +European court decisions still accept in many cases the validity of the software patents granted by national patent offices and the European Patent Office (EPO) that is beyond democratic control. They not only continue to grant them, but also to lobby in favor of them. Despite the current deep crisis of the patent system, they are unable to reform and put at risk too many European businesses with their soft granting policy. + +On 2005 the Commission appeared to be more supportive to the interests of major international conglomerates than of small and medium sized enterprises from Europe - who are a major driving force behind European innovation. The European Parliament rejected at the end the software patent directive, but has no rights for legislative initiatives. + +We urge our legislators + +
      +
    • to pass national legal clarifications to substantive patent law to rule out any software patent;
    • +
    • to invalidate all granted claims on patents that can be infringed by software run on programmable apparatus;
    • +
    • to also strive to propagate these rules to the European level, including the European Patent Convention.
    • +
    + +Sign the petition now! + + +stopsoftwarepatents.eu petition banner \ No newline at end of file diff --git a/content/posts/2010-02-02-how-to-order-your-kindle-from-the-netherlands.md b/content/posts/2010-02-02-how-to-order-your-kindle-from-the-netherlands.md new file mode 100644 index 0000000..5d0d3be --- /dev/null +++ b/content/posts/2010-02-02-how-to-order-your-kindle-from-the-netherlands.md @@ -0,0 +1,85 @@ +--- +title: "How to order your Kindle from the Netherlands" +kind: article +slug: how-to-order-your-kindle-from-the-netherlands +created_at: 2010-02-02 +tags: + - General + - amazon + - kindle + - ereader + - ebook +--- + +I get a lot of questions about how I bought my Kindle and what it cost to get it shipped to the Netherlands. So, for all those Dutchmen (and Dutchwomen) who are considering a Kindle, here's a short how-to in Dutch: + +Eindelijk heb je dan besloten dat je een eReader wilt en je keuze is gevallen op de Amazon Kindle. Goede keuze! Maar, hoe bestel je nu zo'n Kindle? Wat heb je nodig en waar moet je op letten. + +Stap 1 - Shoppen bij Amazon + +De eerste stap is dat je de Kindle (of Kindle DX) in je winkelwagentje laadt, eventueel samen met accessoires zoals deze cover. + +De Kindle kost $259 (de DX gaat voor $489 over de virtuele toonbank). Helaas zijn er nog bijkomende kosten: +
      +
    • Verzenkosten – Amazon bracht bij mijn bestelling zo'n $21 dollar aan verzendkosten in rekening. Voor dit bedrag wordt je Kindle via UPS Express bezorgt.
    • +
    • Invoerrechten – Omdat je de Kindle van uit de V.S. importeert moet je hier invoerrechten over betalen. In het geval van de Kindle komt dit op een bedrag van $53.
    • +
    + +De totale kosten van de bestelling bedragen ±$333. Afhankelijk van de wisselkoers komt dit uit op een bedrag van €240,-. + +Stap 2 - Maken van een Amazon account + +Als je de bestellig wilt gaan plaatsen zal Amazon je vragen in te loggen of een account te maken. Een account maken is snel en eenvoudig. + +Stap 3 - Afleveradres + +Amazon zal je vragen waar het apparaat afgelevert moet worden. Zorg er voor dat je voor 'Netherlands' kiest bij land en vul ter volledigheid bij 'State' je provincie in. + +Stap 4 - Betaling + +Om bij Amazon te betalen heb je een VISA of MasterCard creditcard nodig. Amazon biedt geen iDeal of bankoverschrijving aan, het is immers een Amerikaans bedrijf. + +Heb je zelf geen creditcard, dan kun je misschien een deal maken met een vriend of kennis die wel een creditcard heeft. Amazon biedt je de mogelijkheid om de bestelling in dollars of euros te betalen. Het voordeel van het direct betalen in euros is dat je exact weet hoeveel je crediccard provider in rekening brengt. + +Let op als je de creditcard van een vriend of kennis gebruikt! Je moet dan een apart factuuradres (billing address) opgeven dat klopt met de adresgegevens van de creditcard. Het afleveradres kan gewoon die van jezelf zijn. + +Stap 5 - Afronden van de bestelling + +Als je de bestelling plaats krijg je hiervan per email bevestiging van Amazon. Nu is het wachten tot UPS je Kindle komt afleveren. + +Stap 6 - Levering + +Zodra Amazon je Kindle verstuurt heeft krijg je hiervan een email. In deze mail staat de verwachtte lever datum (meestal ± 5 werkdagen) en een track-and-trace code van UPS. Met deze code kun je op ups.com zien waar je bestelling zich bevindt. + +UPS bezorgt niet op zaterdag. Als je niet thuis bent zullen ze het pakketje bij de buren proberen te bezorgen, je krijgt hiervan een briefje in de brievenbus. + +Stap 7 - Uitpakken en gebruiken + +Als je je pakketje hebt uitgepakt kun je direct gaan lezen! Als je een boek koopt (via de Kindle of via je computer) dan wordt dat boek direct via de draadloze 3G verbinding naar je Kindle gestuurd! + +Veel lees plezier! + +Bonus tip! + +Via project Gutenberg zijn duizenden boeken gratis te download! Op alle boeken die via deze weg beschikbaar zijn rust geen copyright meer, en zijn dus helemaal legaal. Zie hier de top 100 meest gedownloadde boeken. + +Download altijd het Mobipocket formaat. Zet deze file in de documents directory op je Kindle. (Dit doe je door de Kindle met de USB kabel aan je computer aan te sluiten). + +Mocht je via deze 'guide' een Kindle kopen, dan hoor ik graag (via de comments) hoe hij bevalt! + + + + + + +
    + +

    +Amazon Kindle - $259 +

    +
    + +

    +Amazon Kindle DX - $489 +

    +
    \ No newline at end of file diff --git a/content/posts/2010-03-22-shields-up-rrrack-alert.md b/content/posts/2010-03-22-shields-up-rrrack-alert.md new file mode 100644 index 0000000..23fa861 --- /dev/null +++ b/content/posts/2010-03-22-shields-up-rrrack-alert.md @@ -0,0 +1,27 @@ +--- +title: "Shields up! Rrrack alert!" +kind: article +slug: shields-up-rrrack-alert +created_at: 2010-03-22 +tags: + - Wordpress + - Ruby + - toto + - rack +--- +For a very long time I have been dissatisfied with WordPress. Although it's the best PHP blogging engine out there, it sucked. + +Firstly, it's slow. I got the front end a bit snappier by caching the living crap out of it, but that did not help things for me in the backend. So, what to do? +~ + +I've been looking towards ruby-based blogs a lot. Typo and Mephisto were two of the first ones out there, but they just didn't feel right. I did try Mephisto for a while. However, the project didn't appear to be under active development and I had a really hard time migrating my WordPress content. + +Then came the first static site generators into my view. Jekyll being the obvious choice because of its affiliation with Github. But again, I had a hard time migrating my content (which consists of 192 blog posts, excluding this one). + +Then along came Toto. Toto is epic! Without much effort I dumped my posts into files and I had Toto up and running! It worked! All my content was here! + +There are two things that make Toto so great. Firstly all content is stored in files, so they are under version control and no database is needed. This also means that pages are served very quickly! Secondly it's rack-based, which means I can deploy it easily to my VPS at Kabisa under passenger (and Unicorn when I migrate to that). + +Well, so here I am! Still fiddling a bit with Toto (especially the HTML) as you can see. My goal is to make everything HTML5-compliant, but that'll have to wait a few days. + +Thinking about moving away from WordPress yourself? Feel free to comment or ask questions! \ No newline at end of file diff --git a/content/posts/2010-03-24-ariejannet-now-in-valid-html5.md b/content/posts/2010-03-24-ariejannet-now-in-valid-html5.md new file mode 100644 index 0000000..76dbded --- /dev/null +++ b/content/posts/2010-03-24-ariejannet-now-in-valid-html5.md @@ -0,0 +1,31 @@ +--- +title: "Ariejan.net now in valid HTML5" +kind: article +slug: ariejannet-now-in-valid-html5 +created_at: 2010-03-24 +tags: + - html5 + - css3 +--- +_As most_ developers already know: [HTML5][1] [is][2] [very][3] [awesome][4]! So, with my move from WordPress to Toto I've made sure Ariejan.net is HTML5 compliant. + +[1]: http://dev.w3.org/html5/spec/Overview.html +[2]: http://www.readwriteweb.com/archives/5_exciting_things_in_html_5.php +[3]: http://www.geektechnica.com/2009/06/5-amazing-html5-features-to-look-forward-to/ +[4]: http://www.youtube.com/html5 +~ + +I'm not going to tell you how I did it all, there's enough about writing HTML5 on the web already. + +Here's the proof: + +![W3C HTML5 Validation of Ariejan.net][img1] + +Or, if you don't believe the screenshot, [validate it yourself][5]. + +_Note: The warning is about using the expiremental HTML5 Conformance Checker_ + + +[5]: http://validator.w3.org/check?uri=http%3A%2F%2Fariejan.net%2F&charset=%28detect+automatically%29&doctype=Inline&group=0 + +[img1]: http://ariejan.net/images/valid_html5.jpg diff --git a/content/posts/2010-03-24-how-a-little-varnish-changed-my-life.md b/content/posts/2010-03-24-how-a-little-varnish-changed-my-life.md new file mode 100644 index 0000000..191c143 --- /dev/null +++ b/content/posts/2010-03-24-how-a-little-varnish-changed-my-life.md @@ -0,0 +1,70 @@ +--- +title: "How a little varnish changed my life" +kind: article +slug: how-a-little-varnish-changed-my-life +created_at: 2010-03-24 +tags: +--- +Okay, it's a bit of an exaggeration to say [varnish][1] changed my life, but it sure did change the speed of my site! + +I got from a rotten 6 requests per second with WordPress to a whopping 9500! If you're on Linux and running Apache, installing varnish is a breeze! Especially if you're hosting a well cacheable site like a blog. + +[1]: http://varnish-cache.org/ +~ + +I'm running on a Linux Debian virtual server (provided by [Kabisa][2]) with 1 VCPU core and 512MB of memory. Nothing fancy. + +Before I was running Apache 2 with PHP and WordPress. Doing the occasional test this gave me (uncached) a performance of about 6 request/second (using apache's `ab` with 10 concurrent connections). With some caching plugins I was able to crank that up to about 15-20 requests a second. + +After upgrading my blog to Toto, I got quite a boost to about 30 requests/second. But, my blog doesn't contain any dynamic elements any more and Toto + Rack give you all the handles to implement caching (ETags, Cache-Control headers, etc.). FYI: I'm running Apache2 + Passenger to run Toto. + +Since I heard about Varnish a few times before I decided to give it a try and `apt-get install varnish`'ed it on my Debian box (I'm running `squeeze`, thank you). + +Now I have a few other sites running on my vps which I don't want to cache just yet. The problem was how do I tell Varnish to only cache ariejan.net, and skip the rest. + +Here's the entire configuration for Varnish to accomplish just that: + + backend default { .host = "127.0.0.1"; .port = "8080"; } + sub vcl_recv { if (req.http.host !~ "ariejan.net") { return(pass); } } + +Yes, that is just two lines! What this does is forward everything you throw at varnish to the server at port 8080. The `vcl_recv` makes sure that if the hostname does not include ariejan.net varnish passes the request forward - no caching. + +The second thing I had to do was configure Apache to listen on port 8080 instead of 80 in `/etc/apache2/ports.conf`. Then also make sure to have all your virtual hosts (even those you don't want cached) configured for port 8080 too in `/etc/apache2/sites-available` + +Restart apache, restart varnish and you're golden! + +When I first ran my `ab` benchmark with 10 concurrent connections I got to about 150 requests per second. But when I really pushed it to a 1000 concurrent connections (`ab` couldn't handle more), I got to a whopping 9500 requests per seconds doing 60k requests! That is **epic**! Oh, and my VPS didn't even break a sweat - system load got up to 1.08 for second or so. + +For the record: + + # ab -c 1000 -n 60000 http://ariejan.net/2010/03/22/shields-up-rrrack-alert/ + + Server Software: Apache/2.2.15 + Server Hostname: ariejan.net + Server Port: 80 + + Document Path: /2010/03/22/shields-up-rrrack-alert/ + Document Length: 5117 bytes + + Concurrency Level: 1000 + Time taken for tests: 6.290 seconds + Complete requests: 60000 + Failed requests: 0 + Write errors: 0 + Total transferred: 331434376 bytes + HTML transferred: 307460062 bytes + Requests per second: 9539.34 [#/sec] (mean) + Time per request: 104.829 [ms] (mean) + Time per request: 0.105 [ms] (mean, across all concurrent requests) + Transfer rate: 51459.38 [Kbytes/sec] received + + Connection Times (ms) + min mean[+/-sd] median max + Connect: 1 56 364.9 12 6209 + Processing: 2 22 76.7 16 3073 + Waiting: 2 19 76.6 13 3070 + Total: 3 78 374.6 29 6223 + +Interested? Check out [Varnish][1] now or ask us at [Kabisa][2] to help you out! + +[2]: http://kabisa.nl \ No newline at end of file diff --git a/content/posts/2010-03-25-installing-the-nokogiri-ruby-gem-on-debian.md b/content/posts/2010-03-25-installing-the-nokogiri-ruby-gem-on-debian.md new file mode 100644 index 0000000..f45c19e --- /dev/null +++ b/content/posts/2010-03-25-installing-the-nokogiri-ruby-gem-on-debian.md @@ -0,0 +1,21 @@ +--- +title: "Installing the Nokogiri ruby gem on Debian" +kind: article +slug: installing-the-nokogiri-ruby-gem-on-debian +created_at: 2010-03-25 +tags: + - Linux + - Ruby + - debian + - rubygems + - nokogiri + - gems +--- +To install Nokogiri on a Debian system you need some system packages in place. This snippet will get you going quickly. +~ +First, install the necessary debian packages if you don't have them already: + + apt-get install build-essential libxml2-dev libxslt1-dev + +Then you can install nokogiri without any problem with `gem install nokogiri` + diff --git a/content/posts/2010-03-28-really-another-sinatra-url-shortener-in-ruby.md b/content/posts/2010-03-28-really-another-sinatra-url-shortener-in-ruby.md new file mode 100644 index 0000000..75e8477 --- /dev/null +++ b/content/posts/2010-03-28-really-another-sinatra-url-shortener-in-ruby.md @@ -0,0 +1,27 @@ +--- +title: "Really? Another Sinatra URL Shortener in Ruby?" +kind: article +slug: really-another-sinatra-url-shortener-in-ruby +created_at: 2010-03-28 +tags: + - Ruby + - sinatra + - urls + - shortening + - base62 +--- +With my recent interest in [Sinatra][1] I decided to make some us of it and write a URL shortener service for with it. This also gave me an excuse to polish up my [DataMapper][2] skills a bit. + +[1]: http://www.sinatrarb.com/ +[2]: http://datamapper.org/ +~ + +Now, it's finished! I put the app on [http://github.com/ariejan/firefly][3]. [Feel free to fork][4]! + +Not sure if it works? Try this: [http://aj.gs/2][5] + +Questions? Please use the comments! + +[3]: http://github.com/ariejan/firefly +[4]: http://github.com/ariejan/firefly/fork +[5]: http://aj.gs/2 \ No newline at end of file diff --git a/content/posts/2010-03-29-announcing-firefly-a-ruby-url-shortener.md b/content/posts/2010-03-29-announcing-firefly-a-ruby-url-shortener.md new file mode 100644 index 0000000..8f44892 --- /dev/null +++ b/content/posts/2010-03-29-announcing-firefly-a-ruby-url-shortener.md @@ -0,0 +1,126 @@ +--- +title: "Announcing Firefly, a ruby URL shortener" +kind: article +slug: announcing-firefly-a-ruby-url-shortener +created_at: 2010-03-29 +tags: +--- +Here it is! Firefly! The easiest URL shortener there is in Ruby land! + +How easy? Install the gem, copy and paste a `config.ru` and you're good to go! + +Read [the documentation][2] or [the source][3] for more details. I've included the README in this post as well for your convenience. + +_FYI: I'm currently running aj.gs on Firefly 0.1 with MySQL._ + +[1]: http://ariejan.net/2010/03/28/really-another-sinatra-url-shortener-in-ruby/ +[2]: http://github.com/ariejan/firefly#readme +[3]: http://github.com/ariejan/firefly +~ + +### FireFly + +FireFly is a simple URL shortener for personal use. + +### Installation + + sudo gem install firefly + +After you have installed the Firefly gem you should create a `config.ru` file that tells your webserver what to do. Here is a sample `config.ru`: + + :::ruby + require 'rubygems' + require 'firefly' + + disable :run + + app = Firefly::Server.new do + set :hostname, "localhost:3000" + set :api_key, "test" + + # Use Sqlite3 by default + set :database, "sqlite3://#{Dir.pwd}/firefly.sqlite3" + + # You can use MySQL as well. + # Make sure to install the do_mysql gem: + # sudo gem install do_mysql + # set :database, "mysql://root@localhost/firefly" + end + + run app + +Next you can start your web server. You may try thin: + + thin start -R config.ru + +### Configuration + +All configuration is done in `config.ru`. + + * `:hostname` sets the hostname to use for shortened URLs. + * `:api_key` sets the API key. This key is required when posting new URLs + * `:database` a database URI that [DataMapper][1] can understand. + +It's possible to use all kinds of backends with DataMapper. Sqlite3 and MySQL have been tested, but others like MongoDB may work as well. + +[1]: http://datamapper.org/ + +### Usage + +Adding a URL is done by doing a simple POST request that includes the URL and your API key. + + curl -d "url=http://ariejan.net" -d "api_key=test" http://localhost:3000/api/add + +If you're on a MacOSX you could add the following function to your `~/.profile` to automate URL shortening: + + shorten(){ + URL=$1 + SHORT_URL=`curl -s -d "url=$URL&api_key=test" http://localhost:3000/api/add` + echo $SHORT_URL | pbcopy + + echo "-- $URL => $SHORT_URL" + echo "Short URL copied to clipboard." + } + +After you restart Terminal.app (or at least reload the `.profile` file) you can use it like this: + + $ shorten http://ariejan.net + -- http://ariejan.net => http://aj.gs/1 + Short URL copied to clipboard. + +### Bugs, Feature Requests, etc. + + * [Source][2] + * [Issue tracker][3] + +Feel free to fork Firefly and create patches for it. Here are some basic instructions: + + * Fork the project. + * Make your feature addition or bug fix. + * Add tests for it. This is important so I don't break it in a future version unintentionally. + * Commit, do not mess with Rakefile, VERSION, or HISTORY. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) + * Send me a pull request. Bonus points for topic branches. + +### License + +Copyright (c) 2009 Ariejan de Vroom + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/content/posts/2010-04-05-detect-browser-web-sockets-support.md b/content/posts/2010-04-05-detect-browser-web-sockets-support.md new file mode 100644 index 0000000..1151ed8 --- /dev/null +++ b/content/posts/2010-04-05-detect-browser-web-sockets-support.md @@ -0,0 +1,28 @@ +--- +title: "Detect browser Web Sockets support" +kind: article +slug: detect-browser-web-sockets-support +created_at: 2010-04-05 +tags: + - html5 + - websockets + - javascript + - jquery +--- +HTML5 Web Sockets are awesome! I've been toying around with it for a bit today and noticed that not every browser supports native HTML5 Web Sockets yet. + +Google Chrome 5 has native support for web sockets, FireFox 3.6 does not. This poses a problem if you're building something awesome that does require web sockets. +~ + +Luckily, it's easy to detect web sockets support through JavaScript. All you really need to do is check if `WebSocket` is defined or not. + +Here's a simple example, note that I'm using jQuery here. + + :::javascript + $(document).ready(function() { + if( typeof(WebSocket) != "function" ) { + $('body').html("

    Error

    Your browser does not support HTML5 Web Sockets. Try Google Chrome instead.

    "); + } + }); + +Maybe there are better ways, but _it works for me_. \ No newline at end of file diff --git a/content/posts/2010-04-13-get-ready-for-firefly-03.md b/content/posts/2010-04-13-get-ready-for-firefly-03.md new file mode 100644 index 0000000..194d26d --- /dev/null +++ b/content/posts/2010-04-13-get-ready-for-firefly-03.md @@ -0,0 +1,67 @@ +--- +title: "Get ready for Firefly 0.3!" +kind: article +slug: get-ready-for-firefly-03 +created_at: 2010-04-13 +tags: +--- +Hot off the press is Firefly 0.3! Firefly is a simple URL shortener application intended for personal use. It's core features are: + + * Very light-weight - based on Sinatra + * Shorten URLs using 62-Base encoding + * Offers an easy to use API + * Keeps track of URL clicks + * Supports most popular database backends, including MySQL and Sqlite3 + * Includes a ready-to-use Bookmarklet + * Works with any Rack capable web server + +Interested? Give it a go! Here's how in ht +~ + +### 1. Install Firefly + + gem install firefly + +### 2. Ready? + +Create a new directory and place the following `config.ru` file in it: + + :::ruby + require 'rubygems' + require 'firefly' + + disable :run + + app = Firefly::Server.new do + set :hostname, "localhost:3000" + set :api_key, "test" + set :database, "sqlite3://#{Dir.pwd}/firefly.sqlite3" + end + + run app + +### 3. Go! + +Start your engines! In this case I use `thin`: + + thin start -R config.ru + +Now, when you visit `http://localhost:3000/` you'll be asked for your API key to login. Then, start shortening! + +### Updating + +If you were already running Firefly 0.2, upgrading is easy. Simple update the Firefly gem to 0.3 and restart your server. + +When you update from 0.2 you'll notice your click stats are all indicating `0`. Don't worry, no data was lost! The solution is quite simple. + + 1. Remove the `clicks` field from `firefly_urls` + + :::sql + ALTER TABLE `firefly_urls` DROP `clicks`; + + 2. Rename the `visits` field to `clicks` + + :::sql + ALTER TABLE `firefly_urls` CHANGE `visits` `clicks` int; + +All done! No restart required. \ No newline at end of file diff --git a/content/posts/2010-04-14-a-new-day-a-new-firefly.md b/content/posts/2010-04-14-a-new-day-a-new-firefly.md new file mode 100644 index 0000000..3b02063 --- /dev/null +++ b/content/posts/2010-04-14-a-new-day-a-new-firefly.md @@ -0,0 +1,27 @@ +--- +title: "A new day, a new Firefly" +kind: article +slug: a-new-day-a-new-firefly +created_at: 2010-04-14 +tags: +--- +Get ready for 0.4 (or, 0.4.0.1 to be exact)! This release has some interesting new features. Read more to find out, I've included a screen shot for your pleasure. + + * Twitter integration + * Easier copying of short URLs + * Highlight the last shortened URL + * Several fixes, see [HISTORY][1] for details + +Coming from Digg? Check out the source and `README` at [http://github.com/ariejan/firefly][2] + +_The 0.4.0.1 release fixes file permissions for the `twitter.png` image so your server can read it properly._ + +[1]: http://github.com/ariejan/firefly/blob/v0.4.0.1/HISTORY +[2]: http://github.com/ariejan/firefly +~ + +[![aj.gs Screen shot][3]][4] + +[3]: http://ariejan.net/images/aj.gs-sample.jpg "aj.gs Screen shot" +[4]: http://ariejan.net/images/aj.gs-sample-full.jpg + diff --git a/content/posts/2010-04-25-ruby-version-and-gemset-in-your-bash-prompt-yes-sir.md b/content/posts/2010-04-25-ruby-version-and-gemset-in-your-bash-prompt-yes-sir.md new file mode 100644 index 0000000..6555d32 --- /dev/null +++ b/content/posts/2010-04-25-ruby-version-and-gemset-in-your-bash-prompt-yes-sir.md @@ -0,0 +1,40 @@ +--- +title: "Ruby version and gemset in your Bash prompt? Yes sir!" +kind: article +slug: ruby-version-and-gemset-in-your-bash-prompt-yes-sir +created_at: 2010-04-25 +tags: + - Ruby + - BASH + - rvm + - gemset + - profile +--- +RVM is an easy way to switch between different ruby implementations and gemsets. If you don't know about it, [go check it out][1]. If you do know about, you'll know how annoying it is never to know which ruby version and gemset you're currently using. Here is a nice `.profile` hack that shows your current ruby version and optional gemset in your prompt. + +[1]: http://rvm.beginrescueend.com/ +~ +Firstly, add the following function to your `~/.profile`: + + function rvm_version { + local gemset=$(echo $GEM_HOME | awk -F'@' '{print $2}') + [ "$gemset" != "" ] && gemset="@$gemset" + local version=$(echo $MY_RUBY_HOME | awk -F'-' '{print $2}') + [ "$version" != "" ] && version="@$version" + local full="$version$gemset" + [ "$full" != "" ] && echo "$full " + } + +Next, you can use this function in your prompt. Like this: + + export PS1="\$(rvm_version) \w \$(parse_git_branch) \$ " + +The results? For standard ruby 1.8.7 + + @1.8.7 ~ $ + +Or with the `rails3` gemset enabled: + + @1.8.7@rails3 ~ $ + +So, now you always know which ruby you're using! Happy coding! \ No newline at end of file diff --git a/content/posts/2010-04-30-firefly-041-released.md b/content/posts/2010-04-30-firefly-041-released.md new file mode 100644 index 0000000..2eb3dd9 --- /dev/null +++ b/content/posts/2010-04-30-firefly-041-released.md @@ -0,0 +1,28 @@ +--- +title: "Firefly 0.4.1 released" +kind: article +slug: firefly-041-released +created_at: 2010-04-30 +tags: + - firefly + - url shortening +--- +I just pushed [Firefly 0.4.1][1] to [Rubygems][2]. Updating is easy: + + gem update firefly + +Don't forget to restart your server, that's all. + +The 0.4.1 release covers the following changes: + + * Normalize URLs before shortening them. This prevents false duplicates. + * Validate URLs to be valid HTTP or HTTPS, don't accept others. Closes #8 + * Don't ask for the API key after shortening a URL with the bookmarklet. + * Show the highlighted URL separately. Closes #7. + +[1]: http://github.com/ariejan/firefly/tree/v0.4.1 +[2]: http://rubygems.org/gems/firefly +~ +If you are interested in contributing to Firefly, please fork the project on [github][3]. Pull requests are very welcome. + +[3]: http://github.com/ariejan/firefly \ No newline at end of file diff --git a/content/posts/2010-05-17-bundler-passenger-with-rails-235-yes-please.md b/content/posts/2010-05-17-bundler-passenger-with-rails-235-yes-please.md new file mode 100644 index 0000000..aab489f --- /dev/null +++ b/content/posts/2010-05-17-bundler-passenger-with-rails-235-yes-please.md @@ -0,0 +1,43 @@ +--- +title: "Bundler + Passenger with Rails 2.3.5? Yes, please!" +kind: article +slug: bundler-passenger-with-rails-235-yes-please +created_at: 2010-05-17 +tags: + - Ruby + - Rails + - bundler + - passenger +--- +Bundler allows you to define the gems your application uses, resolve dependencies and load everything up. This is great, because you don't have to manage all those different gem versions yourself any more. + +There is a little problem, though. When you want to use Bundler with Rails 2.3.5. you need to do a bit of extra work. You'll need to create a file `config/preinitializer.rb` that contains the following: +~ + :::ruby + require "rubygems" + require "bundler" + + if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5") + raise RuntimeError, "Your bundler version is too old." + + "Run `gem install bundler` to upgrade." + end + + # Set up load paths for all bundled gems + Bundler.setup + rescue Bundler::GemNotFound + raise RuntimeError, "Bundler couldn't find some gems." + + "Did you run `bundle install`?" + end + +Then you deploy your app with Capistrano (as `root`) and find that Passenger can't find your gems. True, you need to install them, so you add a Capistrano task to run `bundle install` after you update your code. Still, passenger can't find the gems. + +The problem is that bundler installs the gems to your `~/.bundle`. When you run bundler as `root`, passenger won't be able to find the gems in `/root/.bundle`. + +A solution is easy: `bundle install .bundle` will install the gems to `./.bundle`, which should be your rails root directory. That solves your problem with passenger! Here's the full Capistrano task: + + :::ruby + desc "Install bundled gems into ./.bundle" + task :bundle do + run "cd #{release_path}; bundle install .bundle" + end + diff --git a/content/posts/2010-05-30-upgrading-to-mongoid-beta-6.md b/content/posts/2010-05-30-upgrading-to-mongoid-beta-6.md new file mode 100644 index 0000000..9502a67 --- /dev/null +++ b/content/posts/2010-05-30-upgrading-to-mongoid-beta-6.md @@ -0,0 +1,39 @@ +--- +title: "Upgrading to Mongoid Beta 6" +kind: article +slug: upgrading-to-mongoid-beta-6 +created_at: 2010-05-30 +tags: + - Ruby + - Rails + - mongoid + - monogdb +--- +If you are working with Rails 3 and Mongoid, you're likely to upgrade to [`mongoid-2.0.0.beta6`][1]. That's okay, but you will run into a few problems. Among others, one will be: + + Database should be a Mongo::DB, not NilClass + +or + + Mongoid::Errors::InvalidDatabase: Mongoid::Errors::InvalidDatabase + +Another, Mongoid-related problem is the error `uninitialized constant OrderedHash`. + +Luckily, these problems can be solved quite easily. +[1]: http://rubygems.org/gems/mongoid +~ +The first thing you need to do is make sure you use the right version of `bson_ext`. Beta 6 requires you to run `bson_ext-1.0.1` or you'll get the `OrderedHash` error. Okay, with that out of the way, let's focus on the MongoDB errors. + +The problem is that Mongoid is accessed/used before it is properly initialized. To resolve this issue, add the following line to your other requires at the top of `config/application.rb`. + + :::ruby + require 'mongoid/railtie' + +With that, you initialize Mongoid correctly. Hope it helps. + +Update: I also ran into `keys must be strings or symbols` errors. This is now a _known issue_ with `mongoid-2.0.0.beta6` and has been fixed in `master`. If you are using Bundler (you are, aren't you?) then you can use the master branch instead of the gem: + + :::ruby + # gem 'mongoid', '2.0.0.beta6' + gem 'mongoid', :git => 'http://github.com/durran/mongoid.git' + diff --git a/content/posts/2010-06-06-firefly-043-and-firefly-client-040-released.md b/content/posts/2010-06-06-firefly-043-and-firefly-client-040-released.md new file mode 100644 index 0000000..d383248 --- /dev/null +++ b/content/posts/2010-06-06-firefly-043-and-firefly-client-040-released.md @@ -0,0 +1,39 @@ +--- +title: "Firefly 0.4.3 and Firefly Client 0.4.0 released" +kind: article +slug: firefly-043-and-firefly-client-040-released +created_at: 2010-06-06 +tags: + - Ruby + - gems + - urls + - firefly + - shortener +--- +Today version 0.4.3 of Firefly was released with some minor updates. To complete the package, a new gem [firefly-client][1] has been released. + +The client library allows your Ruby application to easily shorten URLs with a remote Firefly server. It's very easy to use and lightweight. + +[1]: http://github.com/ariejan/firefly-client +~ +*Firefly 0.4.3 Changelog* + + * Handle invalid API keys correctly. + * Added a fix for MySQL users to update the `code` column to use the correct collation. Fixes [issue #9][2] + +[2]: github.com/ariejan/firefly/issues/9 + +*Firefly Client* + +Using the Firefly Client is very easy, read the following snippet from the [README][3]: + + require 'rubygems' + require 'firefly-client' + + firefly = Firefly::Client.new("http://aj.gs", "my_api_key") + firefly.shorten("http://google.com") + => "http://aj.gs/8ds" + +Nice, huh? Get more info over at [github][1] + +[3]: http://github.com/ariejan/firefly-client#readme diff --git a/content/posts/2010-06-06-setup-your-own-firefly-url-shortener-in-25-minutes.md b/content/posts/2010-06-06-setup-your-own-firefly-url-shortener-in-25-minutes.md new file mode 100644 index 0000000..d5ceb07 --- /dev/null +++ b/content/posts/2010-06-06-setup-your-own-firefly-url-shortener-in-25-minutes.md @@ -0,0 +1,33 @@ +--- +title: "Setup your own Firefly URL shortener in 2.5 minutes" +kind: article +slug: setup-your-own-firefly-url-shortener-in-25-minutes +created_at: 2010-06-06 +tags: + - firefly +--- +By popular demand I've setup a guide to setup your own personal URL shortener with [Firefly][1] and [Heroku][2]. I've timed it an you can do it in under 2.5 minutes. How's that for fast? + +[1]: http://github.com/ariejan/firefly +[2]: http://heroku.com +~ + +I'm going to assume that you have `git` installed on your computer and that you have an already working Heroku account. If not, [sign up here for Heroku first][3]. + +I'll be referencing to a `my_firefly_dir` in this guide. You can just pick a name during step 2 and it will be created for you. + + 1. `gem install heroku` + 2. `git clone git://github.com/ariejan/firefly-heroku.git my_firefly_dir` + 3. `cd my_firefly_dir` + 4. `heroku create` + 5. Use the URL returned by `heroku create` and use the FQDN (xxxx.heroku.com) to configure the `hostname` in `config.ru`. Like: `set :hostname, 'xxxx.heroku.com'` + 6. Change the API key in `config.ru` to something more secure. + 7. Commit your changes with `git commit -am 'Updated hostname and apikey'` + 8. `git push heroku master` + 9. Done! Open your app with `heroku open` + +So, there's no excuse not to try Firefly now! Use the included _bookmarklet_ to quickly shorten URLs from your browser or use the `firefly-client` gem to enable shortening in your own application! + +Please, let me know if you did a succesfull install of Firefly and what you think about it. + +[3]: http://api.heroku.com/signup diff --git a/content/posts/2010-06-07-uploading-files-with-curl.md b/content/posts/2010-06-07-uploading-files-with-curl.md new file mode 100644 index 0000000..461632a --- /dev/null +++ b/content/posts/2010-06-07-uploading-files-with-curl.md @@ -0,0 +1,17 @@ +--- +title: "Uploading files with Curl" +kind: article +slug: uploading-files-with-curl +created_at: 2010-06-07 +tags: + - curl + - file upload +--- +I've always trouble uploading files with Curl. Some how the syntax for that command won't stick, so I post it here for future reference. + +What I want to do is perform a normal `POST`, including a file and some other variables to a remote server. This is it: + + curl -i -F name=test -F filedata=@localfile.jpg http://example.org/upload + +You can add as many `-F` as you want. The `-i` option tells curl to show the response headers as well, which I find useful most of the time. +~ diff --git a/content/posts/2010-06-10-cherry-picking-specific-commits-from-another-branch.md b/content/posts/2010-06-10-cherry-picking-specific-commits-from-another-branch.md new file mode 100644 index 0000000..d0e1b27 --- /dev/null +++ b/content/posts/2010-06-10-cherry-picking-specific-commits-from-another-branch.md @@ -0,0 +1,42 @@ +--- +title: "Cherry-Picking specific commits from another branch" +kind: article +slug: cherry-picking-specific-commits-from-another-branch +created_at: 2010-06-10 +tags: + - git + - cherry-pick + - scm +--- +I'm often asked how to merge only specific commits from another branch into the current one. The reason you'd want to do this is to merge specific changes you need now, leaving other code changes you're not interested in right now behind. + +First of all, use `git log` or the awesome [GitX][1] tool to see exactly which commit you want to pick. An example: + + dd2e86 - 946992 - 9143a9 - a6fd86 - 5a6057 [master] + \ + 76cada - 62ecb3 - b886a0 [feature] + +Let's say you've written some code in commit `62ecb3` of the `feature` branch that is very important right now. It may contain a bug fix or code that other people need to have access to now. Whatever the reason, you want to have commit `62ecb3` in the master branch right now, but not the other code you've written in the `feature` branch. +~ +Here comes `git cherry-pick`. In this case, `62ecb3` is the cherry and you want to pick it! + + git checkout master + git cherry-pick 62ecb3 + +That's all. `62ecb3` is now applied to the master branch and commited (as a new commit) in `master`. `cherry-pick` behaves just like `merge`. If git can't apply the changes (e.g. you get merge conflicts), git leaves you to resolve the conflicts manually and make the commit yourself. + +*Cherry picking a range of commits* + +In some cases picking one single commit is not enough. You need, let's say three consecutive commits. `cherry-pick` is not the right tool for this. `rebase` is. From the previous example, you'd want commit `76cada` and `62ecb3` in `master`. + +The flow is to first create a new branch from `feature` at the last commit you want, in this case `62ecb3`. + + git checkout -b newbranch 62ecb3 + +Next up, you rebase the `newbranch` commit `--onto master`. The `76cada^` indicates that you want to start from that specific commit. + + git rebase --onto master 76cada^ + +The result is that commits `76cada` through `62ecb3` are applied to `master`. + +[1]: http://gitx.frim.nl/ diff --git a/content/posts/2010-07-12-screencast-firefly-url-shortener-in-less-than-25-minutes.md b/content/posts/2010-07-12-screencast-firefly-url-shortener-in-less-than-25-minutes.md new file mode 100644 index 0000000..4d78274 --- /dev/null +++ b/content/posts/2010-07-12-screencast-firefly-url-shortener-in-less-than-25-minutes.md @@ -0,0 +1,16 @@ +--- +title: "Screencast: Firefly URL shortener in less than 2.5 minutes" +kind: article +slug: screencast-firefly-url-shortener-in-less-than-25-minutes +created_at: 2010-07-12 +tags: +--- +Firefly is a light-weight URL Shortener app. It's written in Ruby/Sinatra and can be easily deployed to Heroku, [as I've written before][1]. This screencast illustrates how easy it really is to setup Firefly and give it a spin. Did I mention that both Firefly and Heroku can be used for free? + +

    Firefly URL shortener in less than 2.5 minutes from Ariejan de Vroom on Vimeo.

    + +[1]: https://ariejannet.local/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/ +~ +As always I'm keen to hear how you are using Firefly. Got some coding talents? Feel free to contribute features and fixes through [github][2]. + +[2]: http://github.com/ariejan/firefly diff --git a/content/posts/2010-07-29-how-to-enable-ssh-forwarding-on-mac-os-x-snow-leopard.md b/content/posts/2010-07-29-how-to-enable-ssh-forwarding-on-mac-os-x-snow-leopard.md new file mode 100644 index 0000000..fc94bf2 --- /dev/null +++ b/content/posts/2010-07-29-how-to-enable-ssh-forwarding-on-mac-os-x-snow-leopard.md @@ -0,0 +1,37 @@ +--- +title: "How to enable SSH Forwarding on Mac OS X Snow Leopard" +kind: article +slug: how-to-enable-ssh-forwarding-on-mac-os-x-snow-leopard +created_at: 2010-07-29 +tags: + - Apple + - osx + - mac + - ssh + - forwarding + - rubber + - ssh-agent + - ssh-add + - snowleopard +--- +The other day I was toying with Rubber to deploy a Rails3 app to Amazon EC2. I host the project code in a private Github repository, accessible only with my own SSH key. + +In order to checkout your code an any EC2 instance you can do one of two things: + +1. Copy your private SSH key to the instance - This sounds easy enough, but has serious security implications. You do *not* want to be sending out your private SSH key, do you? That leaves you with option 2. + +2. Let SSH forward the authentication request to your local machine. This is call _Forwarding_ and requires `ssh-agent` to be running on your system. You're in luck, `ssh-agent` is started automatically on your mac. + +Now, the problem is that in Leopard (10.5) SSH Forwarding was enabled by default. You guessed it, in Snow Leopard it has been disabled by default. So, it's up to you to enable SSH Forwarding manually. Here goes: + + 1. Open Terminal.app. + 2. `sudo vi /etc/ssh_config`
    + You will be asked for your password now. Feel free to use your preferred editor here. + 3. Add the following two lines to the top of the file:
    + `Host *`
    + `ForwardAgent yes`
    + 4. Save the file and exit your editor. + +All right sparky, you now have enabled SSH Forwarding. Have fun! + +PS. If you have enabled "Remote Login" under Sharing Preferences, make sure to stop and start that service to notify it of the changes you just made. diff --git a/content/posts/2010-08-09-rename-a-git-branch.md b/content/posts/2010-08-09-rename-a-git-branch.md new file mode 100644 index 0000000..d4d2bd3 --- /dev/null +++ b/content/posts/2010-08-09-rename-a-git-branch.md @@ -0,0 +1,18 @@ +--- +title: "Rename a git branch" +kind: article +slug: rename-a-git-branch +created_at: 2010-08-09 +tags: + - git + - branch + - tip +--- +In git, branching is cheap and easy. You do it all the time (you're not? Well, you should). Sometimes, though, you create a new feature branch, only to come to the conclusion later that the name you gave it does not cover the stuff you've been doing. + +No problem for git! Renaming a branch is really easy: + + git branch -m old_branch new_branch + +That's all. +~ diff --git a/content/posts/2010-08-09-using-multiple-clipboards-in-vim.md b/content/posts/2010-08-09-using-multiple-clipboards-in-vim.md new file mode 100644 index 0000000..01f386b --- /dev/null +++ b/content/posts/2010-08-09-using-multiple-clipboards-in-vim.md @@ -0,0 +1,21 @@ +--- +title: "Using multiple clipboards in Vim" +kind: article +slug: using-multiple-clipboards-in-vim +created_at: 2010-08-09 +tags: + - vi + - vim +--- +On of the first things you learn when using Vim activly is that when you delete something using `x` or `dd` that content is actually cut and put on a clipboard. Later you can retrieve that content using the `p` or `P` commanands. + +One thing that makes Vim more awesome than let's say, uhm TextMate, is that it actually has more than one clipboard! Yeah! In fact, in Vim terminology those clipboards are called *registers*. +~ + +To view your current registers type `:reg`. You'll notice that every register is prefixed with the symbol `"`. + +How do you use these things? Let's say you want to copy a line into a specific register: `"kyy` will yank the current line into register `"k`. If you later want to paste register `"k` you can do this: `"kp`. Easy, huh? + +The beauty of registers is that they keep around until you explicitly copy something else into them. The normal clipboard or *paste buffer* is replaced everytime you cut or copy something, registers are not affected unless you specify to use them. + +Now, go on, code more productively. diff --git a/content/posts/2010-08-23-resque-how-to-requeue-failed-jobs.md b/content/posts/2010-08-23-resque-how-to-requeue-failed-jobs.md new file mode 100644 index 0000000..769cff3 --- /dev/null +++ b/content/posts/2010-08-23-resque-how-to-requeue-failed-jobs.md @@ -0,0 +1,31 @@ +--- +title: "Resque: how to requeue failed jobs" +kind: article +slug: resque-how-to-requeue-failed-jobs +created_at: 2010-08-23 +tags: + - Ruby + - resque +--- +Today I found about 100k Resque jobs in the _failed_ queue. Due to a small error in some user content, those jobs all failed. After fixing the problem, how do you reprocess all those jobs? + +Option one: go to the `resque-web` backend and click _retry_ about 100.000 times. + +Option two: do some cool ruby commands. +~ +Resque offers you direct access to the _failed_ queue and also provides a method to `requeue` jobs. How easy can it be, right? + +With `Resque::Failure.all(0,1)` you retrieve the first job from the _failed_ queue. `Resque::Failure.all` is simply a ruby Array. + +To requeue jobs, you can use `Resque::Failure.requeue(index)` where index corresponds to the index of the job in the `Resque::Failure.all` Array. + +To requeue all jobs in the _failed_ queue, you can simply run the following commands: + + :::ruby + # Requeue all jobs in the failed queue + (Resque::Failure.count-1).downto(0).each { |i| Resque::Failure.requeue(i) } + + # Clear the failed queue + Resque::Failure.clear + +That's all there is to it, really. Happy processing! diff --git a/content/posts/2010-09-11-mass-convert-wma-to-mp3-using-ffmpeg-and-ruby.md b/content/posts/2010-09-11-mass-convert-wma-to-mp3-using-ffmpeg-and-ruby.md new file mode 100644 index 0000000..b73a0a9 --- /dev/null +++ b/content/posts/2010-09-11-mass-convert-wma-to-mp3-using-ffmpeg-and-ruby.md @@ -0,0 +1,33 @@ +--- +title: "Mass convert WMA to MP3 using ffmpeg and ruby" +kind: article +slug: mass-convert-wma-to-mp3-using-ffmpeg-and-ruby +created_at: 2010-09-11 +tags: + - audio + - mp3 + - wma + - music + - ffmpeg +--- +Today I found myself in a situation where I have a few (200+) WMA audio files. Due to personal preference I want MP3, not WMA. So, let's convert that lot. + +What I want to do is convert all those WMA files to constant bitrate 192kbps, stereo mp3 files. (In my case, the WMA files have the required quality to use this settings). +~ +The first tool you need is ffmpeg. If you're on Mac, simple run `brew install ffmpeg`. The second tool is `irb` or _Interactive Ruby. + +With `ffmpeg` setup and in your path, create a directory and stuff you WMA's there. Then open `irb` and run the following Ruby command: + + :::ruby + ext = ".wma" + Dir.glob("*#{ext}").each {|f| m = f.gsub(ext, '.mp3'); `ffmpeg -i '#{f}' -ab 192k -ac 2 -ar 44100 '#{m}'` } + +When done, you'll find the WMA files converted to MP3 (having the same filename, except for the extention). + +Happy transcoding! + +Note: If you need to add ID3 tags to your files, I can highly recommend [MusicBrainz Picard][picard]. It can fingerprint your music and find the proper data online. It's free! + +_Update: Added a separte `ext` variable, this works great for converting FLAC to MP3 as well! Just use `ext = '.flac'`._ + +[picard]: http://musicbrainz.org/doc/MusicBrainz_Picard diff --git a/content/posts/2010-09-28-precompile-sass-to-css-for-deployment-to-heroku.md b/content/posts/2010-09-28-precompile-sass-to-css-for-deployment-to-heroku.md new file mode 100644 index 0000000..9188736 --- /dev/null +++ b/content/posts/2010-09-28-precompile-sass-to-css-for-deployment-to-heroku.md @@ -0,0 +1,67 @@ +--- +title: "Precompile SASS to CSS for deployment to Heroku" +kind: article +slug: precompile-sass-to-css-for-deployment-to-heroku +created_at: 2010-09-28 +tags: + - git + - haml + - sass + - heroku +--- +If you have deployed apps to Heroku you know that you cannot write to the read-only file system that Heroku offers. For file uploads you have to use some storage provider like Amazon S3 or RackSpace CloudFiles. + +Now, if your application (I'm assuming you're already on Rails 3), is using Haml + Sass, you're in some trouble. Sass is set to generate CSS files on the fly and save them to `public/stylesheets` so the can be served like static content. On Heroku, that is not possible. + +Luckily, there are a few solutions to this problem. I'll describe one of them. +~ + +**Use the Force (of git)** + +In my solution I'm using the power of git to generate the necessary CSS files and commit them automatically. Here's how it works. + +In a normal situation the Sass plugin will compile the SASS or SCSS files in `public/stylesheets/sass/*.scss` and store the generated CSC files in `public/stylesheets`. What we need to do is generate those CSS files by hand and commit them just like other versioned files in our repository. To do this, you need to write a `pre-commit` hook for git. This sounds more difficult than it really is. + +Here's the `pre-commit` hook I'm currently using to take all `public/stylesheets/sass/*.scss` files and store the resulting CSS files in `public/stylesheets`. + + :::ruby + #!/usr/bin/env ruby + + Dir['public/stylesheets/**/*.scss'].each do |sass| + basename = sass.gsub(/public\/stylesheets\/sass\//, '').gsub(/\.scss$/, '') + next if basename.match(/^_/) # skip includes + css = "public/stylesheets/#{basename}.css" + puts "Compiling #{sass} -> #{css}" + system "sass #{sass} #{css}" + system "git add #{css}" + end + +Store the above Ruby script in `.git/hooks/pre-commit`, then give is execute permissions with `chmod 755 .git/hooks/pre-commit`. + +This script will find all `*.scss` files and save their `*.css` equivalents. Then it also stages those files for the commit. Since this is a `pre-commit` script, the scenario is like this: + + 1. You stage your files to commit, like usual and run the `git commit` command. + 2. Before git makes the actual commit, it runs the `pre-commit` script, which generates the necessare CSS files. You'll a message like `Compiling public/stylesheets/sass/app.scss -> public/stylesheets/app.css`. + 3. With any changes to the CSS stages, your commit is made. + +**Won't this spam a lot of CSS commits?** + +No, it won't. Git is smart enough to see there are not changes to the content of the CSS file. + +**Does it work?** + +Yes, it works. The generated CSS files are deployed to Heroku like normal, static CSS files and will be served as such. + +**One more thing...** + +By default, Sass is set to generate CSS files when needed. Since the CSS file is already there Sass *probably* won't try to generate it again in production. But, it might try so anyway and cause an exception. + +To prevent Sass for generating CSS in production completely, add the following line to your `config/environments/production.rb`. + + :::ruby + Sass::Plugin.options[:never_update] = true + +**Notes** + +There is one problem with this approach. If you have multiple developers or machines you work on, each and every one must have this `pre-commit` script installed to make it work. My advise would be to include the script in your project's doc directory. + diff --git a/content/posts/2010-10-02-firefly-110-adds-qr-codes-for-your-shortened-urls.md b/content/posts/2010-10-02-firefly-110-adds-qr-codes-for-your-shortened-urls.md new file mode 100644 index 0000000..a562ddf --- /dev/null +++ b/content/posts/2010-10-02-firefly-110-adds-qr-codes-for-your-shortened-urls.md @@ -0,0 +1,26 @@ +--- +title: "Firefly 1.1.0 adds QR Codes for your shortened URLs" +kind: article +slug: firefly-110-adds-qr-codes-for-your-shortened-urls +created_at: 2010-10-02 +tags: + - Ruby + - urls + - firefly + - qrcodes +--- +Example QR Code + +I just pushed Firefly 1.1.0 [(code)][sources] to [Rubygems][rubygems] containing a very nice new feature: QR Codes. + +Why would you care? Well, almost anything can scan QR Codes nowadays. Simple add `.png` to the end of your shortened URL and you'll +get a nice QR Code that you can print or embed on somewhere on the web. Give it a try: [http://aj.gs/2Y.png][qrcode] + +Simply scan the image and it will yield your short URL, ready for use. + +Updating your current Firefly installation is easy, simply run `gem install firefly -v1.1.0` and restart your server. + +[sources]: http://github.com/ariejan/firefly/tree/v1.1.0 +[rubygems]: http://rubygems.org/gems/firefly +[qrcode]: http://aj.gs/2Y.png +~ diff --git a/content/posts/2010-10-11-setup-a-ubuntu-vpn-server.md b/content/posts/2010-10-11-setup-a-ubuntu-vpn-server.md new file mode 100644 index 0000000..0eb5db7 --- /dev/null +++ b/content/posts/2010-10-11-setup-a-ubuntu-vpn-server.md @@ -0,0 +1,47 @@ +--- +title: "Setup a Ubuntu VPN server" +kind: article +slug: setup-a-ubuntu-vpn-server +created_at: 2010-10-11 +tags: + - Linux + - Ubuntu + - vpn +--- +I recently installed Ubuntu Linux on a home server (I hate that word, but it +best describes what it is, so). Anyway, I'd like to be able to create a VPN +network between my home server and my MacbookPro, which might be anywhere in +the world. + +This first part of the tutorial describes how to setup a VPN server in Ubuntu. +~ +First, install the `pptpd` package. `pptpd` offers a `PPTP`-type VPN which is +supported by Microsoft and other network vendors. This is also the easiest to +setup. + + sudo apt-get install pptpd + +Next up, edit `/etc/pptpd.conf` with `sudo vi /etc/pptp.conf`. At the bottom add the following lines: + + localip 192.168.1.10 + remoteip 192.168.1.230-239 + +Here `localip` references the IP of my home server. The `remoteip` variable +configures which IPs remote clients may use when the connect through VPN to my +network. In this case I reserve 10 IP address: 192.168.1.230 through +192.168.1.239. + +With that out of the way, let's tell `PPTP` which users to allow. Edit +`/etc/ppp/chap-secrets`, just like you did before using `sudo`. + + # client server secret IP Address + ariejan pptpd somepassword * + +That's all! Yes, seriously. Just restart the `pptpd` daemon and you're good to +go. + +Now, go and watch my [PPTP VPN setup for Mac OS X Snow Leopard][1] + +_Note: This was tested on Ubuntu 10.10-amd64_ + +[1]: http://ariejan.net/2010/10/12/setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard/ diff --git a/content/posts/2010-10-12-sentestcase-xcbuildlogcommandinvocationsection-error-in-xcode-32.md b/content/posts/2010-10-12-sentestcase-xcbuildlogcommandinvocationsection-error-in-xcode-32.md new file mode 100644 index 0000000..cb7380e --- /dev/null +++ b/content/posts/2010-10-12-sentestcase-xcbuildlogcommandinvocationsection-error-in-xcode-32.md @@ -0,0 +1,28 @@ +--- +title: "SenTestCase: XCBuildLogCommandInvocationSection error in XCode 3.2" +kind: article +slug: sentestcase-xcbuildlogcommandinvocationsection-error-in-xcode-32 +created_at: 2010-10-12 +tags: + - iphone + - sdk + - xcode +--- +Today I wanted to add some unit tests to an iPhone project I'm working on. I came across the following error when trying to run my tests: + + An internal error occurred when handling command output: -[XCBuildLogCommandInvocationSection setTestsPassedString:]: unrecognized selector sent to instance 0x2017a22a0 + An internal error occurred when handling command output: -[XCBuildLogCommandInvocationSectionRecorder endMarker]: unrecognized selector sent to instance 0x201719b60 +~ +It appears there's a bug in XCode somewhere that prevents unit tests to run +(yeah, I know, there should be tests for that at Apple). Anyway, you can +easily fix this. + +Double-click on _Run Script_ for your testing target. Replace + + "${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests" + +with + + "${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests" 1> /tmp/RunUnitTests.out + +Now, build your test target again and you should be good to go. diff --git a/content/posts/2010-10-12-setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard.md b/content/posts/2010-10-12-setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard.md new file mode 100644 index 0000000..a224fd6 --- /dev/null +++ b/content/posts/2010-10-12-setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard.md @@ -0,0 +1,24 @@ +--- +title: "Setup a PPTP VPN connection on Mac OS X Snow Leopard" +kind: article +slug: setup-a-pptp-vpn-connection-on-mac-os-x-snow-leopard +created_at: 2010-10-12 +tags: + - osx + - mac + - vpn + - snow leopard +--- +In my [previous tutorial][1] I explained how to setup a PPTP based VPN server on +Ubuntu. Now I'll show you how to configure Mac OS X Snow Leopard to use that +VPN connection. + +[1]: http://ariejan.net/2010/10/11/setup-a-ubuntu-vpn-server/ +~ +Just watch the movie. + +

    How to setup a PPTP VPN connection from Mac OS X 10.6 from Ariejan de Vroom on Vimeo.

    + +_Note: You may open port 1723 (TCP) on your router to +allow you to connect to your VPN server from the Internets. In that case, use +your public IP Address._ diff --git a/content/posts/2010-10-26-clear-your-mysql-password.md b/content/posts/2010-10-26-clear-your-mysql-password.md new file mode 100644 index 0000000..91b4dd0 --- /dev/null +++ b/content/posts/2010-10-26-clear-your-mysql-password.md @@ -0,0 +1,31 @@ +--- +title: "Clear your MySQL password" +kind: article +slug: clear-your-mysql-password +created_at: 2010-10-26 +tags: + - MySQL + - development + - password +--- +Most people need their MySQL database protected with at least a decent password. I agree, but in development this often causes conflicts - and I prefer to work with my MySQL datbase without all the password-hassle. + +On Ubuntu I recently installed MySQL and set a password. Here's how to remove that password so you can skip all the password stuff during development. +~ +First, connect to MySQL and check what permissions are currently set: + + $ mysql -u root -p + use mysql; + select Host, User, Password from user; + +You'll probably see three entries for the `root` user: `localhost`, `127.0.0.1` and your hostname like `hostname`. To clear the password for the root user issue the following query: + + :::sql + update user set password = '' where user = 'root'; + +Optionally, you may only want to reset the password for `localhost`: + + :::sql + update user set password = '' where user = 'root' and host = 'localhost'; + +Keep in mind that using no MySQL password is insecure. Always protected your MySQL database with at least a strong password in production. diff --git a/content/posts/2010-11-25-hide-last-login-on-bash-login.md b/content/posts/2010-11-25-hide-last-login-on-bash-login.md new file mode 100644 index 0000000..f9706f8 --- /dev/null +++ b/content/posts/2010-11-25-hide-last-login-on-bash-login.md @@ -0,0 +1,20 @@ +--- +title: "Hide \"Last login:\" on bash login" +kind: article +slug: hide-last-login-on-bash-login +created_at: 2010-11-25 +tags: + - BASH + - MacOSX +--- +Everytime I open a new Terminal on my Mac, I get a line like this: + + Last login: Thu Nov 25 09:07:55 on ttys004 + +This annoys me. I don't care when I last opened a local Terminal. +~ +To hide this "Last login" message when logging in to bash you need to create a file in your homedirectory. + + touch ~/.hushlogin + +With this `.hushlogin` file in place you won't see the "Last login" message and go directly to your prompt, where you want to be. diff --git a/content/posts/2010-12-15-why-did-errormessagesfor-disappear-from-rails-3.md b/content/posts/2010-12-15-why-did-errormessagesfor-disappear-from-rails-3.md new file mode 100644 index 0000000..341b89e --- /dev/null +++ b/content/posts/2010-12-15-why-did-errormessagesfor-disappear-from-rails-3.md @@ -0,0 +1,34 @@ +--- +title: "Why did error_messages_for disappear from Rails 3?" +kind: article +slug: why-did-errormessagesfor-disappear-from-rails-3 +created_at: 2010-12-15 +tags: + - rails3 +--- +Today I learned that `error_messages_for` has disappear from Rails 3. When I tried using it I got the following deprecation warning: + + DEPRECATION WARNING: form.error_messages was removed from Rails and is now available as a plugin. + +What happened? Why was this pulled from Rails 3? +~ +The reason `error_messages_for` was pulled from Rails 3 is a new guideline that says that nothing in Rails Core should dictate the look and feel of an app. + +Okay, that's all well and good. How do you fix this problem? There are two ways. The first is to implement your own error handling. A HAML snippet: + + + :::ruby + - if @post.errors.any? + .errors + %h2 There was a problem saving this post + + %ul + - @post.errors.full_messages.each do |msg| + %li= msg + +This is by far the best way to go about it. However, if you have a current Rails 2 app you're upgrading, writing your own error handling may be rather difficult or time consuming. In that case you can install a plugin that restores the original functionality of `error_messages_for`. + + rails plugin install git://github.com/rails/dynamic_form.git + +Just make sure to restart your server. + diff --git a/content/posts/2010-12-24-public-readable-amazon-s3-bucket-policy.md b/content/posts/2010-12-24-public-readable-amazon-s3-bucket-policy.md new file mode 100644 index 0000000..5819f4b --- /dev/null +++ b/content/posts/2010-12-24-public-readable-amazon-s3-bucket-policy.md @@ -0,0 +1,36 @@ +--- +title: "Public Readable Amazon S3 Bucket Policy" +kind: article +slug: public-readable-amazon-s3-bucket-policy +created_at: 2010-12-24 +tags: + - amazon + - s3 + - cloudfront + - bucket policy +--- +Amazon S3 allows you to set per-file permissions to grant read and/or write access. This is nice, but sometimes you just want to share your whole bucket with the world. + +Luckily, Amazon features _bucket policies_, which allow you to define permissions for an entire bucket. +~ +This example will give _read_ access to _Everyone_ on _all files_ in your bucket. + + :::json + { + "Version":"2008-10-17", + "Statement":[{ + "Sid":"AllowPublicRead", + "Effect":"Allow", + "Principal": { + "AWS": "*" + }, + "Action":["s3:GetObject"], + "Resource":["arn:aws:s3:::bucket/*" + ] + } + ] + } + +**Make sure you replace `bucket` in `arn:aws:s3:::bucket/*` with your bucket name.** + +After setting this bucket policy (see 'Bucket -> Properties -> Add Bucket Policy'), all your files will be publicly readable. \ No newline at end of file diff --git a/content/posts/2010-12-31-now-powered-by-heroku.md b/content/posts/2010-12-31-now-powered-by-heroku.md new file mode 100644 index 0000000..c18a387 --- /dev/null +++ b/content/posts/2010-12-31-now-powered-by-heroku.md @@ -0,0 +1,23 @@ +--- +title: "Now powered by Heroku" +kind: article +slug: now-powered-by-heroku +created_at: 2010-12-31 +tags: + - toto + - heroku + - hosting + - dorothy +--- +This is just a quick note to let everyone know *Ariejan.net* is now proudly hosted by [Heroku][1]. Since this site is powered by a [toto][2] (a rack-based blog engine) and no database, Heroku is the perfect hosting solution. + +[1]: http://heroku.com +[2]: https://github.com/cloudhead/toto +~ + +All articles are simple text files (with some YAML front-matter to specify title, tags, etc.) and are kept under Git version control. Deploying *Ariejan.net* is a simple `git push` to Heroku. + +Want to run your own toto-powered blog on Heroku? Checkout [Dorothy][3] ([code][4]) to get started in "10 seconds". + +[3]: http://cloudhead.io/toto +[4]: https://github.com/cloudhead/dorothy \ No newline at end of file diff --git a/content/posts/2011-01-01-rake-task-to-sync-your-assets-to-amazon-s3cloudfront.md b/content/posts/2011-01-01-rake-task-to-sync-your-assets-to-amazon-s3cloudfront.md new file mode 100644 index 0000000..d37549f --- /dev/null +++ b/content/posts/2011-01-01-rake-task-to-sync-your-assets-to-amazon-s3cloudfront.md @@ -0,0 +1,134 @@ +--- +title: "Rake task to sync your assets to Amazon S3/Cloudfront" +kind: article +slug: rake-task-to-sync-your-assets-to-amazon-s3cloudfront +created_at: 2011-01-01 +tags: + - amazon + - s3 + - cloudfront + - hosting + - cloud +--- +With my move to Heroku I felt bad about having Heroku's app servers serve static content for me. It's not really a problem, but I just like to use the best tool available for the job. + +Because _Ariejan.net_ is a rack app, it has a `public` directory with all static assets in once place. There are, however, a few problems that need adressing. +~ + +These are the problems I want to resolve: + +#### Keep my S3 Bucket in sync with my public directory #### + +The first and foremost is to keep my S3 bucket in sync with the content of `public`. I don't care about file deletions, but I do care about new and updated files. Those should be synced with every deployment to S3. + +#### Don't re-upload the entire public directory with every deployment #### + +Over time the size of `public` has grown. New images are added all the time. I don't want to re-upload them with every deployment. So, my sync script must be smart enough to not upload unchanged files. + +#### Hook the S3 sync into my current deployment rake task #### + +My current rake deploy task should be able to call `assets:deploy` or something to trigger an asset sync. + +#### Minimal configuration #### + +I don't want to configure anything, if possible. + +### The script ### + +Well, this is the rake task I currently use: + + :::ruby + require 's3' + require 'digest/md5' + require 'mime/types' + + ## These are some constants to keep track of my S3 credentials and + ## bucket name. Nothing fancy here. + + AWS_ACCESS_KEY_ID = "xxxxx" + AWS_SECRET_ACCESS_KEY = "yyyyy" + AWS_BUCKET = "my_bucket" + + + ## This defines the rake task `assets:deploy`. + namespace :assets do + desc "Deploy all assets in public/**/* to S3/Cloudfront" + task :deploy, :env, :branch do |t, args| + + ## Minify all CSS files + Rake::Task[:minify].execute + + ## Use the `s3` gem to connect my bucket + puts "== Uploading assets to S3/Cloudfront" + + service = S3::Service.new( + :access_key_id => AWS_ACCESS_KEY_ID, + :secret_access_key => AWS_SECRET_ACCESS_KEY) + bucket = service.buckets.find(AWS_BUCKET) + + ## Needed to show progress + STDOUT.sync = true + + ## Find all files (recursively) in ./public and process them. + Dir.glob("public/**/*").each do |file| + + ## Only upload files, we're not interested in directories + if File.file?(file) + + ## Slash 'public/' from the filename for use on S3 + remote_file = file.gsub("public/", "") + + ## Try to find the remote_file, an error is thrown when no + ## such file can be found, that's okay. + begin + obj = bucket.objects.find_first(remote_file) + rescue + obj = nil + end + + ## If the object does not exist, or if the MD5 Hash / etag of the + ## file has changed, upload it. + if !obj || (obj.etag != Digest::MD5.hexdigest(File.read(file))) + print "U" + + ## Simply create a new object, write the content and set the proper + ## mime-type. `obj.save` will upload and store the file to S3. + obj = bucket.objects.build(remote_file) + obj.content = open(file) + obj.content_type = MIME::Types.type_for(file).to_s + obj.save + else + print "." + end + end + end + STDOUT.sync = false # Done with progress output. + + puts + puts "== Done syncing assets" + end + end + +This rake task is hooked into my `rake deploy:production` script and generates the following output (I added a new file just to show you what happens.) + + $ rake deploy:production + (in /Users/ariejan/Code/Sites/ariejannet) + Deploying master to production + == Minifying CSS + == Done + == Uploading assets to S3/Cloudfront + ......................................U......... + == Done syncing assets + + Updating ariejannet-production with branch master + Counting objects: 40, done. + Delta compression using up to 4 threads. + Compressing objects: 100% (27/27), done. + Writing objects: 100% (30/30), 4.24 KiB, done. + Total 30 (delta 17), reused 0 (delta 0) + + -----> Heroku receiving push + +### Conclusion ### + +It's very easy to write your own S3 sync script. My version has still has some issues/missing features that I may or may not add at some later time. There's no support for file deletions and error handling is very poor at this time. Also, `public` is still under version control (where I want it), and is pushed to Heroku. This is non-sense, because most of the assets in `public` are not used (except `robots.txt` and `favicon.ico`) diff --git a/content/posts/2011-02-01-hot-firefly-130-url-shortener-released.md b/content/posts/2011-02-01-hot-firefly-130-url-shortener-released.md new file mode 100644 index 0000000..d47b32e --- /dev/null +++ b/content/posts/2011-02-01-hot-firefly-130-url-shortener-released.md @@ -0,0 +1,50 @@ +--- +title: "Hot: Firefly 1.3.0 URL Shortener released" +kind: article +slug: hot-firefly-130-url-shortener-released +created_at: 2011-02-01 +tags: + - firefly +--- +I've been doing some work on Firefly lately and tonight I've released +version 1.3.0. If you're using Firefly it's recommended you upgrade to +the latest and greatest version. +~ +For those who missed it: Firefly is a simple URL shortener gem +that can be used to host your own personal (or corporate) URL shortner +on your own domain. Since Firefly is rack-based, it's easy to deploy to +Heroku or other ruby hosting providers. + +### What's new + +Since version 1.1.0 (released october 2010) these features have been +added. I'll just summarize those and ignore the 1.2.x release I made. + + * Make Barby optional to save some gems for those of us who don't need QR code generation. [mboeh] + * Added a CodeFactory to generate unique short codes (and prevent collisions with custom short codes) [ariejan] + * Support for specifying one's own preferred short codes via the API. Also, invalid URL creations return a 422 code, not 200. [mboeh] + * Added GUI support for custom short codes. [ariejan] + * Improved error handling / reporting [ariejan] + * Minor tweaks to the web GUI [ariejan] + * Use dm-mysql-adapater as default, dm-sqlite-adapter is optional [ariejan] + +### How to upgrade + +**IMPORTANT**: Always make a backup of your data before you upgrade. + +Simple install the latest gem version and restart your server. + + gem install firefly -v1.3.0 + +Then restart your server. DataMapper will take care of migrating your +database for you. + +**NOTE**: Firefly is now using MySQL by default. If you were using +sqlite3 before, please install the appropriate DataMapper adapater: `gem +install dm-sqlite-adapter` + +### Code, Issues, Forking + +Want to fork? [http://github.com/ariejan/firefly][1] + +[1]: http://github.com/ariejan/firefly diff --git a/content/posts/2011-02-07-using-your-firefly-url-shortener-with-twitter-for-iphone.md b/content/posts/2011-02-07-using-your-firefly-url-shortener-with-twitter-for-iphone.md new file mode 100644 index 0000000..ce04b76 --- /dev/null +++ b/content/posts/2011-02-07-using-your-firefly-url-shortener-with-twitter-for-iphone.md @@ -0,0 +1,43 @@ +--- +title: "Using your Firefly URL Shortener with Twitter for iPhone" +kind: article +slug: using-your-firefly-url-shortener-with-twitter-for-iphone +created_at: 2011-02-07 +tags: + - twitter + - firefly + - url shortener +--- +The Twitter for iPhone app ([itunes][1]) can be configured to use your very +own Firefly URL shortener! Here's how. + + [1]: http://itunes.apple.com/nl/app/twitter/id333903271?mt=8 +~ +Custom URL server... + +Actually, it's quite simple. First, [setup your own Firefly instance][2], +possibly [using Heroku][3]. + +When that's done, simple open Twitter for iPhone, then follow this path: + + * Tap the three dots + * Open _"Accounts & Settings"_ + * Tap the _"Settings"_ button at the bottom + * Open _"Services"_ + * Choose _"URL Shortening"_ + * Then pick _"Custom..."_ + +All right, you're now ready to enter your magic URL. Simply replace the +`YOUR_DOMAIN` and `YOUR_API_KEY` placeholders with your actual +domain name and API key (you can find them in your `config.ru`) + + http://YOUR_DOMAIN/api/add?api_key=YOUR_API_KEY&url=%@ + +Then hit _"Save"_ and you're set! Twitter for iPhone will now +automatically use your Firefly instance to shorten URLs! + +Looking for the [Firefly source code][4]? + + [2]: https://github.com/ariejan/firefly#readme + [3]: http://ariejan.net/2010/06/06/setup-your-own-firefly-url-shortener-in-25-minutes/ + [4]: https://github.com/ariejan/firefly diff --git a/content/posts/2011-02-11-narf-a-ruby-micro-test-framework.md b/content/posts/2011-02-11-narf-a-ruby-micro-test-framework.md new file mode 100644 index 0000000..4c43cd8 --- /dev/null +++ b/content/posts/2011-02-11-narf-a-ruby-micro-test-framework.md @@ -0,0 +1,89 @@ +--- +title: "Narf: A Ruby Micro Test Framework" +kind: article +slug: narf-a-ruby-micro-test-framework +created_at: 2011-02-11 +tags: + - Ruby + - Test + - testing + - bdd + - tdd + - agile +--- +I'm a happy user of RSpec, Cucumber and sometimes Steak. Great tools to +write specs and features and prove my application does what it's +supposed to do. But sometimes I have the need for something more _light +weight_. +~ +For example, sometimes I need to write a single ruby method. Just something +'quick' to import a file, convert some data or whatever. Being a good +citizen I want to test that method. + +Using RSpec or Cucumber here just seems wrong here. So, I've implemented +my own _Ruby Micro Test Framework_: *Narf* + + :::ruby + def assert(message, &block) + puts "#{"%6s" % ((yield || false) ? ' PASS' : '! FAIL')} - #{message}" + end + +(Yes, that's it.) + +With this simple method, added to your ruby file, you can test your +method. And example: + +Let's say you're going to write a method that counts how often the word +'ruby' occurs in a given String: + + :::ruby + def count_rubies(text) + # TODO + end + + #--- Ruby Micro Test Framework + def assert(message, &block) + puts "#{"%6s" % ((yield || false) ? ' PASS' : '! FAIL')} - #{message}" + end + #--- + + #--- Tests + assert "Count zero rubies" do + count_rubies("this is Sparta!") == 0 + end + + assert "Count one ruby" do + count_rubies("This is one ruby") == 1 + end + + assert "Count one RuBy" do + count_rubies("This is one RuBy") == 1 + end + +Now, simple save this file and feed it to ruby: + + $ ruby my_method.rb + ! FAIL - Count zero rubies + ! FAIL - Count one ruby + ! FAIL - Count one RuBy + +Now, implement your method... + + :::ruby + def count_rubies(text) + text.match(/(ruby)/i).size + end + +And re-run your tests: + + $ ruby my_method.rb + PASS - Count zero rubies + PASS - Count one ruby + PASS - Count one RuBy + +So with the addition of just a single method you can fully TDD/BDD your +single method Ruby code. Pretty need, huh? + +[Need codez?][1] + + [1]: http://github.com/ariejan/narf diff --git a/content/posts/2011-03-27-rails-3-devise-uploadify-no-flash-session-hacks.md b/content/posts/2011-03-27-rails-3-devise-uploadify-no-flash-session-hacks.md new file mode 100644 index 0000000..26fc343 --- /dev/null +++ b/content/posts/2011-03-27-rails-3-devise-uploadify-no-flash-session-hacks.md @@ -0,0 +1,106 @@ +--- +title: "Rails 3 + Devise + Uploadify = No Flash Session Hacks" +kind: article +slug: rails-3-devise-uploadify-no-flash-session-hacks +created_at: 2011-03-27 +tags: + - Rails + - rails3 + - devise + - uploadify + - flash +--- +Uploadify is a great project to provide file uploads in your project. +The problem is, it's written in flash. + +Besides the point that _it is flash_, there's something else that has +been bothering me a lot: **sessions**. +~ +The problem is like this. When a browsers opens a connection to your +Rails app, it has a session. Normally, session information is stored a +cookie that is sent with every request. This session also contains +information needed for you to stay logged in as a particular user. + +If you embed Flash on your site and have it communicate with your Rails +application, it will start a new session. Basically, Flash just acts +as a whole other browser. + +Because of this behaviour, when a logged in user uploads files with +Uploadify, the uploads will appear to come from an unauthenticated +user. There is no session information provided by flash to identify +that a user is signed in. + +**The _old_ solution** + +The old solution I've found around the web is to work around this +session problem by forcing flash to send the browser's session +information. This solution also requires you to add a piece of _rack +middleware_ to your app that takes any requests from a flash client and +hacks the session to look like it came from the browser. + +Besides the fact that I couldn't get this hack to work, it still is +a big hack. So, what to do? + +**A word on devise** + +I happen to use Devise in my app to authenticate users. As you may know, +Devise is _very_ complete. It includes everything ranging from sign up, +forgotten passwords and even account locking. It also features _token +authentication_. + +Token authentication means that a user can authenticate himself by +providing a unique token, in most cases a string of random characters. + +A common use case for this token authentication is to provide users with +a _secret link_ to an RSS feed, or to allow quick access to the +application from link sent in an email. You click the link and you're +automagically logged in. + +**The _new_ solution** + +So, this token authentication got me thinking. Instead of sending an +encoded string with session information to flash, which in turn sends it +to the server, which in turn hacks it into an actual session, I could +just send the user's authentication token along! No sever-side hacks +required - it's all built in into devise already! + +Let me show you. First check that your (devise-powered) user has an +authentitication token: + + :::ruby + @user.authentication_token + => "4R2bzzQRdoT_iz-ND4Bb" + +In case your `authentication_token` is nil, you should generate one with +`@user.reset_authentication_token!` + +The next step is to tell Flash to use this authentication token in its +request to the server (while uploading files). Nothing fancy here +either. Not that this is a snippet from JavaScript, embedded in a HAML +template: + + :::javascript + $('#image_file').uploadify({ + // I omitted all other config options, since they're not relevant. + 'script' : '#{images_path(:auth_token => current_user.authentication_token, :format => :json)}' + ) + +Rails will generate a URL like this: +`/images.json?auth_token=4R2bzzQRdoT_iz-ND4Bb`. + +The final step is to protect your `ImagesController#create` action with +devise. + + :::ruby + class ImagesController < ApplicationController + before_filter :authenticate_user! + + def create + # Handle your upload + end + end + +That's all. You dont' even need to add rack middleware or hack Uploadify +to allow an authenticated devise user to upload images through flash. + +Easy, huh? diff --git a/content/posts/2011-04-04-rake-with-namespaces-and-default-tasks.md b/content/posts/2011-04-04-rake-with-namespaces-and-default-tasks.md new file mode 100644 index 0000000..f603b0a --- /dev/null +++ b/content/posts/2011-04-04-rake-with-namespaces-and-default-tasks.md @@ -0,0 +1,53 @@ +--- +title: "Rake with namespaces and default tasks" +kind: article +slug: rake-with-namespaces-and-default-tasks +created_at: 2011-04-04 +tags: + - Ruby + - rake +--- +Rake is an awesome tool to automate tasks for your Ruby (or Rails) +application. In this short article I'll show you how to use namespaces +and set default tasks for them. +~ +Let me first tell you what I want to accomplish. I have a Rails +application that needs to be cleaned up occasionally. Essetially we +delete old data from the database. + +There are several models that each implement a `cleanup!` method which +takes care of cleaning up data. Now all I need is a few rake tasks to +kick off the clean up process. + +This is what I'd like: + + rake cleanup # Cleanup everything + rake cleanup:clicks # Aggregate click stats + rake cleanup:logs # Clean old logs + +Here's what I put in `lib/tasks/cleanup.rake`: + + :::ruby + namespace :cleanup do + desc "Aggregate click stats" + task :clicks => :environment do + Click.cleanup! + end + + desc "Clean old logs" + task :logs => :environment do + Log.cleanup! + end + + task :all => [:clicks, :logs] + end + + desck "Cleanup everything" + task :cleanup => 'cleanup:all' + +Notice that the `cleanup:all` task does not have a description. Without +it, it won't show up when you do a `rake -T` to view available tasks. + +The task `cleanup` has the same name as the namespace and calls the +(undocumentend) `cleanup:all` task, kicking off the entire cleanup +process. diff --git a/content/posts/2011-06-10-vows-and-coffeescript.md b/content/posts/2011-06-10-vows-and-coffeescript.md new file mode 100644 index 0000000..6f5c400 --- /dev/null +++ b/content/posts/2011-06-10-vows-and-coffeescript.md @@ -0,0 +1,90 @@ +--- +title: "Vows and CoffeeScript" +kind: article +slug: vows-and-coffeescript +created_at: 2011-06-10 +tags: + - javascript + - bdd + - nodejs + - coffee-script + - vows + - v8 +--- +CoffeeScript is a really nice way to write JavaScript code. Combined +with NodeJS you are empowered by a very fast platform to develop +server-side applications. Of course, you want to test these apps as well. [Vows][1] is really +great way to do this. Unfortunately it's not straight forward (yet) to +set up NodeJS + CoffeeScript + Vows. + +[1]: http://vowsjs.org +~ +First off, make sure you have CoffeeScript and Vows installed. Here I +install them _globally_ so you can use the `coffee` and `vows` command +line utilities. + + npm install -g coffee-script + npm install -g vows + +Next up, in your product directory, create a directory named `test`. +Here we'll create (classic) example: `division-test.coffee` + + :::coffeescript + vows = require 'vows' + assert = require 'assert' + + vows + .describe('Division by zero') + .addBatch + 'when dividing a number by zero': + topic: -> 42/ 0 + + 'we get Infinity': (topic) -> + assert.equal topic, Infinity + + 'but when dividing zero by zero': + topic: -> 0 / 0 + + 'we get a value which': + 'is not a number': (topic) -> + assert.isNaN topic + + 'is not equal to itself': (topic) -> + assert.notEqual topic, topic + + .export(module) + +I'm not going to explain the intimate details of Vows here, suffice it +to say that you calculate a value and store it in `topic`. Then you +perform a set of expectations. + +The magic part is the last line, `.export(module)`. In all other +examples you'll see the last command is `.run()`. This `run()` command +runs your vows immediately. + +When using coffee-script, you don't want to directly run you vows, but +gather them all together, convert them to JavaScript and then have vows +run them. + +With the `test/division-test.coffee` saved, try running `vows` from +your console. Here's the output from `vows --spec`: + + λ vows --spec + + ♢ Division by zero + + when dividing a number by zero + ✓ we get Infinity + but when dividing zero by zero we get a value which + ✓ is not a number + ✓ is not equal to itself + + ✓ OK » 3 honored (0.002s) + +Another great tip is `vows -w`. This will keep vows running and monitor +your test files for changes. When a file changes, it will re-run your +vows for you. + +Happy testing! + +_Yes, I know there are issues with Coderay parsing UTF-8 characters._ diff --git a/content/posts/2011-06-23-crowd-sourcing-your-bitcoin-mining.md b/content/posts/2011-06-23-crowd-sourcing-your-bitcoin-mining.md new file mode 100644 index 0000000..a064fea --- /dev/null +++ b/content/posts/2011-06-23-crowd-sourcing-your-bitcoin-mining.md @@ -0,0 +1,99 @@ +--- +title: "Crowd sourcing your BitCoin mining" +kind: article +slug: crowd-sourcing-your-bitcoin-mining +created_at: 2011-06-23 +tags: + - bitcoin + - btc + - mining + - crowd-sourcing +--- +[BitCoin][1] is a hip new currency using peer-to-peer networking to process transactions. You can either buy it from others, or _mine_ it by solving math puzzles. Would it be possible to crowd source the _mining_ process to visitors of your website? +[1]: http://bitcoin.org +~ +### What's BitCoin anyway? +
    + +
    + +BitCoin (BTC) is not distributed by a central bank, but is generated by its users. In order to _mine_ BTC, your computer must solve math puzzles. When you solve the puzzle, you are awarded 50BTC. How difficult the puzzles are and how big the reward is, is all determined by the network itself - it cannot be influenced by a single entity or person. + +So, let's turn on that computer and start generating BitCoin! With a current exchange rate between $8 - $30 _per_ BitCoin, gaining 50 BTC would be quite lucrative. + +So, how long would it take to solve that math puzzle and score 50BTC, or $400 - $1500? + +Given the current difficulty (how difficult is the puzzle to solve) of _877226_ and a hash rate (the speed at which you're trying to solve the puzzle) of 6 mHashes/s you can calculate how long it would take on average before you are rewarded 50BTC. + +Using a [calculator][2] I can compute that with my current AMD Phenom II 1055T X6 (6 cores @ 2.8Ghz) it would take an average of about 20 years to generated those 50BTC. That's 0.007 BTC a day, which translates to $0.056 - $0.21 a day. That doesn't even cover the power bill for running your computer each day. + +### Can we do better than a 6 core CPU? + +Yes, we can! My system is also equipped with an ATI Radeon HD 6870 video card. I love playing games, you know ;-). The thing is, a video card is basically a very large and very fast calculator. It can perform simple operations, like the hashing required for BitCoin mining, very vast. It can do 1120 hashes *_at the same time_*. Yes, that is in parallel. + +Where the CPU could do 6 mHashes/s my GPU can do about 270 mHashes/s. Yes, my GPU is about 45 times faster at calculating hashes than my CPU. + +But, what does that mean for the time it takes to get those 50BTC? It will take 161.5 days on average. Yielding about $2.48 - $15.50 per day. That's more like it! Still, not worth leaving your day job for. + +### More power, please! + +It's possible to rig up a machine that uses even faster video cards to squeeze more mHashes/s out of it. Also, a system is not even limited to one video card, you can add up to two or four in the same computer! Imagine having four ATI Radeon 6870's. That's a whopping 1080 mHashes/s. + +1080 mHashes/s gives you an average of 40 days and 9 hours, which translates to about $9.90 - $37.14 per day. You could be making $1100 a month with that (excluding the cost for purchase and power) + +### How about The Cloud? + +You might wonder about the cloud. That big virtual pool of unlimited hardware. How about throwing some very big machines at this problem and get rich instantly. + +Well, it won't work. As we saw, CPUs are bad a generating those hashes and are thus not really a viable option for mining. + +Amazon does offer an instance type that provides two Nvidia Tesla GPUs. Unfortunately Nvidia cards don't perform well generating hashes. The cost of running one of those instances at about $50 a day. Because it performs worse than a single ATI Radeon HD 6870 you'll only lose money with this approach. + +### Team up! + +It's also possible to team up with others to mine BitCoin faster. It's been done before and the most famous example would be [Seti@Home][3]. + +Users run a program in the time their CPU is idle, contributing in the analysis of signals from space. If enough people contribute their spare CPU time, you'll get the processing power of multiple time the fastest super-computer in the world. + +Great, so let's do that for BitCoin! In the BitCoin world these are called _pools_. You can join a pool and try to solve the math puzzle together. When that happens, every participant gets a proportional share of the 50BTC for their work done. + +Imagine there are a hundred people with an ATI Radeon HD 6870 (thus doing equal amounts of work). This would result in 2.7 gHashes/s. 50 BTC would be mined in about 1 day and 14 hours. The result per day would be about the same as a single person mining on his own. + +The big difference is that when mining in a pool you get BitCoins on your balance much faster. After just a day an a half you have 0.5 BTC sitting in your account, yours to spend. A single person would have to wait 162 days for that to happen. + +### Use the Crowd, Luke! + +Now, this got me thinking. What if you could harness the power of a thousands or hundreds of thousands of CPUs, even for a short period of time? How fast would mining be? Would it be profitable? + +Then I noticed there is a website that offers a _web miner_. This is a Java applet that runs in the background of a web page. While a visitor is on your site, the applet will use spare CPU power from that users computer to generate hashes and effectively participate in a private pool. + +I ran an experiment on Ariejan.net during june 2011. I have implemented the web miner and generated BitCoin for a month using my visitors as (mostly unknowingly) test subjects. + +Ariejan.net gets between 55.000 - 65.000 page views each month. The average time per page is 40 seconds. This would yield me more than 700 hours of computing time each month. + +Using the web miner I got between 60 and 100 mHashes/s for an entire month. Doing all the mining on my own it would take 1 year and 2 months to generate 50 BTC. That's $0.75 - $2.84 per day on average. + +Because that would be too long, I participated in another pool, contributing the immense power of my websites visitors. During june you generated 1.98 BTC. About $15.85 - $59.40 when exchanged right now. + +In comparison, my ATI Radeon HD 6870 could have made between $75 and $280 in the same period of time. + +This is a nice chunk in the monthly revenue stream generated by Ariejan.net, but it would not be enough to dedicate time on writing articles on a more regular basis. + +Also, _stealing_ a visitors CPU time is ethically debatable. Personally I think it would be okay to ask a visitor to opt-in. Running the miner secretly in the background as I did for this test would not be acceptable. + +### Conclusion + +If you're in the business of mining BitCoin, crowd sourcing most likely is not an option for you. You probably don't have a web site big enough to generate enough CPU power. Using commodity hardware would yield the same or better results. + +I would be interested to see others continue this _research_. What would be the results: + + * if you asked users to opt-in only + * if you asked users to opt-out only + * on a site with 1+ million visitors + * if a miner were integrated in a game or piece of software + * if Google put a JavaScript miner on google.com that performed only 10 hashes/view. + +*Got an opinion, feel free to leave it below.* + +[2]: http://bitcoinx.com/profit/index.php +[3]: http://setiathome.berkeley.edu/ \ No newline at end of file diff --git a/content/posts/2011-06-24-git-what-files-were-changed-since-the-last-release.md b/content/posts/2011-06-24-git-what-files-were-changed-since-the-last-release.md new file mode 100644 index 0000000..f05217a --- /dev/null +++ b/content/posts/2011-06-24-git-what-files-were-changed-since-the-last-release.md @@ -0,0 +1,17 @@ +--- +title: "Git: What files were changed since the last release?" +kind: article +slug: git-what-files-were-changed-since-the-last-release +created_at: 2011-06-24 +tags: + - git + - log + - history +--- +Sometimes it handy to get a list out of `git log` that tells you which files were changed since your last release. It's not straight forward, but very doable with the help of `git log` and `grep`. +~ +Let's say you want to view all the changed files since the last tagged release, `v1.3.1`: + + git log --reverse --name-status HEAD...v1.3.1 | grep -e ^[MAD][[:space:]] + +As you're used to, this shows each files that *A*dded, *M*odified or *D*eleted. This command does not squash file changes. So it's possible for a file to first be added, the deleted, then added again and later modified. The `--reverse` option shows file changes historically, so the first file changed after the v1.3.1 release is shown first. \ No newline at end of file diff --git a/content/posts/2011-06-27-your-mac-slow-disable-spotlight-in-snow-leopard.md b/content/posts/2011-06-27-your-mac-slow-disable-spotlight-in-snow-leopard.md new file mode 100644 index 0000000..c45ba80 --- /dev/null +++ b/content/posts/2011-06-27-your-mac-slow-disable-spotlight-in-snow-leopard.md @@ -0,0 +1,45 @@ +--- +title: "Your Mac slow? Disable Spotlight in Snow Leopard" +kind: article +slug: your-mac-slow-disable-spotlight-in-snow-leopard +created_at: 2011-06-27 +tags: + - MacOSX + - spotlight + - diskio +--- +For some time now I've experience my mac to be very slow. Opening Vim would take minutes. Creating a new Tab in iTerm would take more than 20 seconds. What the hell is going on - this is a brand new MacBook Pro! +~ +After investigating, using _Activity Monitor_ I discovered the following: + + * CPU's idling at 3-5% usage + * +500M free RAM + * +200G free disk space + * Negligible amount of network traffic + * Very high amount of disk I/O writes (> 450w/s) + +### What's causing disk I/O? + +So, something is using my disk. But what? The solution is to use the `iotop` utility: + + sudo iotop -C 5 12 + +A common entry here is the `mds` procoess, which has an insane amount of `BYTES`. So, this `mds` process is causing a lot of I/O, causing things to get slow. + +### What's the `mds` process do? + +A quick Google search reveals that the `mds` process is actually the Spotlight indexer service. + +### Disable Spotlight + +I don't use Spotlight at all, so let's disable it - preventing the disk I/O. + + sudo mdutil -a -i off + +That's all. Spotlight indexing disabled. After a few seconds the disk I/O dropped from ± 450w/s to 0w/s. Vim starts ups again within a seconds. I'm happy. + +### Enabling Spotlight + +If, for some obscure reason, you want to re-enable Spotlight, use the following command: + + sudo mdutil -a -i on \ No newline at end of file diff --git a/content/posts/2011-07-05-git-squash-your-latests-commits-into-one.md b/content/posts/2011-07-05-git-squash-your-latests-commits-into-one.md new file mode 100644 index 0000000..9a4340f --- /dev/null +++ b/content/posts/2011-07-05-git-squash-your-latests-commits-into-one.md @@ -0,0 +1,42 @@ +--- +title: "Git: Squash your latests commits into one" +kind: article +slug: git-squash-your-latests-commits-into-one +created_at: 2011-07-05 +tags: + - git + - rebase + - squash +--- +With git it's possible to squash previous commits into one. This is a great way to group certain changes together before sharing them with others. +~ +Here's how to squash some commits into one. Let's say this is your current `git log`. + + * df71a27 - (HEAD feature_x) Updated CSS for new elements (4 minutes ago) + * ba9dd9a - Added new elements to page design (15 minutes ago) + * f392171 - Added new feature X (1 day ago) + * d7322aa - (origin/feature_x) Proof of concept for feature X (3 days ago) + +You have a branch `feature_x` here. You've already pushed `d7322aa` with the proof of concept of the new feature X. After that you've been working to add new element to the feature, including some changes in CSS. Now, you want to squash your last three commits in one to make your history look pretty. + +The command to accomplish that is: + + git rebase -i HEAD~3 + +This will open up your editor with the following: + + pick f392171 Added new feature X + pick ba9dd9a Added new elements to page design + pick df71a27 Updated CSS for new elements + +Now you can tell git what to do with each commit. Let's keep the commit `f392171`, the one were we added our feature. We'll squash the following two commits into the first one - leaving us with one clean commit with features X in it, including the added element and CSS. + +Change your file to this: + + pick f392171 Added new feature X + squash ba9dd9a Added new elements to page design + squash df71a27 Updated CSS for new elements + +When done, save and quit your editor. Git will now squash the commits into one. All done! + +Note: do not squash commits that you've already shared with others. You're changing history and it will cause trouble for others. \ No newline at end of file diff --git a/content/posts/2011-08-24-git-log-what-did-i-do-yesterday-exactly.md b/content/posts/2011-08-24-git-log-what-did-i-do-yesterday-exactly.md new file mode 100644 index 0000000..b9609fb --- /dev/null +++ b/content/posts/2011-08-24-git-log-what-did-i-do-yesterday-exactly.md @@ -0,0 +1,38 @@ +--- +title: "Git Log: What did I do yesterday, exactly?" +kind: article +slug: git-log-what-did-i-do-yesterday-exactly +created_at: 2011-08-24 +tags: + - git +--- +Sometimes you have to take your git repository's log to see what you did the day before (ideal in preparation for the daily stand-up). What I want is a clean overview of each commit messages, their author and the time. The output result should be easily grep-able so I can filter stuff I don't need out. +~ +To do this, I use the following custom `git log` command: + + git log --pretty=format:'%Cred%h%Creset - %C(yellow)%ae%Creset - %Cgreen%cd%Creset - %s%Creset' --abbrev-commit --date=iso + +The result: + + 5d6ef1e - ariejan@ariejan.net - 2011-05-02 10:36:43 +0200 - Bumped to version 1.5.2 + afede9e - ariejan@ariejan.net - 2011-05-02 10:35:53 +0200 - Fixed #29 - Sharing to facebook without a title now works properly. + d9985a1 - ariejan@ariejan.net - 2011-04-23 00:13:06 -0700 - Added Travis build status to README + 3e1149c - ariejan@ariejan.net - 2011-04-22 13:44:21 +0200 - Bumped version to 1.5.1 + fcab3bf - ariejan@ariejan.net - 2011-04-22 13:43:41 +0200 - Don't test on ruby 1.9.2 for now. See issue #27 + 93843ec - ariejan@ariejan.net - 2011-04-22 13:42:29 +0200 - Fixed issue #28 - Share-to-* double-encodes titles + ec56315 - ariejan@ariejan.net - 2011-04-22 12:43:41 +0200 - Fixed up utf-8 encoding for ruby 1.9.2 + 2146134 - ariejan@ariejan.net - 2011-04-22 12:39:37 +0200 - Bump Sinatra to 1.2.3 + c5efbf4 - ariejan@ariejan.net - 2011-04-22 12:07:41 +0200 - Truncate long URLs in the back-end to maintain a correct layout. + +Now, it's easy to see what I did on april 22nd using grep + + git log | grep 2011-04-22 + +You can also filter on email address to select only specific user etc. Of course, all this could be done with git, but I'm way more comfortable using grep. + +To make life easy, you can create a shortcut for this log command. Add this to you `~/.gitconfig`: + + [alias] + timelog = log --pretty=format:'%Cred%h%Creset - %C(yellow)%ae%Creset - %Cgreen%cd%Creset - %s%Creset' --abbrev-commit --date=iso + +You can now use `git timelog` in any git repository. \ No newline at end of file diff --git a/content/posts/2011-09-08-git-remove-reset-and-rollback-commits.md b/content/posts/2011-09-08-git-remove-reset-and-rollback-commits.md new file mode 100644 index 0000000..98b9703 --- /dev/null +++ b/content/posts/2011-09-08-git-remove-reset-and-rollback-commits.md @@ -0,0 +1,38 @@ +--- +title: "Git: remove, reset and rollback commits" +kind: article +slug: git-remove-reset-and-rollback-commits +created_at: 2011-09-08 +tags: + - git + - revert + - reset + - rollback +--- +We've all been there, you committed changes you now regret. If you didn't share those commits with anyone yet, you're safe. Let me show you how to remove commits from your local repository. I'll also include an example how to roll back commits you already did share with others. +~ +Use `git log` to see your most recent commits. Let's say you want to revert the last three commits, you can run the following command: + + git reset --hard HEAD~3 + +If you only want the last commit to be removed: + + git reset --hard HEAD~1 + +HEAD~1 is a shorthand for the commit before head. But, it's also possible to roll back to a specific commit using the SHA hash. + + git reset --hard d3f1a8 + +> Please note that all your uncommitted changes will be lost when you perform `git reset --hard`. You might want to use [git stash][1] to save your uncommitted changes. + +In case you already pushed your changes to a remote repository, you can't use `git reset`, because it will wreak havoc with other people's repositories later. Instead, you could revert your commit (e.g. create a new commit, undoing a previous one). + +Note that git revert does not walk back into history, but only works on a specific commit or range of commits. To use my previous examples: + + git revert HEAD~3..HEAD + git revert HEAD~1..HEAD + git revert d3f1a8..master + +Optionally specify the `--no-commit` option to see what's being reverted. + +[1]: http://ariejan.net/2008/04/23/git-using-the-stash/ \ No newline at end of file diff --git a/content/posts/2011-09-13-git-checkout-a-single-file-from-another-commit-or-branch.md b/content/posts/2011-09-13-git-checkout-a-single-file-from-another-commit-or-branch.md new file mode 100644 index 0000000..c1582dd --- /dev/null +++ b/content/posts/2011-09-13-git-checkout-a-single-file-from-another-commit-or-branch.md @@ -0,0 +1,13 @@ +--- +title: "Git: checkout a single file from another commit or branch" +kind: article +slug: git-checkout-a-single-file-from-another-commit-or-branch +created_at: 2011-09-13 +tags: + - git +--- +I recently worked on a new feature in a separate branch. It didn't work out well, so I branched master again and tried another solution. However, I needed a specific filesI committed in the first feature branch. To avoid placing those files back in my working copy by hand, I used git to checkout the specific file from the other branch. + + git checkout feature_1 -- path/to/file/iwant + +This will *not* checkout the `feature_1` branch, but instead checkout the most recent version of `path/to/file/iwant` in the `feature_1` branch. Very handy indeed! \ No newline at end of file diff --git a/content/posts/2011-09-14-lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn.md b/content/posts/2011-09-14-lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn.md new file mode 100644 index 0000000..5a5247b --- /dev/null +++ b/content/posts/2011-09-14-lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn.md @@ -0,0 +1,535 @@ +--- +title: "Lighting fast, zero-downtime deployments with git, capistrano, nginx and Unicorn" +kind: article +slug: lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn +created_at: 2011-09-14 +tags: + - Capistrano + - Rails + - git + - unicorn + - nginx + - zero-downtime + - deployment +--- +Everyone who has ever developed a web app has had to deploy it. Back in the day you simply uploaded your files with FTP and everything would be good. Today we have to clone git repositories, restart servers, set permissions, create symlinks to our configuration files, clean out caches and what not. + +## Doctor, what's wrong? + +In my opinion there are two critical problems with deployments today: + + * They are slow + * They cause downtime + +Both topics have been discussed by the likes of Twitter and Github. They have optimized their deployment process to allow for fast and _continuous deployments_. But, you are probably stuck with a default Capistrano install. As it turns out, with a little work, it's quite easy to setup the same deployment process as Github and Twitter use. + +For Ariejan.net, I've managed to get zero-downtime deployments that run in under 10 seconds. Yes, you read that right. +~ +## Let's go! + +This guide will help you setup your server and Rails 3.1 project for fast, zero-downtime deployments. I'll be using Nginx+Unicorn to serve the application, git+capistrano for fast deployments. +~ +## The shopping list + +Here's a list of ingredients you'll need: + + * A recent Ubuntu server (I used 11.04 Netty) + * Your Rails 3.1 app + * A remote git repository that contains your app + +## Assumptions + +I'm making some assumptions about your app: + + * Ruby 1.9.2 + * Rails 3.1 app using Postgres named `my_site` + * You want to use RVM and Bundler + +## Setting up your server + +There are a few things you need to setup before diving in. The first bit is run under the `root` user. + +Here's the full `apt-get` command list I used. + + apt-get update + apt-get upgrade -y + apt-get install build-essential ruby-full libmagickcore-dev imagemagick libxml2-dev \ + libxslt1-dev git-core postgresql postgresql-client postgresql-server-dev-8.4 nginx curl + apt-get build-dep ruby1.9.1 + +You'll also need a separate user account to run your app. Believe me, you don't want to run your app as `root`. I call my user `deployer`: + + useradd -m -g staff -s /bin/bash deployer + passwd deployer + +To allow `deployer` to execute commands with super-user privileges, add the following to `/etc/sudoers`. This required `deployer` to enter his password before allowing super-user access. + + # /etc/sudoers + %staff ALL=(ALL) ALL + +## Ruby and RVM + +With that done, you're ready to install `rvm`, I performed a system-wide install, so make sure you run this as root. + + bash < <(curl -s https://rvm.beginrescueend.com/install/rvm) + +Next up install the required ruby, in this case ruby-1.9.2-p290 and rubygems: + + rvm install ruby-1.9.2-p290 + wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.10.tgz + tar zxvf rubygems-1.8.10.tgz + cd rubygems-1.8.10 + ruby setup.rb + +Create a `~/.gemrc` file, this sets some sane defaults for your production server: + + :::yaml + # ~/.gemrc + --- + :verbose: true + :bulk_threshold: 1000 + install: --no-ri --no-rdoc --env-shebang + :sources: + - http://gemcutter.org + - http://gems.rubyforge.org/ + - http://gems.github.com + :benchmark: false + :backtrace: false + update: --no-ri --no-rdoc --env-shebang + :update_sources: true + +Also create this `~/.rvmrc` file to auto-trust your .rvmrc project files: + + # ~/.rvmrc + rvm_trust_rvmrcs_flag=1 + +_Note: do this for both `root` and the `deployer` user to avoid confusion later on._ + +Because you'll be running your app in production-mode all the time, add the following line to `/etc/environment` so you don't have to repeat it with every Rails related command you use: + + RAILS_ENV=production + +## Postgres + +I know not everybody uses Postgres, but I do. I love it and it beats the living crap out of MySQL. If you use MySQL, you'll know what to do. Here are instructions for setting up Postgres. First create the database and login as the `postgres` user: + + sudo -u postgres createdb my_site + sudo -u postgres psql + +Then execute the following SQL: + + :::sql + CREATE USER my_site WITH PASSWORD 'password'; + GRANT ALL PRIVILEGES ON DATABASE my_site TO my_site; + +## Nginx + +Nginx is a great piece of Russian engineering. You'll need some configuration though: + + # /etc/nginx/sites-available/default + upstream my_site { + # fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response (in case the Unicorn master nukes a + # single worker for timing out). + + # for UNIX domain socket setups: + server unix:/tmp/my_site.socket fail_timeout=0; + } + + server { + # if you're running multiple servers, instead of "default" you should + # put your main domain name here + listen 80 default; + + # you could put a list of other domain names this application answers + server_name my_site.example.com; + + root /home/deployer/apps/my_site/current/public; + access_log /var/log/nginx/my_site_access.log; + rewrite_log on; + + location / { + #all requests are sent to the UNIX socket + proxy_pass http://my_site; + proxy_redirect off; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + client_max_body_size 10m; + client_body_buffer_size 128k; + + proxy_connect_timeout 90; + proxy_send_timeout 90; + proxy_read_timeout 90; + + proxy_buffer_size 4k; + proxy_buffers 4 32k; + proxy_busy_buffers_size 64k; + proxy_temp_file_write_size 64k; + } + + # if the request is for a static resource, nginx should serve it directly + # and add a far future expires header to it, making the browser + # cache the resource and navigate faster over the website + # this probably needs some work with Rails 3.1's asset pipe_line + location ~ ^/(images|javascripts|stylesheets|system)/ { + root /home/deployer/apps/my_site/current/public; + expires max; + break; + } + } + +All dandy! One more then: + + # /etc/nginx/nginx.conf + user deployer staff; + + # Change this depending on your hardware + worker_processes 4; + pid /var/run/nginx.pid; + + events { + worker_connections 1024; + multi_accept on; + } + + http { + sendfile on; + tcp_nopush on; + tcp_nodelay off; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + gzip_disable "msie6"; + + # gzip_vary on; + gzip_proxied any; + gzip_min_length 500; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; + } + +Okay, that's Nginx for you. You should start it now, although you'll get a 500 or proxy error now: + + /etc/init.d/nginx start + +## Unicorn + +The next part involves setting up Capistrano and unicorn for your project. This is where the real magic will happen. + +You'll be doing `cap deploy` 99% of the time. This command needs to be _fast_. To accomplish this I want to utilize the power of git. Instead of having Capistrano juggle around a bunch of release directories, which is painfully slow, I want to use git to switch to the correct version of my app. This means I'll have just _one_ directory that is updated by git when it needs to be. + +Let's get started by adding some gems to your app. When done run `bundle install`. + + :::ruby + # Gemfile + gem "unicorn" + + group :development do + gem "capistrano" + end + +The next step is adding a configuration file for Unicorn in `config/unicorn.rb`: + + :::ruby + # config/unicorn.rb + # Set environment to development unless something else is specified + env = ENV["RAILS_ENV"] || "development" + + # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete + # documentation. + worker_processes 4 + + # listen on both a Unix domain socket and a TCP port, + # we use a shorter backlog for quicker failover when busy + listen "/tmp/my_site.socket", :backlog => 64 + + # Preload our app for more speed + preload_app true + + # nuke workers after 30 seconds instead of 60 seconds (the default) + timeout 30 + + pid "/tmp/unicorn.my_site.pid" + + # Production specific settings + if env == "production" + # Help ensure your application will always spawn in the symlinked + # "current" directory that Capistrano sets up. + working_directory "/home/deployer/apps/my_site/current" + + # feel free to point this anywhere accessible on the filesystem + user 'deployer', 'staff' + shared_path = "/home/deployer/apps/my_site/shared" + + stderr_path "#{shared_path}/log/unicorn.stderr.log" + stdout_path "#{shared_path}/log/unicorn.stdout.log" + end + + before_fork do |server, worker| + # the following is highly recomended for Rails + "preload_app true" + # as there's no need for the master process to hold a connection + if defined?(ActiveRecord::Base) + ActiveRecord::Base.connection.disconnect! + end + + # Before forking, kill the master process that belongs to the .oldbin PID. + # This enables 0 downtime deploys. + old_pid = "/tmp/unicorn.my_site.pid.oldbin" + if File.exists?(old_pid) && server.pid != old_pid + begin + Process.kill("QUIT", File.read(old_pid).to_i) + rescue Errno::ENOENT, Errno::ESRCH + # someone else did our job for us + end + end + end + + after_fork do |server, worker| + # the following is *required* for Rails + "preload_app true", + if defined?(ActiveRecord::Base) + ActiveRecord::Base.establish_connection + end + + # if preload_app is true, then you may also want to check and + # restart any other shared sockets/descriptors such as Memcached, + # and Redis. TokyoCabinet file handles are safe to reuse + # between any number of forked children (assuming your kernel + # correctly implements pread()/pwrite() system calls) + end + +Okay, as you can see there's some nice stuff in there to accomplish zero-downtime restarts. Let me tell you a bit more about that. + +Unicorn starts as a `master` process and then spawns several workers (we configured four). When you send Unicorn the 'USR2' signal it will rename itself to `master (old)` and create a new master process. The old master will keep running. + +Now, when the new master starts and forks a worker it checks the PID files of the new and old Unicorn masters. If those are different, the new master was started correctly. We can now send the old master the QUIT signal, shutting it down gracefully (e.g. let it handle open requests, but not new ones). + +All the while, you have restarted your app, without taking it down: zero downtime! + +## Capistrano + +Now for Capistrano, add the following to your `Gemfile`. + + :::ruby + # Gemfile + group :development do + gem "capistrano" + end + +And generate the necessary Capistrano files. + + capify . + +Open up `config/deploy.rb` and replace it with the following. + +This deploy script does all the usual, but the special part is where you reset the release paths to the current path, making the whole release directory unnecessary. + +Also not that the `update_code` is overwritten to do a simple `git fetch` and `git reset` - this is very fast indeed! + + :::ruby + # config/deploy.rb + require "bundler/capistrano" + + set :scm, :git + set :repository, "git@codeplane.com:you/my_site.git" + set :branch, "origin/master" + set :migrate_target, :current + set :ssh_options, { :forward_agent => true } + set :rails_env, "production" + set :deploy_to, "/home/deployer/apps/my_site" + set :normalize_asset_timestamps, false + + set :user, "deployer" + set :group, "staff" + set :use_sudo, false + + role :web, "123.456.789.012" + role :app, "123.456.789.012" + role :db, "123.456.789.012", :primary => true + + set(:latest_release) { fetch(:current_path) } + set(:release_path) { fetch(:current_path) } + set(:current_release) { fetch(:current_path) } + + set(:current_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip } + set(:latest_revision) { capture("cd #{current_path}; git rev-parse --short HEAD").strip } + set(:previous_revision) { capture("cd #{current_path}; git rev-parse --short HEAD@{1}").strip } + + default_environment["RAILS_ENV"] = 'production' + + # Use our ruby-1.9.2-p290@my_site gemset + default_environment["PATH"] = "--" + default_environment["GEM_HOME"] = "--" + default_environment["GEM_PATH"] = "--" + default_environment["RUBY_VERSION"] = "ruby-1.9.2-p290" + + default_run_options[:shell] = 'bash' + + namespace :deploy do + desc "Deploy your application" + task :default do + update + restart + end + + desc "Setup your git-based deployment app" + task :setup, :except => { :no_release => true } do + dirs = [deploy_to, shared_path] + dirs += shared_children.map { |d| File.join(shared_path, d) } + run "#{try_sudo} mkdir -p #{dirs.join(' ')} && #{try_sudo} chmod g+w #{dirs.join(' ')}" + run "git clone #{repository} #{current_path}" + end + + task :cold do + update + migrate + end + + task :update do + transaction do + update_code + end + end + + desc "Update the deployed code." + task :update_code, :except => { :no_release => true } do + run "cd #{current_path}; git fetch origin; git reset --hard #{branch}" + finalize_update + end + + desc "Update the database (overwritten to avoid symlink)" + task :migrations do + transaction do + update_code + end + migrate + restart + end + + task :finalize_update, :except => { :no_release => true } do + run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true) + + # mkdir -p is making sure that the directories are there for some SCM's that don't + # save empty folders + run <<-CMD + rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids && + mkdir -p #{latest_release}/public && + mkdir -p #{latest_release}/tmp && + ln -s #{shared_path}/log #{latest_release}/log && + ln -s #{shared_path}/system #{latest_release}/public/system && + ln -s #{shared_path}/pids #{latest_release}/tmp/pids && + ln -sf #{shared_path}/database.yml #{latest_release}/config/database.yml + CMD + + if fetch(:normalize_asset_timestamps, true) + stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S") + asset_paths = fetch(:public_children, %w(images stylesheets javascripts)).map { |p| "#{latest_release}/public/#{p}" }.join(" ") + run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" } + end + end + + desc "Zero-downtime restart of Unicorn" + task :restart, :except => { :no_release => true } do + run "kill -s USR2 `cat /tmp/unicorn.my_site.pid`" + end + + desc "Start unicorn" + task :start, :except => { :no_release => true } do + run "cd #{current_path} ; bundle exec unicorn_rails -c config/unicorn.rb -D" + end + + desc "Stop unicorn" + task :stop, :except => { :no_release => true } do + run "kill -s QUIT `cat /tmp/unicorn.my_site.pid`" + end + + namespace :rollback do + desc "Moves the repo back to the previous version of HEAD" + task :repo, :except => { :no_release => true } do + set :branch, "HEAD@{1}" + deploy.default + end + + desc "Rewrite reflog so HEAD@{1} will continue to point to at the next previous release." + task :cleanup, :except => { :no_release => true } do + run "cd #{current_path}; git reflog delete --rewrite HEAD@{1}; git reflog delete --rewrite HEAD@{1}" + end + + desc "Rolls back to the previously deployed version." + task :default do + rollback.repo + rollback.cleanup + end + end + end + + def run_rake(cmd) + run "cd #{current_path}; #{rake} #{cmd}" + end + +Now there is one little thing you'll need to do. I like to run my apps, even on the server, to use their own gemset. This keeps everything clean and isolated. Login to the `deployer` account and create your gemset. Next run `rvm info` and fill the `PATH`, `GEM_HOME` and `GEM_PATH` variables accordingly. + +> Don't forget to install `bundler` in your new gemset + +## Database configuration + +I always like to keep the database configuration out of git. I'll place it in the shared directory. + + :::yaml + # /home/deployer/apps/my_site/shared/database.yml + production: + adapter: postgresql + encoding: unicode + database: my_site_production + pool: 5 + username: my_site + password: password + +## First setup + +Now setup your deployment like this: + + cap deploy:setup + +This will clone your repo and link your `database.yml` file. Optionally, you may want to run migrations or upload an SQL dump to get started quickly with your app. + +## Deployments + +Whenever you have a new feature developed in a feature branch, this is the process of deploying it: + + 1. Merge `feature_branch` into `master` + 2. Run your tests to make sure everything is dandy. + 3. Push `master` + 4. Run `cap deploy` + +For Ariejan.net, step 4 takes less than 10 seconds. Unicorn is restarted with zero-downtime so users don't even notice the site was updated. + +## What's next? + +You now have fast, zero-downtime deployments working for your app. There are still some things you should to (which I might cover in some later post): + + * Tweak the nginx and Unicorn settings (especially number of workers); Perform some tests and see what works best for your app/server combination. + * Add caching to nginx (or add Varnish) + * Enable some monitoring, maybe Monit + * Crank up security a notch + * Integrate other deployment tasks like `whenever` or prepare your assets \ No newline at end of file diff --git a/content/posts/2011-09-19-capistrano-and-the-custom-maintenance-page.md b/content/posts/2011-09-19-capistrano-and-the-custom-maintenance-page.md new file mode 100644 index 0000000..308c9a7 --- /dev/null +++ b/content/posts/2011-09-19-capistrano-and-the-custom-maintenance-page.md @@ -0,0 +1,110 @@ +--- +title: "Capistrano and the custom maintenance page" +kind: article +slug: capistrano-and-the-custom-maintenance-page +created_at: 2011-09-19 +tags: + - Capistrano + - Rails + - git + - migrations + - deployment + - maintenance +--- +Randuin posted a comment on my previous [_Lighting fast, zero-downtime deployments with git, capistrano, nginx and Unicorn_][1] post asking how I handle database migrations. This is a good question. + +Database migrations, especially with large datasets, take a long time to run. They also lock your database tables which may cause all kinds of trouble. + +There's a quite an easy solution for this, offered to us by Capistrano. Unfortunately it will cause downtime for your site while the migration is running. + +[1]: http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn +~ +## Setup nginx + +The first thing you should do it update your Nginx configuration in `/etc/nginx/sites-available/default` and add the following snippet, just before the `location /`: + + if (-f $document_root/system/maintenance.html) { + return 503; + } + + error_page 503 @maintenance; + location @maintenance { + rewrite ^(.*)$ /system/maintenance.html last; + break; + } + +As [iGEL][3] and [Anlek Consulting][4] pointed out in the comments, it's good practice to send a _503 Service Temporarily Unavailable_ HTTP code back to the client. A normal user won't notice the difference, but spiders do. Sending the 503 code will tell search engines, like Google, that your site is not available and that they should not re-index your maintenance message as the new site content. Instead, they'll come back later when your site returns a _HTTP 200_ code again. + +You can try this fairly easily by putting your site in maintenance mode (per the instructions that follow) and do a HEAD request: + + λ curl -I http://ariejan.net + HTTP/1.1 503 Service Temporarily Unavailable + Server: nginx/0.8.54 + Date: Tue, 20 Sep 2011 18:22:35 GMT + Content-Type: text/html + Content-Length: 1276 + Connection: keep-alive + +## Use Capistrano + +Now, you can run (from your own machine): + + cap deploy:web:disable + +This task will upload a generic placeholder and place it in `public/system/maintenance.html`. If nginx sees this file exists, it will render it and abort further processing. So, as long as the maintenance.html file is present, your app is not accessible. + +When you're migrations are done, you can run: + + cap deploy:web:enable + +This will have remove the `maintenance.html` file, and thus making your app accessible again. + +## Piecing it all together + +What you probably want is a separate task to deploy new code *and* run migrations. Here's the task I used: + + :::ruby + namespace :deploy do + desc "Deploy and migrate the database - this will cause downtime during migrations" + task :migrations do + transaction do + update_code + web:disable + migrate + web:enable + end + restart + end + end + +## Customize your maintenance page + +Of course you want to customize the maintenance page, because, frankly, it's kind of ugly by default. This does require you write your own `deploy:web:disable` task: + + :::ruby + namespace :deploy do + namespace :web do + task :disable, :roles => :web, :except => { :no_release => true } do + require 'erb' + on_rollback { run "rm #{shared_path}/system/maintenance.html" } + + reason = ENV['REASON'] + deadline = ENV['UNTIL'] + + template = File.read("./app/views/layouts/maintenance.html.erb") + result = ERB.new(template).result(binding) + + put result, "#{shared_path}/system/maintenance.html", :mode => 0644 + end + end + end + +Now you can create a maintenance page for your app at `app/views/layouts/maintentance.html.erb`. See [the original maintenance template][2] for inspiration. + +Note that you can use the environment variables `REASON` and `UNTIL` to give some info about why the app is down. + +_Keep in mind that when you put up the `maintenance.html` none of your files is accessible. This includes stylesheets and js files. Make sure to host any images and other assets on a remote server like Amazon S3._ + +[2]: https://github.com/capistrano/capistrano/blob/master/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +[3]: http://igels.net +[4]: http://twitter.com/anlek \ No newline at end of file diff --git a/content/posts/2011-09-24-rspec-speed-up-by-tweaking-ruby-garbage-collection.md b/content/posts/2011-09-24-rspec-speed-up-by-tweaking-ruby-garbage-collection.md new file mode 100644 index 0000000..0011f53 --- /dev/null +++ b/content/posts/2011-09-24-rspec-speed-up-by-tweaking-ruby-garbage-collection.md @@ -0,0 +1,61 @@ +--- +title: "RSpec speed-up (24.6%) by tweaking ruby garbage collection" +kind: article +slug: rspec-speed-up-by-tweaking-ruby-garbage-collection +created_at: 2011-09-24 +tags: + - Ruby + - Rails + - rspec + - speed + - garbage collection + - GC +--- +[Today I learned][1] that Ruby garbage collection can be of huge importance to performance. More precisely, if Ruby does a lot of garbage collection it may slow down your code. Running garbage collection only every 10 or 20 seconds when running specs may increase performance dramatically. + +At this time specs for Ariejan.net take an average of 25.29s to run. This is not bad, but in my opinion faster specs are better. After tweaking ruby garbage collection I got my specs to run in 19.05s, a **24.6% speed increase**! +[1]: http://37signals.com/svn/posts/2742-the-road-to-faster-tests +~ +So, how do _you_ speed up _your_ tests? + +The key is that you don't want ruby to decide when to do garbage collection for you. Instead, we'll tell Ruby when to do this, and we'll let it do it every 15 seconds. + +To set this up for RSpec create a file `spec/support/deferred_garbage_collection.rb`: + + :::ruby + class DeferredGarbageCollection + + DEFERRED_GC_THRESHOLD = (ENV['DEFER_GC'] || 15.0).to_f + + @@last_gc_run = Time.now + + def self.start + GC.disable if DEFERRED_GC_THRESHOLD > 0 + end + + def self.reconsider + if DEFERRED_GC_THRESHOLD > 0 && Time.now - @@last_gc_run >= DEFERRED_GC_THRESHOLD + GC.enable + GC.start + GC.disable + @@last_gc_run = Time.now + end + end + end + +Next, add the following to your `spec/spec_helper.rb` so rspec will use our deferred garbage collection. + + :::ruby + config.before(:all) do + DeferredGarbageCollection.start + end + + config.after(:all) do + DeferredGarbageCollection.reconsider + end + +Now, when you run `rake spec` you should see a nice speed increase. Try to alter the threshold value a bit to see what gives your best performance: + + DEFER_GC=20 rake spec + +Enjoy! \ No newline at end of file diff --git a/content/posts/2011-09-25-properly-testing-rails-3-scopes.md b/content/posts/2011-09-25-properly-testing-rails-3-scopes.md new file mode 100644 index 0000000..58696d9 --- /dev/null +++ b/content/posts/2011-09-25-properly-testing-rails-3-scopes.md @@ -0,0 +1,65 @@ +--- +title: "Properly testing Rails 3 scopes" +kind: article +slug: properly-testing-rails-3-scopes +created_at: 2011-09-25 +tags: + - Ruby on Rails + - Rails + - rspec + - rails3 + - scope + - named_scope +--- +

    The content of this post is no longer correct. Please read this article for details.

    + +Testing scopes has always felt a bit weird to me. Normally I'd do something like this: + + :::ruby + class Post < ActiveRecord::Base + scope :published, where(:published => true) + scope :latest, order("created_at DESC") + end + + describe Post do + context 'scopes' do + before(:all) do + @first = FactoryGirl.create(:post, :created_at => 1.day.ago, :published => true) + @last = FactoryGirl.create(:post, :created_at => 4.day.ago, :published => false) + end + + it "should only return published posts" do + Post.published.should == [@first] + end + + it "should return posts in the correct order" do + Post.latest.should == [@first, @last] + end + end + end + +This test is okay. It tests if the named scope does what it needs to do. And there's also the problem. Scopes are part of ActiveRecord and are already extensively tested there. All we need to do is check if we _configure_ the scope correctly. + +What we need is a way to inspect what `where` and `order` rules are set for a particular scope and make sure those are the correct ones. We can then trust ActiveRecord to keep its promise to execute the proper SQL query for us. + +Here's another test that utilizes some Rails 3 methods you may not have heard of before. + + :::ruby + describe Post do + context 'scopes' do + it "should only return published posts" do + Post.published.where_values_hash.should == {:published => true} + end + + it "should return posts in the correct order" do + Post.latest.order_values.should == ["created_at DESC"] + end + end + end + +The `where_values_hash` and `order_values` allow you to inspect what a scopes doing. By writing your test this way you achieve two import goals: + + 1. You test _your_ code (instead of the ActiveRecord) + 2. You don't use the database, which is a significant speed boost + +Happy testing to you all! \ No newline at end of file diff --git a/content/posts/2011-10-09-testing-rails-3-scopes-revisited.md b/content/posts/2011-10-09-testing-rails-3-scopes-revisited.md new file mode 100644 index 0000000..2c7a373 --- /dev/null +++ b/content/posts/2011-10-09-testing-rails-3-scopes-revisited.md @@ -0,0 +1,44 @@ +--- +title: "Testing Rails 3 scopes revisited" +kind: article +slug: testing-rails-3-scopes-revisited +created_at: 2011-10-09 +tags: + - Ruby on Rails + - Rails + - rspec + - rails3 + - scope + - named_scope +--- +In my [previous article][pa] I told you about how I like to tests my scope. There was a fair amount of criticism on that post and after considering it all (and hearing Corey Haines' talk on Arrrrcamp last friday), I'm convinced it's the wrong path. + +In essence Rails allows you to create a scope to generate a custom database query for you. Now, if you only test your configuration of Rails (as I did in my previous post), you don't know if that scope works or not. If a piece directly relies on your database, you must test it against your database. + +Also, your configuration may be correct, but what happens when you upgrade to a newer version of Rails or PostgreSQL? Does that configuration still work as advertised? + +So, this is the proper way of testing your scopes: + + :::ruby + class Post < ActiveRecord::Base + scope :latest, order("created_at DESC") + end + + describe Post do + context 'scopes' do + before(:all) do + @first = FactoryGirl.create(:post, :created_at => 1.day.ago) + @last = FactoryGirl.create(:post, :created_at => 4.day.ago) + end + + it "should return posts in the correct order" do + Post.latest.should == [@first, @last] + end + end + end + +## A note on speed + +So, you now have to utilize the whole Rails stack _and_ hit your database to perform this test, which is slow. But then you don't upgrade Rails or your database very often, so you don't have run this spec with every TDD Baby Step you take. (More on that in a later post.) + +[pa]: http://ariejan.net/2011/09/25/properly-testing-rails-3-scopes \ No newline at end of file diff --git a/content/posts/2011-10-13-fast-specs-run-your-specs-in-less-than-1-second.md b/content/posts/2011-10-13-fast-specs-run-your-specs-in-less-than-1-second.md new file mode 100644 index 0000000..dc073ce --- /dev/null +++ b/content/posts/2011-10-13-fast-specs-run-your-specs-in-less-than-1-second.md @@ -0,0 +1,238 @@ +--- +title: "Fast specs - Run your specs in less than 1 second" +kind: article +slug: fast-specs-run-your-specs-in-less-than-1-second +created_at: 2011-10-13 +tags: + - Ruby + - Rails + - rspec + - testing + - bdd + - tdd + - cucumber + - fast_spec + - fastspec + - coreyhaines +--- +Okay, let me clarify that title first. I, as most of you, have two sets of tests for my Rails application: rspec and cucumber. rspec heavily focusses on testing models and business logic while cucumber focusses on testing the entire application stack and user interaction. + +The problem is that as your app grows, your test set grows - and so does the time it takes to run those tests. + +_This post is inspired by [Corey Haines](http://coreyhaines.com)' talk at [Arrrrcamp](http://arrrrcamp.be) (Oct 2011) and my own experience with writing fast specs._ + +## Red - Green - Refactor + +If you do TDD/BDD you most likely follow the Red-Green-Refactor pattern: + +1. Write one test, and see it fail (red) +2. Write the most minimal implementation to satisfy that test, and see it pass (green) +3. Refactor your code to look/perform better (refactor) +4. Repeat + +But what if your test suite takes >30 seconds to run. You write a test, then wait 30 seconds to see the test fail. You then write the simple implementation, wait 30 seconds. Oops, you made a typo - fix it, wait 30 seconds. Now it passes. Refactor, again wait 30 seconds. + +I think this scenario is very familiar for many rails developers. + +## Take a closer look + +So, let's take a closer look at a real-world example. + +This is a `Post` model, it `belongs_to` and author and it can give you a summary of an article by returning the text above a '~' marker. + + :::ruby + # app/models/post.rb + class Post < ActiveRecord::Base + DELIMITER = "~\n" + + belongs_to :author + + def summary + summary = if body =~ /#{DELIMITER}/i + body.split(/#{DELIMITER}/i).first.strip + else + body + end + end + end + +This is a spec that's defined for the `Post` model: + + :::ruby + # spec/models/post_spec.rb + require 'spec/helper' + + describe Post do + context "summary" do + it "should return the summary" do + post = FactoryGirl.build(:post, :body => "Summary\n~\nNo summary.") + post.summary.should == "Summary" + end + end + end + +Running this spec would take at least 10 seconds. Running `time rspec spec/models/post_spec.rb` woudl output something like this: + + Finished in 0.05523 seconds + real 0m10.387s + +This means that running the actual spec took 0.05s, but running the entire command took 10 seconds. What is slowing us down? + +## Dependencies + +As you see in the spec above I use `FactoryGirl.build` instead of `FactoryGirl.create` to prevent interacting with the database. I do this because hitting the database slows down your test. + +I removed the database dependency in order for my test to run faster. + +What other dependencies are there that could be removed in order to test the summary functionality? + +Any idea? + +Yes! + +Rails. + +## Rails is a dependency to your app + +You have a Rails-app. But Rails is not your app, it's a dependency, just like the `pg` and `haml` gems are dependencies. + +Loading the entire Rails stack takes quite some time. And just like with the database dependency we must ask ourselves: do we really need Rails to perform this test? + +There are a lot of scenarios where the answer to that question is _NO_. + +## Specs without Rails + +This may seem a bit weird at first, but let's take another look at the `Post` model: + + :::ruby + # app/models/post.rb + class Post < ActiveRecord::Base + DELIMITER = "~\n" + + belongs_to :author + + def summary + summary = if body =~ /#{DELIMITER}/i + body.split(/#{DELIMITER}/i).first.strip + else + body + end + end + end + +The `summary` method does not interact with the `Post` model at all, except that it access the `body` attribute. But the `body` attribute is just a `String`. + +So, if we wanted to test the `summary` method and remove all Rails dependencies, we'd have to remove ActiveRecord. + +Consider this: + + :::ruby + # app/logic/myapp/summary.rb + module MyApp + class Summary + def self.for(text, delimiter) + summary = if text =~ /#{delimiter}/i + text.split(/#{delimiter}/i).first.strip + else + text + end + end + end + end + +I think you can quickly see that this method does exactly the same as `Post#summary`. But it does not have any dependency to ActiveRecord. + +Could we rewrite our test for this new class? + + :::ruby + # fast_spec/myapp/summary_spec.rb + require 'myapp/summary' + + describe MyApp::Summary do + it "should return the summary" do + MyApp::Summary.for("Summary\n~\nNo summary.", "~\n").should == "Summary" + end + end + +That looks good! Note that this spec _does not_ include `spec_helper`. `spec_helper` is responsible for loading up your test environment, which normally includes all your app dependencies, including Rails. + +## Your new Post model + +The `Post` model should also be updated, of course to utilise this new class. + + :::ruby + # app/models/post.rb + class Post < ActiveRecord::Base + DELIMITER = "~\n" + + belongs_to :author + + def summary + MyApp::Summary.for(body, DELIMITER) + end + end + +The spec for `Post` should also be changed. Since we already have tested that `MyApp::Summary#for` returns the right summary for a given text and delimiter, all we have left to do is make sure that `Post#summary` calls it correctly. + + :::ruby + # spec/models/post_spec.rb + require 'spec/helper' + + describe Post do + context "summary" do + it "should return the summary" do + post = FactoryGirl.build(:post) + MyApp::Summary.should_receive(:for).with(post.body, Post::DELIMITER) + post.summary + end + end + end + +## Running fast specs + +The files above are located in `fast_spec` and `app/logic`. I do this because I want to separate my fast_specs so I can run them independtly from my normal specs. + +Running fast specs works like this: + + rspec -I app/logic fast_spec + +Try it out with `time rspec -I app/logic fast_spec`: + + Finished in 0.03223 seconds + real 0m0.421s + +That's your same spec, down from about 10 seconds to 0.5 second. + +## The big picture + +By now you have seen that you can extract business logic into a seperate class. You've also seen that you can write tests that don't depend on Rails and run amazingly fast. + +Of course you ask, how does this relate to normal RSpec and Cucumber tests? + +My opinion is that your fast_specs test business logic that does not relate to anything Rails specific, like making calculations, processing text, et cetera. + +When you make changes to your business logic, you can test is very quickly. This shortens your TDD cycle and allows you to focus more on the task at hand (instead of waiting for your tests). + +RSpec tests are there to test integration with your dependencies like Rails. This is the place where test scopes and mailers. + +Cucumber still has its place to test user interaction with your app. Can a user still click the 'Order' button and get the proper response from our app. + +RSpec and Cucumber will now be ran less often. Maybe only before you commit code - or maybe have your CI run them for you? + +## Added benefit - Design + +An added bonus of TDD is that your tests dictate the design of your app. By using fast_spec you force your business logic into separate classes. This makes them even more re-usable than normal Rails models. + +Your ActiveRecord models now become cleaner and are more a _configuration of Rails_ than anything else. + +By doing this your app will become way more maintainable, easier to test, faster to test and your code will be more re-usable. What's not to like? + +## Where to go from here? + +As Corey Haines put it: + +> Try to extract one single method from your app into a fast_spec. Just one. When you have that one spec running so fast it's easy to add more. + +This would also be my advise to you. Try to extract one piece of functionality from a model into a fast_spec. It may be difficult at first, but try it and see the results. + +Then, when you have that one example working, add more. \ No newline at end of file diff --git a/content/posts/2011-10-14-rails-3-customized-exception-handling.md b/content/posts/2011-10-14-rails-3-customized-exception-handling.md new file mode 100644 index 0000000..d3da84d --- /dev/null +++ b/content/posts/2011-10-14-rails-3-customized-exception-handling.md @@ -0,0 +1,98 @@ +--- +title: "Rails 3: Customized exception handling" +kind: article +slug: rails-3-customized-exception-handling +created_at: 2011-10-14 +tags: + - Rails + - rails3 + - exceptions + - error + - rescue +--- +Exceptions happen. There's no way around that. But not all exceptions are created equally. + +For instance, a 404 "Not found" error can (and should) be handled correctly in your application. + +Let me give you an example of how to handle a `ActiveRecord::RecordNotFound` exception. Let's assume you have an application that could show a user profile: + + :::ruby + # GET /p/:name + def show + @profile = Profile.find(params[:name]) + end + +Now, it may happen that the `:name` paramater contains a value that cannot be found in our database, most likely because someone made a typo in the URL. + +If `Profile#find` cannot get a proper result it will throw `ActiveRecord::RecordNotFound`. + +Now, instead of showing the user the (by default ugly) 404 page from `public/404.html` we want to do something more fancy. + +## Action-specific exception handling + +Here's one solution: + + :::ruby + # GET /p/:name + def show + @profile = Profile.find(params[:name]) + rescue + render :template => 'application/profile_not_found', :status => :not_found + end + +You can now create `app/views/applicaiton/profile_not_found.html.haml` and give a nice custom error message to your user. + +You may try to find some matching profiles to `:name` or show a search box. + +## Global exception handling + +The above example only works for the specific profile `show` action. It's also possible to hanlde exceptions on the application level. + +Your `show` action still looks like this: + + :::ruby + # GET /p/:name + def show + @profile = Profile.find(params[:name]) + end + +Then, in your `app/controllers/application_controller.rb` add this: + + :::ruby + class ApplicationController < ActionController::Base + rescue_from ActiveRecord::RecordNotFound, :with => :rescue_not_found + + protected + def rescue_not_found + render :template => 'application/not_found', :status => :not_found + end + end + +Whenever an `ActiveRecord::RecordNotFound` exception is thrown (and not handled by the action itself), it will be handled by your `ApplicationController`. + +## Custom exceptions + +It's possible to throw your own custom exceptions and handle them in different ways. Like this: + + :::ruby + # Define your own error + class MyApp::ProfileNotFoundError < StandardError + end + + # GET /p/:name + def show + @profile = Profile.find_by_name(params[:name]) + raise MyApp::ProfileNotFoundError if @profile.nil? + end + +And add this to your `ApplicationController`: + + :::ruby + rescue_from MyApp::ProfileNotFoundError, :with => :profile_not_found + +Optionally, if you don't want to write that custom `profile_not_found` method, you may also supply a block: + + :::ruby + rescue_from MyApp::ProfileNotFoundError do |exception| + render :nothing => "Profile not found.", :status => 404 + end diff --git a/content/posts/2011-10-22-automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails.md b/content/posts/2011-10-22-automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails.md new file mode 100644 index 0000000..6dc75c0 --- /dev/null +++ b/content/posts/2011-10-22-automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails.md @@ -0,0 +1,196 @@ +--- +title: "Automatically switch between SSL and non-SSL with Nginx+Unicorn+Rails" +kind: article +slug: automatically-switch-between-ssl-and-non-ssl-with-nginx-unicorn-rails +created_at: 2011-10-22 +tags: + - Rails + - rails3 + - unicorn + - nginx + - ssl +--- +_Scroll down for setup instructions. Or, read this bit about SSL in the real world first._ + +SSL or Secure Socket Layer is a nice way to secure sensitive parts of your Rails application. It achieves to goals. + +Firstly is encrypts all traffic between you and the remote server. Consider the passwords and personal information you submit to websites. When unencrypted (using HTTP), all this data is sent over the internet for all to read. With SSL (HTTPS) enabled, all traffic is encrypted, making it very hard for a third-party to eavesdrop. + +Secondly, a proper SSL connection can give you trust in that you're communicating with the right people. + +For example, Rabobank (a Dutch bank) uses SSL for its website. When you open their site, you'll notice the green 'Rabobank Nederland' in the address bar. + +![image](https://ariejannet.s3.amazonaws.com/content/rabobank-ssl.jpg) + +This tells me I am communicating with Rabobank Nederland. This is why SSL certificates are so expensive - the SSL authority needs to verify the identify of Rabobank before they issue the certificate. + +In the example above Rabobank uses an [EV SSL Certificate][evssl]. EV stands for Extended Validation. This means that the SSL authority has verified (among other things) that Rabobank is a legitimate business and that they are the legal owner of the domain rabobank.nl + +[evssl]: http://en.wikipedia.org/wiki/Extended_Validation_Certificate + +The cost for such an EV SSL Certificate is $200 - $1000 per year. You probably don't need it for your site. + +## SSL for you and me +When you are looking to secure the back-end of your site (where you login etc.), you only require the encryption part of SSL. There are two routes you can take + +### Self signed SSL +You are able to create a working SSL certificate yourself. This will give you encryption, but no identity validation. When you use a self-signed SSL certificate all browsers will warn you about this. + + * Encryption + * No validation + * Warnings from your browser + * Free + +For me, that's a reason not to use self signed SSL for any other than development and testing purposes. + +### Standard SSL +Most SSL authorities provide you with a _Standard SSL_ product. These certificates only check if you own the domainname. They also offer encryption and work (without warnings) in your browser. You can get one of these for as little as $9 a year. + + * Encryption + * Domain validation / trust + * No warnings from your browser + * Cheap ($10 - $20) + +## Setting up SSL for your Rails application +Setting up SSL is a web server thing. It does not involve your Rails appliation directly (but more on that in a moment). + +If you followed my [nginx+unicorn][nu] guide, you'll have Nginx and Unicorn setup already. + +[nu]: http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn + +### Create your private SSL key (key) + +First you need to create a private key. Do this on you server. The following command will generate a 2048 bit key. When it asks you to set a passphrase, do so. + + $ openssl genrsa -des3 -out example.com.key 2048 + Generating RSA private key, 2048 bit long modulus + ..................................+++ + .................................................................................+++ + e is 65537 (0x10001) + Enter pass phrase for example.com.key: + Verifying - Enter pass phrase for example.com.key: + +### Creating a key and Certificate Sign Request (csr) + +Okay, you now have your key. Next step, create the Certificate Sign Request. The sign request is the part you'll send to the SSL authority to sign. You will need to provide some information here. Make sure you enter everything correctly. + + * `Country Name` - Your 2 letter country code. I.e. NL, UK, BE + * `State or Povince Name`. I.e. Noord-Brabant, New York + * `Locality Name` - Your city. I.e. Eindhoven, London + * `Organization Name` - Your company or site name: Ariejan.net, Apple Inc. + * `Organization Unit Name` - The section of your company. You may leave this blank. I.e. Online Services, Finance + * `Common Name` - The domain this SSL certificate will be used for. If you want to run this on https://www.example.com, you enter `www.example.com` here. This is *not* a wildcard. `example.com` will **not** work on `www.example.com` and vice versa. + * `Email Address`, `challenge password` and `optional company name` should normally be left blank. Just hit enter. + +Here's the full version: + + $ openssl req -new -key example.com.key -out example.com.csr + Enter pass phrase for example.com.key: <> + You are about to be asked to enter information that will be incorporated + into your certificate request. + What you are about to enter is what is called a Distinguished Name or a DN. + There are quite a few fields but you can leave some blank + For some fields there will be a default value, + If you enter '.', the field will be left blank. + ----- + Country Name (2 letter code) [AU]:NL + State or Province Name (full name) [Some-State]:Noord-Brabant + Locality Name (eg, city) []:Eindhoven + Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ariejan.net + Organizational Unit Name (eg, section) []: + Common Name (eg, YOUR name) []:example.com + Email Address []: + + Please enter the following 'extra' attributes + to be sent with your certificate request + A challenge password []: + An optional company name []: + +Great, you now have two files: + + * `example.com.key` - your private key. Keep it secret, keep it safe! + * `example.com.csr` - sign request + +### Get your certificate (crt) +Now, go to your selected SSL authority, order your Standard SSL Certificate and upload the contents of `example.com.csr` when requested. + +After doing some validations, which may require you to click some links in emails, you're certificate should be ready for download. Save this file as `example.com.crt`. + +### Intermediate certificates +Some SSL authorities work with so called intermediate certificates. This requires you to include an intermediate certificate with your own certificate. If your SSL provider requires this, save the intermediate certificate as `intermediate.crt`. + +For usage with nginx, you must place both your own and the intermediate certificates in a single file. This is easy: + + $ cat example.com.crt intermediate.crt > sslchain.crt + +### Remove the passphrase from your key +Now, this is not recommended, but many people do this. The reason is that when your private key has passphrase, your server requires that passphrase everytime your (re)start it. This could mean that your server cannot boot up without manual interaction from your part. + + $ cp example.com.key example.com.key.orig + $ openssl rsa -in example.com.key.orig -out example.com.key + +You now have `example.com.key.orig`, which is your original private key _with_ the passphrase. And you have `example.com.key`, which is the same private key, but _without_ the passphrase. + +### Setup nginx for SSL +Finally, you can setup Nginx for SSL. Normally I add both a SSL and non-SSL configuration. Setup is very easy. + +First of all, become root and copy your keys and certificate to `/etc/ssl`. + + $ cp example.com.key example.com.crt /etc/ssl + +or if you use an intermediate certificate: + + $ cp example.com.key sslchain.crt /etc/ssl + +Next, you take your non-SSL `server` configuration and duplicate it. Then you add the following lines. + + listen 443; # Instead of Listen 80 + + ssl on; + ssl_certificate /etc/ssl/sslchain.crt; # or /etc/ssl/example.com.crt + ssl_certificate_key /etc/ssl/example.com.key; + + location / { + # Add this to the location directive. + proxy_set_header X-Forwarded-Proto https; + } + +Most of this is pretty straight forward. The `proxy_set_header` directive is needed to let your Rails application know if the request came in over SSL or not. Normally this shouldn't matter, but you'll need it for the next part of this guide. + +Save, restart nginx and your SSL connection should be available. + +## Automatically switch between SSL and non-SSL with Rails +To take this site as an example, I don't want to run the front-end through SSL. First of all, you can't submit any data to my server. Second, I include several external resources (Disqus, Twitter, AdSense), that will give you warnings about using "insecure" content on an encrypted page. + +What I _do_ want is to encrypt traffic to the backend, where I log in and write posts like these. + +I need to make sure that your browser knows exactly when and when not to switch to SSL. This is where the `rack-ssl-enforcer` gem comes in. + +First, update your Gemfile: + + # Gemfile + gem 'rack-ssl-enforcer' + +After you've run `bundle install`, update `config/application.rb` (or `config/environments/production.rb` is you only want to configure this for your production environment). + + # config/application.rb or config/environments/production.rb + config.middleware.use Rack::SslEnforcer, + :redirect_to => 'https://example.com', # For when behind a proxy, like nginx + :only => [/^\/admin\//, /^\/authors\//], # Force SSL on everything behind /admin and /authors + :strict => true # Force no-SSL for everything else + +With the following statement, you achieve the following: + + * When you access any URL with a path that starts with `/admin` or `/authors`, you'll be redirected to the SSL site. + * Because we're behind a proxy and want to do proper redirects, we specify the correct SSL domain. + * We set `strict` to true. If you access something that is _not_ `/admin` or `/authors`, you will be redirected to the non-SSL version of that page. + +There's a lot more possible with the [rack-ssl-enforcer][rse] gem. Checkout their [README on Github][readme] for details. + +_Note: if you find yourself getting into an infinite redirect loop, make sure have the `proxy_set_header` directive set correctly in your Nginx configuration._ + +[rse]: http://rubygems.org/gems/rack-ssl-enforcer +[readme]: https://github.com/tobmatth/rack-ssl-enforcer#readme + +## Wrapping up +You now know how you can setup an SSL certificate with Nginx and how you can make your Rails application automatically switch between SSL and non-SSL whenever you want to. \ No newline at end of file diff --git a/content/posts/2011-10-24-installing-node-js-and-npm-on-ubuntu-debian.md b/content/posts/2011-10-24-installing-node-js-and-npm-on-ubuntu-debian.md new file mode 100644 index 0000000..bc33c34 --- /dev/null +++ b/content/posts/2011-10-24-installing-node-js-and-npm-on-ubuntu-debian.md @@ -0,0 +1,54 @@ +--- +title: "Installing Node.js and NPM on Ubuntu/Debian" +kind: article +slug: installing-node-js-and-npm-on-ubuntu-debian +created_at: 2011-10-24 +tags: + - Linux + - Ubuntu + - debian + - nodejs + - devops + - node + - npm +--- +This is just short snippet on how to install Node.js (any version) and NPM (Node Package Manager) on your Ubuntu/Debian system. + +**Step 1 - Update your system** + + sudo apt-get update + sudo apt-get install git-core curl build-essential openssl libssl-dev + +**Step 2 - Install Node.js** + +First, clone the Node.js repository: + + git clone https://github.com/joyent/node.git + cd node + +Now, if you require a specific version of Node: + + git tag # Gives you a list of released versions + git checkout v0.4.12 + +Then compile and install Node like this: + + ./configure + make + sudo make install + +Then, check if node was installed correctly: + + node -v + +** Step 3 - Install NPM** + +Simply run the NPM install script: + + curl http://npmjs.org/install.sh | sudo sh + +And then check it works: + + npm -v + +That's all. \ No newline at end of file diff --git a/content/posts/2011-11-08-fixing-a-slow-starting-terminal-or-iterm2-on-mac-os-x.md b/content/posts/2011-11-08-fixing-a-slow-starting-terminal-or-iterm2-on-mac-os-x.md new file mode 100644 index 0000000..f517eb5 --- /dev/null +++ b/content/posts/2011-11-08-fixing-a-slow-starting-terminal-or-iterm2-on-mac-os-x.md @@ -0,0 +1,30 @@ +--- +title: "Fixing a slow starting Terminal or iTerm2 on Mac OS X" +kind: article +slug: fixing-a-slow-starting-terminal-or-iterm2-on-mac-os-x +created_at: 2011-11-08 +tags: + - BASH + - shell + - MacOSX + - devops + - terminal + - terminal.app + - iterm + - iterm2 + - zsh +--- +For some time I have been annoyed with how slow my Terminal (in my case iTerm2) starts up. It would take 5-10 seconds before I was presented a prompt. Being in the console for the better part of the day, this was unacceptable. + +The first thing I did was _upgrade_ Bash to Zsh. I'd heard great things about Zsh, so I thought I'd give it a try. Zsh is really awesome, but it did not fix the start-up delay I was experiencing previously with Bash. + +Doing a little digging around I found a working solution. Apparently Apple keeps system logs in `/private/var/log/asl/*.asl`. Removing these files made my shell fast again. + +

    Do not remove all files in /private/var/log/asl as there are other files there that are not related to the shell. Leave them in there.

    + +With the above warning in the back of your mind, open your terminal (slowly) and issue the following command: + + sudo rm /private/var/log/asl/*.asl + +Now quit and restart Terminal or iTerm2 and your prompt should present itself quickly again. + diff --git a/content/posts/2011-11-09-contributing-to-open-source-with-github.md b/content/posts/2011-11-09-contributing-to-open-source-with-github.md new file mode 100644 index 0000000..3df23d5 --- /dev/null +++ b/content/posts/2011-11-09-contributing-to-open-source-with-github.md @@ -0,0 +1,79 @@ +--- +title: "Contributing to Open-Source with Github" +kind: article +slug: contributing-to-open-source-with-github +created_at: 2011-11-09 +tags: + - git + - rebase + - github + - flow + - fork + - upstream +--- +You want to contribute to an open-source project, but are scared away by all the git-complexity involved? This small guide will help you out. + +Octocat + +## Step 1 - Fork, fork, fork + +First create a fork of the original project. You can do this easily by clicking the "Fork" button on the top of the Github project page. This will give you your own copy (fork) of the entire repository. + +Then, check out your fork: + + git clone git@github.com:ariejan/repo-name.git + +## Step 2 - Contribute + +Before you start writing code there are a few tasks you need to perform: + + * Are there tests for this project? Are they all green? If not, fix this first. (using this same step, of course) + * Create a new branch: `git checkout -b fix_for_this_or_that` + * Red-Green-Refactor (e.g. write code) + * Commit your changes (in your `fix_for_this_or_that` branch). + +## Step 3 - Sharing your contribution + +With your contribution done, don't merge it back into `master`. `master` is your way of receiving changes from the original (upstream) repo (see step 4). + +First push your branch to Github, so you can share it with others. + + git push origin fix_for_this_or_that + +You should now see this branch in your Github project page. You'll also notice there's a "Pull Request" button at the top. Click it if you want the project maintainer to pull your `fix_for_this_or_that` branch into the main project. + +## Step 4 - Keeping up-to-date + +Over time the `master` of your fork will start lagging behind. Because you did not merge any of your code changes into the `master` of your fork, you can update it easily. + +Before you can pull in changes you must add a git remote for it. You can use the _Git Read-only_ URL for this. + + git remote add upstream https://github.com/some_one/some-repo.git + +Now, for raking in the changes; + + git checkout master + git fetch upstream + git merge upstream/master + +## Step 4.5 - Keeping your feature branch up-to-date + +If you pulled in changes from `upstream` and did not yet share your feature branch, you can rebase your feature branch. This makes sure you have the latest code from `master`. This also makes merging your pull request easier. + +

    Do not rebase if you already pushed your branch to Github. Read why.

    + + git checkout fix_for_this_or_that + git rebase master + +If any conflict arise, fix them. And continue your rebase: + + git add conflicting_file + git rebase --continue + +You may also abort the rebase: + + git rebase --abort + +## Big picture + +This small guide will help you to fork repositories on Github and use them to contribute code. By using the approach you lighten the task of the project maintainer who can easily checkout your specific changes and decide to include them into the main project or not. \ No newline at end of file diff --git a/content/posts/2011-11-18-deploying-a-third-party-rails-application-like-gitlab.md b/content/posts/2011-11-18-deploying-a-third-party-rails-application-like-gitlab.md new file mode 100644 index 0000000..bb92936 --- /dev/null +++ b/content/posts/2011-11-18-deploying-a-third-party-rails-application-like-gitlab.md @@ -0,0 +1,68 @@ +--- +title: "Deploying a third-party Rails application - like Gitlab" +kind: article +slug: deploying-a-third-party-rails-application-like-gitlab +created_at: 2011-11-18 +tags: + - Capistrano + - github + - gitlab + - deploy +--- +We all know how to deploy our own Rails projects. (If not, [read this guide][1].) But how do you handle deploying a third-party application that may require some customisation on your part? + +A good example would be [Gitlab][gitlab] + +Gitlab is an open source Github clone, build using Ruby on Rails. It's a nice project that uses Gitosis under the hood to manage your git repositories. There are [several][2] [good][3] installation guides available on the web, but they all assume you want to deploy gitlab verbatim - without any modification or configuration + +I have also setup Gitlab, but I want to use capistrano and unicorn. I also want to tweak some configuration. In some rare cases I want to fix an annoying bug and not wait for the Gitlab team to pull it. + +Doing a `git pull` on my remote server just won't cut it. + +## Fork! Fork! + +What I did was clone Gitlab and use the same principles describe in my [_Contributing to open source with Github_][4] to apply my own changes and merge any upstream changes when they are available. + +Here's how I set everything up. + +First, clone the official Gitlab repository and name it `upstream`. + + git clone --origin upstream https://github.com/gitlabhq/gitlabhq.git my_git_server + +Next I made all the changes I want. I updated `config/gitosis.yml` and `unicorn` to `Gemfile` and setup Capistrano. + +I then pushed this to my own git server. This is the same server Capistrano will use to pull changes from. + + git remote add origin git@git.ariejan.net:my_git_server.git + git push origin master + cap deploy + +That's all there is to deploying Gitlab from my own repository. + +## Merging upstream changes + +Now, the Gitlab crew is pushing out new features at an amazing rate. So, how do I get those new features (and the occasional bug fix) into my copy of Gitlab for deploying? + + git fetch upstream + +Remember how we named the official Gitlab repository `upstream` earlier? With this `fetch` we get all changes from their repository (but we don't apply them to anything yet). + +Then, merge the upstream changes with your own branch. + + git merge upstream/master + +There may be merge conflicts, just resolve them and commit your merge. Then again to deploy: + + git push origin master + cap deploy + +## Why do this? + +The reason I use this approach is that it's easy to merge upstream changes and have my own customizations and deployment tools at the same time. + +[1]: http://ariejan.net/2011/09/14/lighting-fast-zero-downtime-deployments-with-git-capistrano-nginx-and-unicorn +[gitlab]: http://www.gitlabhq.com +[2]: http://www.ryanwersal.com/blog/2011/10/18/installing-gitlab-on-ubuntu-server/ +[3]: http://nepalonrails.tumblr.com/post/12603081685/setup-gitlab-github-clone-using-vagrant-and-chef +[4]: http://ariejan.net/2011/11/09/contributing-to-open-source-with-github + diff --git a/content/posts/2011-11-22-upgrade-postgresql-8-4-to-postgresql-9-1-on-debian.md b/content/posts/2011-11-22-upgrade-postgresql-8-4-to-postgresql-9-1-on-debian.md new file mode 100644 index 0000000..efe6a72 --- /dev/null +++ b/content/posts/2011-11-22-upgrade-postgresql-8-4-to-postgresql-9-1-on-debian.md @@ -0,0 +1,34 @@ +--- +title: "Upgrade postgresql-8.4 to postgresql-9.1 on debian" +kind: article +slug: upgrade-postgresql-8-4-to-postgresql-9-1-on-debian +created_at: 2011-11-22 +tags: + - Ubuntu + - debian + - dba + - postgres + - postgresql +--- +Today I upgraded a production PostgreSQL 8.4 database to version 9.1. This was on a Debian server. +~ +The first step is to make a full dump of your data. I personally like to store that dump somewhere safe before upgrading. As root: + + su - postgres + pg_dumpall > dump.sql + exit + cp ~postgres/dump.sql /root/ + +Now you can safely remove the postgresql-8.4 and install postgresql-9.1: + + aptitude purge postgresql-8.4 + aptitude install postgresql-9.1 + +Next check the postgresql configuration in `/etc/postgresql/9.1/main`. If you make any changes, make sure to restart postgres with `/etc/init.d/postgresql restart`. + +Postgresql 9.1 is now up and running, let's import our data back into it. + + su - postgres + psql < dump.sql + +That's all. You're now fully upgraded to PostgreSQL 9.1. \ No newline at end of file diff --git a/content/posts/2011-12-13-recursively-fixing-file-and-directory-permissions.md b/content/posts/2011-12-13-recursively-fixing-file-and-directory-permissions.md new file mode 100644 index 0000000..4270ce5 --- /dev/null +++ b/content/posts/2011-12-13-recursively-fixing-file-and-directory-permissions.md @@ -0,0 +1,31 @@ +--- +title: "Recursively fixing file and directory permissions" +kind: article +slug: recursively-fixing-file-and-directory-permissions +created_at: 2011-12-13 +tags: + - Linux + - BASH + - git + - sh + - file permissions +--- +While working on a [Gitlab][1] installation I noticed that all repository file permissions were off. Fixing recursive file and directory permissions can be quite hard. Or so I thought. + +Using the following commands (in plain Bash) allow you to recursively set permissions for files and directories. So, to fix the proper read permissions on your Gitlab repositories you can use this: + + # Go to your git repositories directory (as git or the gitlab user) + cd /home/git/repositories + + # Fix ownership + sudo chown -R git:git * + + # Fix directory permissions + sudo find -type d -exec chmod 770 {} \; + + # Fix file permissions + sudo find -type f -exec chmod 660 {} \; + +After this, your Gitlab should have no trouble accessing your code (e.g. in the tree browser). + +[1]: http://gitlabhq.com/ \ No newline at end of file diff --git a/content/posts/2012-01-10-removing-untracked-files-and-directories-with-git.md b/content/posts/2012-01-10-removing-untracked-files-and-directories-with-git.md new file mode 100644 index 0000000..559e980 --- /dev/null +++ b/content/posts/2012-01-10-removing-untracked-files-and-directories-with-git.md @@ -0,0 +1,51 @@ +--- +title: "Removing untracked files and directories with git" +kind: article +slug: removing-untracked-files-and-directories-with-git +created_at: 2012-01-10 +tags: + - git +--- +I just tried writing some new code, but it was no success. This happens, but it left me with a working copy littered with new and changed files. + + # Changes not staged for commit: + # (use "git add ..." to update what will be committed) + # (use "git checkout -- ..." to discard changes in working directory) + # + # modified: config/routes.rb + # + # Untracked files: + # (use "git add ..." to include in what will be committed) + # + # db/migrate/20111231131752_create_validations.rb + # vendor/assets/images/ + # vendor/assets/javascripts/ + # vendor/assets/stylesheets/custom.sass + +So, how do I get rid of this mess? + +## 1 - Be absolutely sure you want to delete your work +I sometimes commit my work although it's of no use to me at the moment. It's in a separate branch anyway and can be easily ignored until I come back to it. + +So, if you're really sure you want to delete your changes and files, continue to step 2. + +## 2 - Delete untracked files and directories +Now, delete all files that are not yet tracked by git. You could do it like this: + + rm -r db/migrate/20111231131752_create_validations.rb vendor/assets/images \ + vendor/assets/javascripts/ vendor/assets/stylesheets/custom.sass + +This requires you to copy/paste or type all the files and directories you want to delete. There's a much easier way: ask git to do it for you: + + git clean -df + +The `d` option tells git to include directories, `f` says that you really want to perform the delete. Optionally, you can first run with `n` instead of `f` to see what's going to happen - a so called dry-run. + +If you use this command frequently and don't want to specify the `f` options every time you can set `clean.requireForce` in your `~/.gitconfig` to `true` to omit the `f` options. + +## 3 - Reset changes to tracked files +With all the new stuff out of the way, let's clean up the files that are tracked by git. You want to revert everything back to the last commit you made: + + git reset HEAD --hard + +That's all. You're now back to a clean working directory at `HEAD`. Start over now. \ No newline at end of file diff --git a/content/posts/2012-03-20-open-source-is-a-privilege-not-a-right.md b/content/posts/2012-03-20-open-source-is-a-privilege-not-a-right.md new file mode 100644 index 0000000..be10c39 --- /dev/null +++ b/content/posts/2012-03-20-open-source-is-a-privilege-not-a-right.md @@ -0,0 +1,59 @@ +--- +title: "Open Source is a privilege. Not a right." +kind: article +slug: open-source-is-a-privilege-not-a-right +created_at: 2012-03-20 +tags: + - oss + - free + - open-source +--- +_Edit: this is not a Gitlab specific issue. I've seen it before and I'm seeing it again at the Gitlab project._ + +The past few months I've been contributing to the [Gitlab](https://github.com/gitlabhq/gitlabhq) project. This has been a great experience, mostly because Gitlab has become a very popular project with over 2.3k watchers right now. + +It gives me great satisfaction knowing that my code is being used by hundreds if not thousands of people right now. But there is also this little thing that has been bugging me lately. + +There is a small core team of developers devoting time to the development of Gitlab. They do this passionatly and they have been cranking out releases and new features every 22nd of month for the past six months. + +A lot of effort has gone into making Gitlab what it is today. + +But with the rise in popularity there has also been an increased number issues, bugs and feature requests. The mailing list is also more active than ever. Of course, all of this is a good thing for an open source project, you would think. + +Most users appreciate the effort that goes into developing an application as complex as Gitlab. They appreciate the fact it's available for _free_. The appreciate somebody made time to develop a tool they can put to use on a daily basis. + +This appreciation is great motivator for the developers. Knowing their code is being put to good use only drives them write more code, more features. + +Unfortunately, I read tickets and threads of this kind too often lately: + +> Add [this feature]. My company needs this and if you don't add this we won't use Gitlab. + +Also this: + +> We are expecting [this feature] in the next release. + +Or: + +> We must have [this feature]. Gitlab is useless without it and we'll go back to using Github. + +Why do people think they can make _demands_ like these? + +Is it too hard to read the installation guide that comes with the project? + +Don't get me wrong, it's awesome people are willing to try and install Gitlab. It's even more awesome people post their ideas and feature requests to further improve Gitlab. + +However, the **lack of respect** for people who spend their **free time** developing an open source application for you **to use for free** is sickening. + +No, really. Sickening. + +There really is no reason to _threaten_ or _pressure_ the people who are dedicating the free time, parts of the lives, to an open source project to implement something for you. + +Of course, some of you will say, "Just ignore those ass holes. They're not worth your time." I agree, but after reading two or three of these responses I don't feel like spending my evening off coding some awesomeness. + +This is a request to anyone who's ever going to post a feature request, a bug report or even a post to the mailing list of an open source project: + + * be polite, + * don't make threaths or demands, and + * show some respect for the souls who dedicate their time to making something awesome. + +**Open Source is a privilege. Not a right.** \ No newline at end of file diff --git a/content/posts/2012-03-21-from-11-34s-to-0-625s-for-opening-a-rb-file-in-vim.md b/content/posts/2012-03-21-from-11-34s-to-0-625s-for-opening-a-rb-file-in-vim.md new file mode 100644 index 0000000..f371477 --- /dev/null +++ b/content/posts/2012-03-21-from-11-34s-to-0-625s-for-opening-a-rb-file-in-vim.md @@ -0,0 +1,53 @@ +--- +title: "From 11.34s to 0.625s for opening a .rb file in Vim" +kind: article +slug: from-11-34s-to-0-625s-for-opening-a-rb-file-in-vim +created_at: 2012-03-21 +tags: + - Ruby + - vim +--- +Would you believe me if I told you that opening a simple Ruby file on my 2011 MacBook Pro takes 11.34 seconds? + +To test this, I've used this command: + + $ vim --startuptime log-before.txt app/models/user.rb + +This command will time everything that Vim does until the file is ready for you to edit down to the millisecond. This is a great way to find out what's slowing things down. + +I'll highlight the most interesting parts of [`log-before.txt`](https://gist.github.com/2147190#file_log_before.txt) here: + + 000.028 000.028: --- VIM STARTING --- + 6643.597 5496.976 5496.976: sourcing /usr/share/vim/vim73/ftplugin/ruby.vim + 11262.686 3963.993 3963.993: sourcing /Users/ariejan/.vim/bundle/vim-css-color/after/syntax/css.vim + 11263.907 3976.912 004.334: sourcing /usr/share/vim/vim73/syntax/html.vim + 11263.997 3977.361 000.449: sourcing /usr/share/vim/vim73/syntax/xhtml.vim + 11340.533 000.004: --- VIM STARTED --- + +These are the _big spenders_ of loading a ruby file. Firstly there is `ruby.vim` taking about 5.4 seconds to load. Then there is `css.vim` taking another 3.9 seconds - and this file doesn't even include CSS! + +These two time sinking hogs are keeping me back – 11 seconds at a time. + +Let's see, `vim-css-color`. This plugin shows color hashes in their actual colour. So `#00f` will have a blue background. Great when editing CSS files, but not all that import. I removed `vim-css-color`. + +_Note: the reason `vim-css-color` is slow with terminal vim is that is has to pre-compile colour hashes to ther xterm escape code equivalents. This is pretty time consuming._ + +Next up: `ruby.vim`. Why is this so bloody slow? + +As it turns out, Vim has trouble finding the right `ruby` for me. This can be remedied by adding the following snippet to your `~/.vimrc`. It sets a logical search path for `ruby`: + + if !empty($MY_RUBY_HOME) + let g:ruby_path = join(split(glob($MY_RUBY_HOME.'/lib/ruby/*.*')."\n".glob($MY_RUBY_HOME.'/lib/ruby/site_ruby/*'),"\n"),',') + endif + +Again I ran my timer command ([full output](https://gist.github.com/2147190#file_log_after.txt)): + + $ vim --startuptime log-after app/models/user.rb + +Now look at that: + + 000.034 000.034: --- VIM STARTING --- + 107.182 000.834 000.834: sourcing /usr/share/vim/vim73/ftplugin/ruby.vim + 625.001 000.003: --- VIM STARTED --- + +Yes, that is 0.625 seconds! I'm a happy ruby coder again. \ No newline at end of file diff --git a/content/posts/2012-03-21-redis-using-2gb-of-memory-on-70mb-data-set-the-fix.md b/content/posts/2012-03-21-redis-using-2gb-of-memory-on-70mb-data-set-the-fix.md new file mode 100644 index 0000000..6028d98 --- /dev/null +++ b/content/posts/2012-03-21-redis-using-2gb-of-memory-on-70mb-data-set-the-fix.md @@ -0,0 +1,51 @@ +--- +title: "Redis using 2GB of memory on 70MB data set - the fix" +kind: article +slug: redis-using-2gb-of-memory-on-70mb-data-set-the-fix +created_at: 2012-03-21 +tags: + - redis + - memory + - malloc + - jemalloc +--- +For [Ariejan.net](http://ariejan.net) I use [redis](http://redis.io) to cache pages and shards. This works great and all, but today I noticed something alarming: + + redis Running 3d 12h 4m 0.0% 45.2% [1829556 kB] + +Yes, that's about 1.7 GB of RAM. That's way too much for what I cache. Let's see what redis has to say for itself: + + $ redis-cli + redis 127.0.0.1:6379> info + redis_version:2.2.12 + ... + connected_clients:5 + connected_slaves:0 + used_memory:71626608 + used_memory_human:68.31M + used_memory_rss:1873465344 + mem_fragmentation_ratio:26.16 + +Well, that's awkward. The OS is reporting 1.7GB memory usage, while Redis claims to store a mere 68MB. What's happening here! + +You may have noticed that I included `mem_fragmentation_ratio` in the snippet above as well. It's at a whopping 26.16, meaning that for every byte I store, 26.16 bytes of memory are used. This explains the 1.7GB memory usage. + +But, how do I get rid of this? My system has enough RAM to cope redis as is, but it's not a comforting thought to leave Redis running like this. + +As it turns out, there isn't a lot you can do about this. Redis 2.2 uses _malloc_ which causes the fragmentation. One alternative is to add a slave redis-server, migrate your data and then switch the slave to master. Although this is reported to work well, it's not a good solution to the problem. + +Fortunately, Redis 2.4 on Linux by default does not use _malloc_ anymore. Instead it uses _jemalloc_. From the redis README: + +> Redis is compiled and linked against libc malloc by default, with the exception of jemalloc being the default on Linux systems. This default was picked because jemalloc has proven to have fewer fragmentation problems than libc malloc. + +The only logical step to take is to upgrade to Redis 2.4. + +If you're already running (or just upgraded to) 2.4 you can easily check if your redis is using _jemalloc_: + + $ redis-cli + redis 127.0.0.1:6379> info + redis_version:2.4.9 + mem_fragmentation_ratio:1.11 + mem_allocator:jemalloc-2.2.5 + +I've taken this step and the `mem_fragmentation_ratio` samples I've measured have all been in the 1.1-1.4 regions. diff --git a/content/posts/2012-04-02-showing-ruby-rails-and-git-info-in-your-app.md b/content/posts/2012-04-02-showing-ruby-rails-and-git-info-in-your-app.md new file mode 100644 index 0000000..5fd33c8 --- /dev/null +++ b/content/posts/2012-04-02-showing-ruby-rails-and-git-info-in-your-app.md @@ -0,0 +1,74 @@ +--- +title: "Showing Ruby, Rails and git info in your app" +kind: article +slug: showing-ruby-rails-and-git-info-in-your-app +created_at: 2012-04-02 +tags: + - Ruby + - Rails + - protip +--- +Some people've asked me how I show rendering information on [ariejan.net](http://ariejan.net). + +![](https://ariejannet.s3.amazonaws.com/images/render_stats.jpg) + +There are a few things going on here, let me explain them one by one. + +### Rails version + +The current Rails version is probably the easiest you see here. Rails exposes its version information like this: + + :::ruby + Rails.version + +### Ruby version + +Ruby also exposes version information, albeit using constants: + + :::ruby + RUBY_VERSION + => "1.9.3" + +You may know that ruby also has different patch levels for each release. You can also retrieve that information: + + :::ruby + RUBY_PATCHLEVEL + => 125 + +The you may also want to know which engine you're using. This may be "ruby" or something different: + + :::ruby + RUBY_ENGINE + => "ruby" + +Combine these to get a sexy ruby version string: + + :::ruby + puts "#{RUBY_ENGINE}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" + => "ruby-1.9.3-p125" + +### Current process ID + +I like to know which Unicorn produced a certain page. Retrieving the process ID is easy: + + :::ruby + Process.pid + => 9473 + +### Current git revision SHA + +Knowing which version of your app is currently running can be useful information. To do this I created the following initializer: + + :::ruby + # config/initializers/git_revision.rb + module AppName + REVISION = `git log --pretty=format:'%h' -n 1` + end + +This will expose the current short SHA in your application's namespace: + + :::ruby + AppName::REVISION + => "ac6d3a0" + +You can now use this info through-out your app to show version information. \ No newline at end of file diff --git a/content/posts/2012-04-06-eindhoven-rb-lightning-talk-gitlab.md b/content/posts/2012-04-06-eindhoven-rb-lightning-talk-gitlab.md new file mode 100644 index 0000000..d0dbdc4 --- /dev/null +++ b/content/posts/2012-04-06-eindhoven-rb-lightning-talk-gitlab.md @@ -0,0 +1,12 @@ +--- +title: "Eindhoven.rb Lightning Talk: Gitlab" +kind: article +slug: eindhoven-rb-lightning-talk-gitlab +created_at: 2012-04-06 +tags: +--- +These are the slides of my [Eindhoven.rb](http://eindhovenrb.nl) lightning talk. The topic is Gitlab, why it's awesome and how it relates to Github. + +If you have any questions about Gitlab, feel free to contact me or visit the [Gitlab Mailinglist](https://groups.google.com/forum/#!forum/gitlabhq) + + \ No newline at end of file diff --git a/content/posts/2012-04-14-decorating-devise-s-current_user-with-draper.md b/content/posts/2012-04-14-decorating-devise-s-current_user-with-draper.md new file mode 100644 index 0000000..c0aa368 --- /dev/null +++ b/content/posts/2012-04-14-decorating-devise-s-current_user-with-draper.md @@ -0,0 +1,25 @@ +--- +title: "Decorating Devise's current_user with Draper" +kind: article +slug: decorating-devise-s-current_user-with-draper +created_at: 2012-04-14 +tags: + - Rails + - devise + - draper + - decorators +--- +I've become a big fan of decorators, especially [Draper](https://github.com/jcasimir/draper). + +Decorators allow you to move view related functionality for your models in to separate decorator classes. This keeps both your models and views clean and readable. + +Anyway, if you use Devise you're provided with a `current_user` helper. However, this helper returns an instance of `User` - without your decorators. To enable decorators for your `current_user` by default, simple add this to `app/controllers/application_controller.rb`: + + :::ruby + def current_user + UserDecorator.decorate(super) unless super.nil? + end + +Now, anywhere in your views where you call `current_user` you'll get a decorated version instead. + +[Check here to see how to use Draper with Sorcery](http://ariejan.net/2012/11/02/decorating_sorcery_current_user_with_draper) \ No newline at end of file diff --git a/content/posts/2012-05-11-running-a-different-ruby-with-passenger-3-2-and-rvm.md b/content/posts/2012-05-11-running-a-different-ruby-with-passenger-3-2-and-rvm.md new file mode 100644 index 0000000..1ca145f --- /dev/null +++ b/content/posts/2012-05-11-running-a-different-ruby-with-passenger-3-2-and-rvm.md @@ -0,0 +1,56 @@ +--- +title: "Running a different ruby with Passenger 3.2 and RVM" +kind: article +slug: running-a-different-ruby-with-passenger-3-2-and-rvm +created_at: 2012-05-11 +tags: + - Ruby + - rvm + - passenger + - nginx +--- +Passenger 3.2 will have quite some nice new features. [1](http://blog.phusion.nl/2012/04/13/a-sneak-preview-of-phusion-passenger-3-2/) [2](http://blog.phusion.nl/2012/04/25/a-sneak-preview-of-phusion-passenger-3-2-part-2/) + +The features I'm looking forward to most is the ability to specify - per virtual server - which ruby to use. + +Before, you installed passenger and specified the required ruby version using `passenger_ruby`, like this in your `nginx.conf`: + + http { + passenger_root /opt/passenger; + passenger_ruby /usr/local/bin/ruby; + + server { + server_name ariejan.net; + passenger_enabled on; + } + } + +Now, if you added another server it would be forced to use the same ruby version. This might be okay for most servers, but for me not so much. I have several side-projects running on a single machine, and using only one ruby version is not optimal or even impossible. + +Now, with the upcoming Passenger 3.2 you can select a ruby version *per server*. This is awesome. All you have to do is move the `passenger_ruby`directive into the `server` block and all is set. Of course, you can leave the globally set ruby just as is. + + http { + passenger_root /opt/passenger; + passenger_ruby /usr/local/bin/ruby; + + server { + server_name ariejan.net; + passenger_enabled on; + passenger_ruby /home/deployer/.rvm/rubies/ruby-1.9.3-p194/bin/ruby; + } + } + +As you can see in the example above, I'm referencing ruby-1.9.3-p194, installed with RVM. + +### Installing "experimental" Passenger + +The installation is easy, as usual, but you must checkout the passenger source from Github and use the `experimental` branch. + +**Warning: do not install the `experimental` branch of Passenger on your production server unless you are absolutely sure what you're doing and you know how to rollback quickly and easily to a stable version of passenger.** + + cd /opt + git clone https://github.com/FooBarWidget/passenger.git + cd /opt/passenger + git checkout -b experimental origin/experimental + ./bin/passenger-install-nginx-module + diff --git a/content/posts/2012-05-19-what-happened-to-downloading-games-from-the-pirate-bay.md b/content/posts/2012-05-19-what-happened-to-downloading-games-from-the-pirate-bay.md new file mode 100644 index 0000000..82055af --- /dev/null +++ b/content/posts/2012-05-19-what-happened-to-downloading-games-from-the-pirate-bay.md @@ -0,0 +1,31 @@ +--- +title: "What happened to downloading games from The Pirate Bay?" +kind: article +slug: what-happened-to-downloading-games-from-the-pirate-bay +created_at: 2012-05-19 +tags: + - piracy + - diablo3 + - steam +--- +This post is on piracy. Piracy of software, games, movies, music and other virtual goods. + +Before I continue writing this post let me confess to you that I used to use pirated software. I played games downloaded from the internet. And movies I watches 'on demand' by downloading them when I wanted to. There were times when there were no legally purchased songs on my iPod. + +After reinstalling my Mac a few weeks ago I noticed something remarkable. There is not a single piece of pirated software on my Mac. Everything has been paid for or is free / open-source. The same goes for my gaming rig, all games are legally purchased. + +This is in shrill contrast to a few years ago. What the fuck happened? + +Then I saw Diablo 3 was released. I'm not a huge fan of the franchise, but wanted to play Diablo 3 anyway. So, what to do. + +Checking with local and online retailers I can get a fair price of €49 euro - but the game is either sold out or will not be delivered in the next few days. I want to play Diablo 3 ** RIGHT THE FUCK NOW**. + +Okay, let's check with Blizzard then, the Battle.net store. They have the digital download version of Diablo 3, but for €59. It is instantly available, though. + +Clickety-Click, Diablo 3 purchased. Download ready in 16 minutes. Playing 5 minutes later. + +It's been said many times before, and it will be said many times again: _buying digital goods should be easy, fast and secure._ + +I've amazed myself by forking out an additional €10 to get easy access to Diablo 3. But I'm a happy customer and I bet Blizzard is also happy to have sold another Diablo 3 copy. + +Hopefully the entertainment industry will get this message soon. Because movies are the only thing that are still available to me when I want them through The Pirate Bay. \ No newline at end of file diff --git a/content/posts/2012-05-20-why-diablo-3-s-drm-is-the-best-kind-there-is.md b/content/posts/2012-05-20-why-diablo-3-s-drm-is-the-best-kind-there-is.md new file mode 100644 index 0000000..14357a2 --- /dev/null +++ b/content/posts/2012-05-20-why-diablo-3-s-drm-is-the-best-kind-there-is.md @@ -0,0 +1,30 @@ +--- +title: "Why Diablo 3's DRM is the best kind there is" +kind: article +slug: why-diablo-3-s-drm-is-the-best-kind-there-is +created_at: 2012-05-20 +tags: + - error + - diablo3 + - rage + - drm +--- +I just tried to play Diablo 3, but could not log on due to server maintenance. Blizzard requires you to be _always online_ to play even single player campaigns. I've seen a lot of people rage about this, but Blizzard actually has very good reasons to do this. And they even kept you, as a player, in mind! + +![Diable 3 Rage](https://s3-eu-west-1.amazonaws.com/ariejannet/images/diablo_3_rage.jpg) + +First of all, what Blizzard is doing is not really DRM. + +Digital Rights Management is making sure you have authorization (from the providing party) to do something off-line. The best example is music. After _authorizing_ your computer you can play your DRM protected music. + +What Blizzard requires is you logging on to one of their servers to play Diablo 3. This sounds like DRM, but you are actually using Blizzard's servers to play. And for good reason! + +Diablo 3 contains several features that allow you to interact with other players. For example, you can easily interact with your friends and you can even join their games. There's also the Auction House, where you can trade items. + +These features are nothing new to World of Warcraft players, and it's not surprising they require a central server to make sure everything is fair. + +An added benefit of this _always online_ model is that Blizzard can closely monitor how players behave in the game, allowing them to fine-tune game balance with every update the release. + +So, there are many _wins_ for the player here. The big win for Blizzard is that they can be selective about whom they allow to enter the server: only people with a valid license key. + +I know it's frustrating that you can't play Diablo 3 on a rainy sunday afternoon when you want to. But with a modern, social game like Diablo 3, being online is an absolute must. \ No newline at end of file diff --git a/content/posts/2012-05-21-what-s-causing-all-that-disk-i-o-on-my-mac.md b/content/posts/2012-05-21-what-s-causing-all-that-disk-i-o-on-my-mac.md new file mode 100644 index 0000000..9282768 --- /dev/null +++ b/content/posts/2012-05-21-what-s-causing-all-that-disk-i-o-on-my-mac.md @@ -0,0 +1,36 @@ +--- +title: "What's causing all that disk I/O on my Mac?" +kind: article +slug: what-s-causing-all-that-disk-i-o-on-my-mac +created_at: 2012-05-21 +tags: + - osx + - mac + - disk-io + - troubleshooting +--- +After doing a full re-install of my MacBook Pro a few weeks back to combat system slowness, I'm again struck with a slow system. + +The problem is that there's a lot of disk I/O going on. CPU and memory are fine. I'm just not sure what's causing this trouble. + +Luckily, there's an easy solution to find out what's doing disk I/O on you mac. + +Open up a terminal and run + + $ sudo fs_usage + 09:08:45.749208 read F=12 B=0x400 0.000210 W Sparrow.3845544 + 09:08:45.749274 lseek F=12 O=0x0602e000 0.000002 Sparrow.3845544 + 09:08:45.749279 read F=12 B=0x400 0.000005 Sparrow.3845544 + 09:08:45.749341 lseek F=12 O=0x06038c00 0.000002 Sparrow.3845544 + 09:08:45.750358 RdData[async] D=0x025aaf38 B=0x1000 /dev/disk0s2 0.028830 W Sparrow.3845547 + 09:08:45.750379 pread F=16 B=0x30 O=0x00b32730 0.028895 W Sparrow.3845547 + 09:08:45.750390 pread F=16 B=0x30 O=0x0de9a810 0.000008 Sparrow.3845547 + 09:08:45.750396 pread F=16 B=0x30 O=0x1f5ec5c0 0.000006 W Sparrow.3845547 + 09:08:45.750399 pread F=16 B=0x30 O=0x236c84a0 0.000003 Sparrow.3845547 + 09:08:45.750405 pread F=16 B=0x4b O=0x236c84b6 0.000002 Sparrow.3845547 + 09:08:45.750701 pread F=16 B=0x30 O=0x00c95f00 0.000007 Sparrow.3845547 + 09:08:45.758502 RdData[async] D=0x04f0b608 B=0x1000 /dev/disk0s2 0.009148 W + +What you see here is Sparrow doing mad reads ot my disk. I'm not sure what it's doing, but it must stop. After killing Sparrow disk I/O goes down to normal, workable levels again. + +Now, to pick another mail client for Mac... \ No newline at end of file diff --git a/content/posts/2012-05-22-ruby-regex-scanning-in-a-case-statement.md b/content/posts/2012-05-22-ruby-regex-scanning-in-a-case-statement.md new file mode 100644 index 0000000..18850ef --- /dev/null +++ b/content/posts/2012-05-22-ruby-regex-scanning-in-a-case-statement.md @@ -0,0 +1,20 @@ +--- +title: "Ruby: regex scanning in a case statement" +kind: article +slug: ruby-regex-scanning-in-a-case-statement +created_at: 2012-05-22 +tags: + - Ruby + - snippet +--- +Here's a handy ruby snippet that might come in handy one day. + +When the regex matches (input should end with " today"), you can directly grab the matched value using the special `$1` variable. + + :::ruby + case input + when /(.*)\stoday$/i then + puts "Today: #{$1}" + end + +I think you can see how you can bend this to your own needs. \ No newline at end of file diff --git a/content/posts/2012-05-23-how-star-trek-has-shaped-our-technology-and-future.md b/content/posts/2012-05-23-how-star-trek-has-shaped-our-technology-and-future.md new file mode 100644 index 0000000..199825d --- /dev/null +++ b/content/posts/2012-05-23-how-star-trek-has-shaped-our-technology-and-future.md @@ -0,0 +1,51 @@ +--- +title: "How Star Trek has shaped our technology and future" +kind: article +slug: how-star-trek-has-shaped-our-technology-and-future +created_at: 2012-05-23 +tags: + - star trek + - sci-fi + - wtfomg +--- +You have probably watched Star Trek. Even if you don't like it, I bet you've seen at least a few episodes of _The Next Generation_ or a movie. + +Regardless of you opinion on Star Trek, its cool to see that current technology was only Science-Fiction ten or twenty years ago. + +Let's start with a simple example - the Communicator. Press a button and talk to anyone wirelessly, wherever they are. Reminds you of that mobile phone you carry around all day, doesn't it? + +Well, if that doesn't convince you, then check out the [Vocera B3000 Communication Bagde](http://www.vocera.com/) + +But there's more. Are you afraid of needles? Wouldn't it be totally cool if they didn't have to stick metal tubes into your arm to give you meds? Well, the can! Enter [SonoPrep](http://www.sontra.com/technology/) (Sorry, their site appears to be down). It'll weaking your sking in 15 seconds and you're ready to receive meds. You won't feel a thing. + +Warp Speeds? Try [Hyperdrive](http://en.wikipedia.org/wiki/Hyperdrive). + +Cloaking devices? [Got it](http://www.networkworld.com/community/node/13348?nwwpkg=slideshows) + +Phasers? [Check](http://latimesblogs.latimes.com/technology/2011/04/navy-laser-weapon-fire-.html) + +Medical Tricorder? [Sure, we can](http://www.networkworld.com/community/node/18946?nwwpkg=slideshows) + +Transparent Aluminium? [Yes, please](http://en.wikipedia.org/wiki/Aluminium_oxynitride) + +Okay, tractor beams then? [Oh, yeah](http://www.stanford.edu/group/blocklab/Optical%20Tweezers%20Introduction.htm) + +A huge library where you can just play about any tune in history? [Sweet mother of god, yes](http://www.spotify.com) + +Hand-Held devices that tell you your current location? [We have those too](https://buy.garmin.com/shop/shop.do?pID=63349&ra=true) + +PADD's? [Oh, yes!](http://www.apple.com/ipad/) + +Warp Core's and photon torpedo's? We'd need [Antimatter](http://en.wikipedia.org/wiki/Antimatter) for that, so we're getting there. + +I must admit that I was quite surprised to find some of these things already exist in current technology! I wonder how long it will take before we build our first _"USS Enterprise"_. + +Well, about 20 years. + +[Build the Enterprise](http://www.buildtheenterprise.org/) has thought about building a huge, Enterprise like, star ship. It's entirely feasable to built on in the next 20 years with proper funding (about half of what NASA got during its Apollo missions). + +Theoretically the Enterprise could be ready in 20 years. It could then take you to Mars in 90 days. The moon would take you 'only' 3. + +The future is now. + +Note: Probably no one can tell if a specific technology or device was inspired by Star Trek or not. But it's very fascinating to see that stuff that was once thought-up by [Gene Roddenberry](http://en.wikipedia.org/wiki/Gene_Roddenberry) (and others) are already a reality. \ No newline at end of file diff --git a/content/posts/2012-05-24-seo-is-bullshit.md b/content/posts/2012-05-24-seo-is-bullshit.md new file mode 100644 index 0000000..09e2b7d --- /dev/null +++ b/content/posts/2012-05-24-seo-is-bullshit.md @@ -0,0 +1,63 @@ +--- +title: "SEO is bullshit" +kind: article +slug: seo-is-bullshit +created_at: 2012-05-24 +tags: + - Opinion + - seo + - rant +--- + + + + +I get quite a lot of "oh we can SEO optimize your site for you" emails lately. I don't know why, but **SEO is bullshit**. Really. SEO is one of those _areas of expertise_ that are total rubbish. + +## Common SEO advice + +The most common advise regarding SEO includes: + + * Getting links to your site + * Add tons of keywords so the Google Crawler knows what you're talking about + * Add META data, lots of it + * Structure your page in some way for crawlers to understand better + * Add an XML sitemap + +There might be some merit in this, but you are optimizing your site for a _crawler_, a _computer program_ that interprets your site. Are crawlers your primary audience? If so, you have a sad site. + +_SEO Hacking_ is another common term I find in emails. SEO hacking tries to trick search engines to list your site higher for certain keywords. I won't go into the details, but basically you try to cheat a search engine's indexing rules. + +Some shady SEO companies even offer you a guarantee that you'll be the number 1 listen for certain keywords. Really? But how valuable is that? + +The problem with doing a lot of SEO hacks on your site is this: search engines and crawlers are continuiously updated to prevent hacking. Although this is not disclosed by Google, there are rumours that if you do actually use 'SEO hacks', you're site will be ranked lower, instead of higher. + +## The best SEO advice you can get + +So, what can you do to make people find your site? Simple: + +**Write. Good. Content.** + +Use headers like `

    ` and `` and `` tags to highlight import terms or words in your text — this is valuable to your readers as well. + +If your content is good, readers will find it. Link to it. Tweet about it. And crawlers will _always_ be able to index your site properly. Of course, it helps to tell people about your article using social media and other news sites for your niche. + +## So, not META-tags you say? + +No, that's not whaty I'm saying. META tags _do_ prodide information to crawlers, you should not assume they use that information in any way. For example, Ariejan.net has these META-tags: + + :::html + + + +In Google, this results in a view like this: + +![Google Search Result for Ariejan.net](https://ariejannet.s3.amazonaws.com/images/ariejan-seo-example.jpg) + +Do note that the posts listed are considered popular by Google. Those are the post that are most visited and linked to on my site. + +## Conclusion + +If you get an email, promising you that for $200 you're site will be ranked nr. 1 for specific keywords: throw it away. + +The best SEO is writing awesome content people want to read. \ No newline at end of file diff --git a/content/posts/2012-06-18-search-and-replace-in-multiple-files-with-vim.md b/content/posts/2012-06-18-search-and-replace-in-multiple-files-with-vim.md new file mode 100644 index 0000000..29493ca --- /dev/null +++ b/content/posts/2012-06-18-search-and-replace-in-multiple-files-with-vim.md @@ -0,0 +1,45 @@ +--- +title: "Search and Replace in multiple files with Vim" +kind: article +slug: search-and-replace-in-multiple-files-with-vim +created_at: 2012-06-18 +tags: + - Rails + - vim +--- +I recently learned a nice VimTrick™ when paring with [Arjan](http://arjanvandergaag.nl). We upgrade an app to Rails 3.2.6 and got the following deprecation message: + + DEPRECATION WARNING: :confirm option is deprecated and will be removed from Rails 4.0. + Use ':data => { :confirm => 'Text' }' instead. + +Well, nothing difficult about that, but we have quite a few `:confirm` in this app. + +Firstly we checked where we used them (note we use ruby 1.9 hash syntax everywhere): + + $ ack -l "confirm:" app + +Now you have a listing of all the files that contain the `:confirm` hash key. You can leave out the `-l` option to get some context for each find. + +Now, we need to open Vim with those files: + + $ ack -l "confirm:" app | xargs -o vim + +Vim will open the first of these files. Here's a snippet of what you may find: + + = link_to "Delete", something_path, confirm: "Are you sure?" + +Now, search and replace is easy: + + :%s/confirm: ".*"/data: { & }/g + +This will surround the current confirm with the `data` hash. Just the way Rails likes it. The `&` character will be replaced with whatever text matched the search pattern. + +You could repeat this for every file manually. But, you're using Vim. + + :argdo %s/confirm: ".*"/data: { & }/g | update + +This will perform the search and replace on each of the supplied arguments (in this case the files selected with `ack`) and update (e.g. save) those files. + +Now you can quit Vim and enjoy the glory of all the disappearing deprecation warnings. + +Note: to do this with the ruby 1.8 hash syntax, just update the search and replace texts accordingly. \ No newline at end of file diff --git a/content/posts/2012-07-11-vpn-too-complicated-use-a-ip-over-ssh-tunnel-instead.md b/content/posts/2012-07-11-vpn-too-complicated-use-a-ip-over-ssh-tunnel-instead.md new file mode 100644 index 0000000..66b1671 --- /dev/null +++ b/content/posts/2012-07-11-vpn-too-complicated-use-a-ip-over-ssh-tunnel-instead.md @@ -0,0 +1,64 @@ +--- +title: "VPN too complicated? Use a IP-over-SSH tunnel instead" +kind: article +slug: vpn-too-complicated-use-a-ip-over-ssh-tunnel-instead +created_at: 2012-07-11 +tags: + - ssh + - vpn + - hacks + - tunnel +--- +Some times you find yourself in a place where your Mac is safely tucked away behind a firewall. That's great, but sometimes it is annoying as hell, because you _need_ to access resources over FTP or contact people who're on IRC. + +The normal solution would be to setup a VPN with one of your servers elsewhere and connect to the outside world that way. Unfortunately, in all their wisdom, sys admins have probably closed up the proper ports to access your VPN server as well. + +As a last resort you might consider setting up a SSH Tunnel for a specific service like this: + + ssh -N user@server -L 3306:127.0.0.1:3306 + +But, this only works for a single port, and thus application. It may help, but it can become tedious pretty quickly. You also have to rewrite any configuration you had for connection to a remote host to use your localhost, most likely on some strange port. + +Luckily for us, there's this awesome tool called [sshuttle]. + +Sshuttle allows you to setup what's called IP-over-SSH. Basically it runs a local proxy server to a remote server over SSH and changes the routing for your machine to send everything through that proxy. + +Besides giving you access to all the services you need, you also encrypt (e.g. hide) all your traffic from the prying eyes of any sys admins on the local network. + +Installing sshuttle on your Mac is a breeze + + $ brew install sshuttle + +Then you can setup an IP-over-SSH connection to any remote server you have SSH access to. You'll need your local admin password in order to setup routing properly. + + $ sshuttle -r username@server 0/0 -vv + +This routes all traffic over the tunnel towards `server`. Use on of those online ip checkers to see that you're actually using your `server`'s IP address. + +In the future you may want to change the `-vv` verbose option out and swap in `-D` to run in daemon mode. + +The one thing this does _not_ do is DNS. DNS is still done using your locally configured DNS server, mostly for speed. + +Not to worry, you can go 'full stealth' with the `--dns` options, which also routes DNS over to the remote server: + + $ sshuttle --dns -r username@server 0/0 -vv + +To stop using your IP-over-SSH connection, simply press CTRL-C twice and sshuttle should restore your normal networking connections. + +If sshuttle does not restore the connection properly, you can do so manually: + + $ sudo ipfw -q -f flush + +I've already create a few aliases in my `~/.zshrc`: + + alias tunnel='sshuttle -r ariejan@server 0/0 -vv' + alias tunnel_dns='sshuttle --dns -r ariejan@server 0/0 -vv' + alias reset_tunnel='sudo ipfw -q -f flush' + +So, no need to setup complicated VPN contraptions, just use plain old SSH and off you go. + +Bonus: you can also connect to a non-standard SSH port, in case port 22 has been blocked in the firewall as well: + + $ sshuttle --dns -r username@server:port 0/0 -vv + +[sshuttle]: https://github.com/apenwarr/sshuttle/ \ No newline at end of file diff --git a/content/posts/2012-08-14-move-your-latest-commits-to-a-separate-branch.md b/content/posts/2012-08-14-move-your-latest-commits-to-a-separate-branch.md new file mode 100644 index 0000000..f2663f3 --- /dev/null +++ b/content/posts/2012-08-14-move-your-latest-commits-to-a-separate-branch.md @@ -0,0 +1,29 @@ +--- +title: "Move your latest commits to a separate branch" +kind: article +slug: move-your-latest-commits-to-a-separate-branch +created_at: 2012-08-14 +tags: + - git + - protip +--- +The situation is pretty straightforward. You have been making commits for that new feature in your `master` branch. Naughty you! + +Let's assume you want to have this: + + A - B - (C) - D - E - F + +`C` was the last commit you pulled from `origin` and D, E and F are commits you just made but should have been in their own branch. This is what you wanted: + + A - B - (C) + \ D - E F + +Step 1: Assuming you're at `F` on `master`, create a new branch with those commits: + + git branch my_feature_branch + +Then, still on `master`, reset back to commit `C`. This is 3 commits back. + + git reset --hard HEAD~3 + +Okay, you're `master` is now back at `C`, which you lasted pulled, and the `my_feature_branch` includes D, E and F. Just checkout `my_feature_branch` and continue work as usual. I'm sure no one saw what you just did. \ No newline at end of file diff --git a/content/posts/2012-08-27-getting-started-with-arduino.md b/content/posts/2012-08-27-getting-started-with-arduino.md new file mode 100644 index 0000000..4b459d6 --- /dev/null +++ b/content/posts/2012-08-27-getting-started-with-arduino.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Arduino" +kind: article +slug: getting-started-with-arduino +created_at: 2012-08-27 +tags: + - arduino +--- +I'm a software engineer. I don't do hardware. + +Well, that used to be the case. I did build some PC's back in the day, but that's where my involvement with electronics hardware ended. That's all changed after I purchased an [Arduino][1]. + +## Arduino + +Arduino is an easy to use hardare _and_ software platform for designers, developers and tinkerers. What? + +Well, Arduino is more than just a small piece of electronics hardware. It does include hardware. This is the Arduino Uno, the latest incarnation and the board you'll most likely purchase if you want to break into the world of Arduino + +![The Arduino Uno](https://ariejannet.s3.amazonaws.com/images/arduino/impression.jpeg) + +This Arduino has some nice features you should know about. At its core it has the [Atmel Atmega328][2] chip. It has 32Kb of flashmemory and usually runs at 16MHz. Now this may not sound like much, but note that this is not like your average Intel Core i7 processor. This chip does several things for you, but the most important part is that you can program it to interact with the outside word through its 14 I/O pins. It gets its power over USB or from a battery that can supply 7-12V. + +As I said earlier, Arduino is more than just the hardware board. It also contains software. Software on the Atmega328 chip, called the bootloader, allows you to easily interact with the Arduino from your computer. Then there is the Arduino IDE, which you can install on Mac, Linux and Windows. This is the tool you write your code with and which allows you to upload that code to the Arduino. + +Now the great part is that both the hardware designs and the software are open-source. This means that you're free to buy all the separate components for an Arduino board and solder one yourself . You may even sell it if you like. The only catch is that you can't use the name _Arduino_. + +## An example + +The most basic thing you can do with Arduino is hook up a LED and resistor and make the LED blink. + +This is what that would look like if you'd build it: + +![Arduino Blink Setup](https://ariejannet.s3.amazonaws.com/images/arduino/ExampleCircuit_bb.png) + +This is the electrical schematic for this setup. It's very straight forward, as you can see, but enough to get you started. Even if you don't yet know how LED's work or what a resistor does, you can make this easily enough. + +_Note: if you do want to build this, I recommend you check out the [Blink tutorial][3] and buy an Arduino Starter Kit, which includes all the components to get started._ + +For those a bit tech-savvy, here's the electrical schematic for the Blink tutorial. I'm not going to explain it in to detail, but note that the led and resistor are connected to `D13` or pin 13 on the Arduino. + +![Arduino Blink Schematic](https://ariejannet.s3.amazonaws.com/images/arduino/ExampleCircuit_sch.png) + +With the following, relatively simple code, you can make that LED blink. + + :::c + int led = 13; // This refers to pin 13. + + // setup() is run once during start-up + void setup() { + // initialize the digital pin as an output. + pinMode(led, OUTPUT); + } + + // the loop routine runs over and over again forever: + void loop() { + digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level) + delay(1000); // wait for a second + digitalWrite(led, LOW); // turn the LED off by making the voltage LOW + delay(1000); // wait for a second + } + +After making this first step you'll get to know more components and features of Arduino. You'll start using sensors (buttons, light sensors, sound sensors, GPS, magnetometers, gas sensors) and you'll start using actuators (motors, steppers, relais, LED matrixes). The possibilities are endless. + +## The next step + +The great thing about Arduino is that it's designed to be easy to use. But is also offers great possibilities. + +For one, the Arduino Uno has only got 14 digital and 6 analog I/O pins. When you start using more other components you'll run out of I/O pins quickly. Enter the Arduino Mega which sports 54 digital and 16 analog I/O pins. Need something smaller: Arduino Nano, Need something to integrate into clothing: Arduino Lilypad. Need something custom: it's frickin' open-source, so design your own PCB, add the Arduino components you need and you're good to go. + +The things that are being doing with Arduino are amazing. The most important thing is that it makes electronics tinkering available to more people. It's pretty cheap to get started and learn about electronics. Then there are hundreds (if not more) possibilities to use Arduino, ranging for fancy LED matrix animations to the full-grown [Lawn Bot 400][4]: + +![Lawn Bot 400](https://ariejannet.s3.amazonaws.com/images/arduino/lawnbot.jpeg) + +## What's got all this to do with Ariejan.net? + +Of course, I'm a Rails web developer, but Arduino has sparked my interest. + +Over at Kabisa we've already done a small side-project using Arduino to scan and log RFID-tags - we'll be deploying this at a Rails conference later this year (and we'll be using it to give away some awesome prizes). + +In the scarce spare-time I have I'm planning on going beyond the blinking LED and build a 4WD Robot. I'll post my progress here at Ariejan.net. Keep an eye on the [Arduino section][5] for updates. + +It'll take me some months to get my robot driving and doing the stuff I want it to do. But my dream project is building a Quadcopter. + +## What about you? + +You are still reading this? And you didn't get started with Arduino yet? Come on, find [your local Arduino distributor][6] or head over [http://arduino.cc][1] and get one. Start doing the tutorials and be ready to be pulled into the world of tinkering and hobby electronics. It's fun! + +_Images have been taken from [arduino.cc][1] with not so much permission._ + +[1]: http://arduino.cc/ +[2]: http://www.atmel.com/devices/atmega328.aspx +[3]: http://arduino.cc/en/Tutorial/blink +[4]: http://www.instructables.com/id/Arduino-RC-Lawnmower/ +[5]: http://ariejan.net/posts/tagged/arduino +[6]: http://arduino.cc/en/Main/Buy \ No newline at end of file diff --git a/content/posts/2012-08-28-rails-migrations-decimal-precision-and-scale.md b/content/posts/2012-08-28-rails-migrations-decimal-precision-and-scale.md new file mode 100644 index 0000000..e349069 --- /dev/null +++ b/content/posts/2012-08-28-rails-migrations-decimal-precision-and-scale.md @@ -0,0 +1,24 @@ +--- +title: "Rails migrations: decimal precision and scale" +kind: article +slug: rails-migrations-decimal-precision-and-scale +created_at: 2012-08-28 +tags: + - Rails + - migrations + - postgresql +--- +I'm always confused when using `decimal` in a Rails migration. Normally I need to store a value that has 2 or 3 numbers behind the comma (or dot), or decimals. + +Let's say you have a `Product` model with a `discount_percentage` attribute. This attribute is currently an integer, only allowing non-decimal values. To allow 2 digit decimal values (e.g. `12.54`), you can mak the following migration: + + :::ruby + change_column :products, :discount_percentage, :decimal, precision: 5, scale: 2 + +This will allow you to store values like `80.00`, `99.99` and `100.00`. There are five digits in the entire number and of those five, two decimals. + +It's not necessary so specify `precision` and `scale` as your database will then allow any decimal value it can store. + +Read more about the `decimal` data type in PostgreSQL here: [http://www.postgresql.org/docs/9.1/static/datatype-numeric.html][1] + +[1]: http://www.postgresql.org/docs/9.1/static/datatype-numeric.html \ No newline at end of file diff --git a/content/posts/2012-09-04-git-interactive-revert.md b/content/posts/2012-09-04-git-interactive-revert.md new file mode 100644 index 0000000..23da78f --- /dev/null +++ b/content/posts/2012-09-04-git-interactive-revert.md @@ -0,0 +1,52 @@ +--- +title: "Git: Interactive Revert" +kind: article +slug: git-interactive-revert +created_at: 2012-09-04 +tags: + - git + - protip +--- +I recently made a commit in a project that, mistakenly, included changes to `db/schema.rb`. My local schema was out of date and this could cause trouble for the others in my team. + +Luckily we use [a successful git branching model][1] so my changes were still up for review by the team. + +The change I made was part of larger commit. But all I wanted was to revert serveral chunks from `db/schema.rb` that would cause trouble. + +## Interactive add + +Now, there's `git add -i` which allows you to selectively stage chunks for the next commit. Unfortunately, the process for reversing interactively is a bit more complicated, but not much. + +## Interactive revert + +Well, let's say you want to revert changes you made in commit `f1e11c`. Go to your feature branch and revert your changes for that commit but do not commit them yet, hence the `-n` option: + + git revert -n f1e11c + +Your working copy now contains staged changes to revert the entire `f1e11c` commit. You don't want to revert everything, so unstage all those changes. + + git reset + +The key now is to interactively stage the changes you want to revert. You can do this like this: + + git add -p + +or + + git add -i + +Choose whichever suits you. + +In my case I staged only the chunks that related to the changes in `db/schema.rb` that I wanted to revert. + +With your reverting changes staged, commit them. + + git commit + +You're now left with reverting changes you didn't want to make. Just throw them away. + + git reset --hard + +And that's all. You have now selectively reverted a commit. + +[1]: http://nvie.com/posts/a-successful-git-branching-model/ \ No newline at end of file diff --git a/content/posts/2012-10-01-migrate-git-repositories.md b/content/posts/2012-10-01-migrate-git-repositories.md new file mode 100644 index 0000000..4bbc106 --- /dev/null +++ b/content/posts/2012-10-01-migrate-git-repositories.md @@ -0,0 +1,22 @@ +--- +title: "Migrate git repositories" +kind: article +slug: migrate-git-repositories +created_at: 2012-10-01 +tags: + - git +--- +Sometimes you have to move your git repository to another host. In this case I want to move a privately hosted git repository to a brand spanking new github repository. + +These are four easy steps to get that done: + + $ git clone --bare git@yourserver.com:project.git + $ cd project.git + $ git push --mirror git@github.com:ariejan/project.git + $ cd .. && rm -rf project.git + +That's it. Don't forget to update the `remote` of your working copy accordingly: + + $ git remote set-url origin git@github.com:ariejan/project.git + +Of course, this works with any git server or service, not just Github, although Github is awesome and you should use it. \ No newline at end of file diff --git a/content/posts/2012-10-04-a-static-file-server-in-go.md b/content/posts/2012-10-04-a-static-file-server-in-go.md new file mode 100644 index 0000000..af0e380 --- /dev/null +++ b/content/posts/2012-10-04-a-static-file-server-in-go.md @@ -0,0 +1,30 @@ +--- +title: "A static file server in Go" +kind: article +slug: a-static-file-server-in-go +created_at: 2012-10-04 +tags: + - go +--- +If you don't know Go, you should really look into it. Today I was trying to figure out how to write a simple (and fast) static file server in Go. + +As it turns out, this is very easy to do. Go contains (in the `net/http` package) a nice `FileServer` type that can server files from the directory you point it to. + +Here's a sweet and short example: + + :::go + package main + + import ( + "net/http" + "log" + ) + + func main() { + err := http.ListenAndServe(":4242", http.FileServer(http.Dir("public"))) + if err != nil { + log.Printf("Error running web server for static assets: %v", err) + } + } + +By itself this is not very useful, but you can easily integrate this into any other http server you create, maybe for handling dynamic requests or doing web sockets. \ No newline at end of file diff --git a/content/posts/2012-11-02-decorating_sorcery_current_user_with_draper.md b/content/posts/2012-11-02-decorating_sorcery_current_user_with_draper.md new file mode 100644 index 0000000..3dec52e --- /dev/null +++ b/content/posts/2012-11-02-decorating_sorcery_current_user_with_draper.md @@ -0,0 +1,27 @@ +--- +title: "Decorating Sorcery's current_user with Draper" +kind: article +slug: decorating_sorcery_current_user_with_draper +created_at: 2012-11-02 +tags: + - Ruby + - Rails + - devise + - draper + - sorcery +--- +I already [wrote][1] about how to apply your decorator to the `current_user` when you're using [Devise][2]. However, the trick is a bit different when applied to [Sorcery][3]. + +Instead of being `nil` when no user is signed in, Sorcery uses an explicit `false` value, no `nil`. In your `ApplicationController` at `app/controllers/application_controller.rb` add this: + + :::ruby + def current_user + UserDecorator.decorate(super) unless super == false + end + +I'm using the most recent version of [Draper][4] by the way. + +[1]: http://ariejan.net/2012/04/14/decorating-devise-s-current_user-with-draper +[2]: https://github.com/plataformatec/devise +[3]: https://github.com/NoamB/sorcery +[4]: https://github.com/drapergem/draper \ No newline at end of file diff --git a/content/posts/2012-11-05-a-call-to-all-ci-service-providers.md b/content/posts/2012-11-05-a-call-to-all-ci-service-providers.md new file mode 100644 index 0000000..02eb2ac --- /dev/null +++ b/content/posts/2012-11-05-a-call-to-all-ci-service-providers.md @@ -0,0 +1,73 @@ +--- +title: "A Call to all CI Service Providers" +kind: article +slug: a-call-to-all-ci-service-providers +created_at: 2012-11-05 +tags: + - testing + - bdd + - tdd + - rant + - ci + - jenkins +--- +As a professional developer I test my code. Every check-in I do is tested either on [Kabisa][1]'s [Jenkins][2] server or on [Travis CI Pro][4]. + +For open source projects there's [Travis CI][3]. It's free and a great way to get to know Travis. + +Now, as an individual I have some side projects, most notably my own site, [Ariejan.net][7]. I value well written and well tested code as much for my own stuff as for my professional work. However, I don't have a Jenkins CI server in my basement. + +Looking around the internets there are a few CI services that can test my Ariejan.net Rails application, among them [Cirlce CI][5], [Atlassian Bamboo][8] and [Semaphore][6]. + +Now, the thing here is that to test the few projects I have (about four) it will cost me more to have the hosted in a CI services than actually running these apps. + + * Travis - $129/mo for 2 concurrent builds. + * Circle CI - $49/mo for 10 private projects. + * Semaphore - $39/mo for 5 private projects. + * Bamboo - $20/mo for 10 jobs.¹ + +_¹ Bamboo advertises $10/mo, but a $10/mo Jira subscription is also required._ + +As an individual, these prices are pretty steep for the few commits I make during the weekend. + +### Looking at Github and Heroku + +Let's take a look at github and heroku here. At heroku I pay nothing to host a site. Performance is limited, but it more often than not is sufficient for a side-project. Adding a worker or extra dyno costs about $38/mo. Still less than half of the CI services I mentioned. + +Having private repos with Github is also quite cheap. You'll get 5 for $7/mo. Which, converted, is about €1 per repo a month. + +Now, as an individual developer, I'm more than happy to use these services for these relatively low fees. + +### Where are the cheap, individual CI services? + +As an individual developer, this is what I need: + + * Unlimited, or at least 5-10 jobs/private projects. + * Low monthly cost: $5 - $10. + +What I don't need is: + + * Concurrency and paralellisation + * Immediate execution + * 24/7 Support + +I can do with a single worker doing one build at a time. I don't write enterprise CRM software in my weekends, you know. + +Also, I don't expect my build to execute immediately. I don't mind if there's a job queue and I have to wait a bit longer to get back results. + +### Business opportunity? + +IMHO there's a business opportunity here. Github and Heroku are great examples of how giving something away for free (or cheap) to individual developers creates a market for their product. + +I understand that CI takes a considerable amount of system resources and time, but having a good experience as an individual is more than likely to attrack people to your platform when they need a more profession plan. + +Who's stepping up to the plate? + +[1]: http://kabisa.nl +[2]: http://jenkins-ci.org +[3]: http://travis-ci.org +[4]: http://travis-ci.com +[5]: https://circleci.com +[6]: https://semaphoreapp.com +[7]: http://ariejan.net +[8]: http://www.atlassian.com/software/bamboo/ \ No newline at end of file diff --git a/content/posts/2012-11-22-customac.md b/content/posts/2012-11-22-customac.md new file mode 100644 index 0000000..cca8164 --- /dev/null +++ b/content/posts/2012-11-22-customac.md @@ -0,0 +1,194 @@ +--- +title: "CustoMac" +kind: article +slug: customac +created_at: 2012-11-22 +tags: + - osx + - hacks + - customac + - osx86 +--- +Ever since Apple decided to put Intel processors in their Macs there have been attempts by enthusiasts to run Mac OS X on commodity hardware – with mixed results. + +The key to installing Mac OS X on a non-Mac computer is using the right hardware. If your hardware is a close to Apple kit as possible, you have the best chance to succeed. + +The so-called *Hackintosh* community has come a long way the past few years in making it easy for "normal people" to install Mac OS X on their _PC_. + +Since I was tired of using Debian Linux on my Desktop, dual booting to Windows to play the occasional game of _World of Warcraft_, I decided to give installing Mac OS X a try. But there was a problem. + +## The hardware + +My current PC is powered by an AMD processor (AMD Phenom II X6 1055T, to be precise). Simply put, installing Mac OS X on an AMD cpu is not going to work. + +So, the first thing I did was go over to the [OSx86 Project Hardware Compatibility List][2] and see what hardware is most compatible with Mac OS X 1.8.2 (the most recent version of OS X at this time). + +I already knew I'd need a shiny new Intel CPU, and thus also a new motherboard. + +## Choosing a CPU + +Choosing a CPU for your CustoMac is not very difficult, because you are limited to Intel. Since the release of the new MacBooks Apple officially supports the [Ivy Bridge][1] architecture, which means about a 50% lower power consumption and a 5-15% speed increase ([link][1]). + +The main candidates were: + +* Intel i5 3570 +* Intel i5 3570K +* Intel i7 3770K + +I won't go into to too much detail here, but I chose the _Intel i5 3570_. + +The i7 offers [Hyper Threading][3] at a price bump of about € 100,-. For me, this was not worth the money. + +Then there's the choice between the _3570_ or the _3570K_. The 'K' version has less features, but is _unlocked_, allow it to be easily over-clocked to higher speeds. + +The price difference between the _3570_ and the _3570_ are minimal, but I'm not planning to over-clock my CPU, so I went for the slightly cheaper _Intel i5 3570_ processor. + +## Choosing a motherboard + +Next came a more difficult decision. The motherboard. Again, the [Hardware Compatibility List][2] was a great help here. In the end I chose the _Gigabyte Z77-DS3H_. + +The pro's of this board are that it's well supported by Mac OS X and the OSx86 community. This board is special because it features Gigabyte's 3D UEFI Bios. This bios would make it easy to install Mac OS X _untouched_ on your machine. + +I didn't end up using this UEFI feature, but nonetheless, support for this board is incredible. + +## The full hardware list + +So, recycling other parts of my current _PC_ I build the following CustoMac configuration: + +* Gigabyte Z77-DS3H Motherboard ([But at Amazon][9]) +* Intel i5 3570 CPU @3.4Ghz ([Buy at Amazon][8]) +* 16GB RAM at 1333Mhz +* 1x 120 GB OCZ Agility 3 SSD +* 1x 1TB Western Digital HD +* 1x 2TB Western Digital HD +* XFX ATI Radeon HD 6870 1GB ([Buy at Amazon][10]) + +_Note 1: The above Amazon links are affiliate links._ + +_Note 2: I could have upgraded my memory to 1600Mhz units, which would be faster. But I have no use for the old memory, so I chose to re-use it for now._ + +## The preparation + +Before you get started, you should prepare an installation USB drive on another Mac. You'll need at least 8GB of space on the drive. + +* Buy Mountain Lion from the App Store on you Mac and download the installer. If you already purchased Mountain Lion, re-download it. +* Download [UniBeast][4] for Mountain Lion and follow step 1 and 2 from [this guide][5]. + +At this point you have a bootable USB drive with the Mountain Lion install on it. + +To prepare the actual installation, remove any devices you don't need, like extra hard drivers, DVD/BluRay drives, etc. In my case I also pulled out the ATI 6870 and used the onboard Intel HD Graphics during installation. + +Plug your USB drive into a USB 2.0 (black, not blue) port on the motherboard. Make sure to use one of the ports on the back of your computer, those are directly attached to the motherboard and have the greatest chance of succes. + +Now, boot up the computer and enter the BIOS. There are two important changes you need to make. + +* Set SATA to `AHCI` mode. +* Disable `VT-d`. + +Then select the USB drive as the bootable device and boot. + +## Booting the installer + +You'll see the UniBeast boot screen which show a 'USB' option (and possibly other, depending on what's on your disks). Choose 'USB' - but don't press `ENTER` just yet. Instead, type `-x`, which will show up on the screen. Then, press `ENTER`. + +After a few minutes you should have the Mac OS X Installer in front of you. Go ahead, install this baby. + +## Notes on Fusion drive + +It's possible to create a CustoMac Fusion Drive using an SSD and regular harddisk. + +When you're in the installer, choose 'Terminal' to open a terminal window and follow the steps in [this fusion drive guide][6]. + +I was able to create and install Mac OS X on a Fusion Drive without problems. The only knack was that the custom bootloader you need is not Fusion Drive aware, which makes it difficult to use. + +In the end I decided to not use a Fusion drive setup, and just install everything on the SSD. + +## Completing the installation + +Now comes the tricky part. The installation is done and you're CustoMac wants to reboot. Let it, but leave the USB drive connected. + +Your machine will boot up with the same boot menu as before, but instead of 'USB' you should now be able to select 'Macintosh HD'. + +Select that, enter `-x` followed by `ENTER`. + +You'll now be taken through the final steps of installation, like setting up iCloud and creating a user account. + +When finished, you should be on your new Mac desktop. + +## Installing custom kexts + +Now, your CustoMac can only boot with the USB drive. Let's change that by installing a bootloader and some kernel extensions. + +* Download [MultiBeast][4] for Mountain Lion. +* Run the installer and select the following options: +** UserDSDT or DSDT-Free Installation +** Miscellaneous => FakeSMC +** Audio => Realtek ALC8xx => Without DSDT => Latest version for ALC8887/888b. +** Network => maolj's AtherosL1cEthernet +** Disk => TRIM fix for 1.8.1+ +** Bootloaders => Chimera + +That's all. Install that stuff. Now, you should be able to reboot your CustoMac and boot it without the USB drive. + +If things don't work out (like a black or white screen, kernel panics, whatever), just plugin your USB drive again, boot from it and select your 'Macintosh HD'. + +## Graphics + +At this point, you should have a working CustoMac with sound and network working. The only thing missing is a proper graphics card. + +You'll need to make some tweaks to the chameleon plist file. Then shutdown your computer and install the graphics card. + +_Note: this works for my XFX ATI Radeon HD 6870 card. There may be subtle differences for different version and brands. Just use the Google to find hints, boot with the USB drive to get to your system and make updates as needed._ + +In `/Extra/org.chameleon.Boot.plist` make sure you have the following entries: + + AtiConfig + Duckweed + AtiPorts + 5 + Graphics Mode + 1920x1080x32 + GraphicsEnabler + Yes + PciRoot + 1 + +Depending on which slot you used for your graphics card, you may have to set `PciRoot` to `0`. + +I also had to add `npci=0x2000` to `Kernel Flags`, but you may or may not need it. + + Kernel Flags + npci=0x2000 darkwake=0 + +## Boot up! + +Now, reboot one last time and everything should go smoothly. + +If your system comes up without any troubles, start attaching those others disks and drives you had disconnected during the installation. There shouldn't be any issues here. + +## You're done! + +Congratulations. You now have a CustoMac! + +Keep in mind that you should not blindly install any update you see. Installing an update my change the bootloader or change kernel extensions that break your system. + +A good tip is to create a full disk image of your SSD using a tool like [Super Duper][7]. In case of shit hitting the fan after an update, you can easily restore your disk to working order. + +In my case, I've attached an old 500GB drive to store this disk image. It works great. + +## Shiny and fast! + +Just as a side-note, my CustoMac is blazingly fast. It's the combination of the fast _Ivy Bridge_ architecture, the i5 processor, and the SSD. + +I've measured boot-up time from pressing the power button to the Mac OS X login screen at about 11 seconds. + +[1]: http://en.wikipedia.org/wiki/Ivy_Bridge_%28microarchitecture%29 +[2]: http://wiki.osx86project.org/wiki/index.php/HCL_10.8.2 +[3]: http://en.wikipedia.org/wiki/Hyper-threading +[4]: http://www.tonymacx86.com/downloads +[5]: http://tonymacx86.blogspot.nl/2011/10/unibeast-install-mac-os-x-lion-using.html +[6]: http://jollyjinx.tumblr.com/post/34638496292/fusion-drive-on-older-macs-yes-since-apple-has +[7]: http://www.shirt-pocket.com/SuperDuper/SuperDuperDescription.html +[8]: http://www.amazon.com/gp/product/B0087EVHTE/ref=as_li_ss_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=B0087EVHTE&linkCode=as2&tag=ariejannet-20 +[9]: http://www.amazon.com/gp/product/B007R21JWC/ref=as_li_ss_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=B007R21JWC&linkCode=as2&tag=ariejannet-20 +[10]: http://www.amazon.com/gp/product/B0047ZH7GE/ref=as_li_ss_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=B0047ZH7GE&linkCode=as2&tag=ariejannet-20 \ No newline at end of file diff --git a/content/posts/2012-11-29-binary-debugging-with-git-bisect.md b/content/posts/2012-11-29-binary-debugging-with-git-bisect.md new file mode 100644 index 0000000..4976af9 --- /dev/null +++ b/content/posts/2012-11-29-binary-debugging-with-git-bisect.md @@ -0,0 +1,125 @@ +--- +title: "Binary debugging with git bisect" +kind: article +slug: binary-debugging-with-git-bisect +created_at: 2012-11-29 +tags: + - git + - bisect +--- +Part of resolving a bug is finding where and when that bug was introduced into your code. Not so much for blaming a specific person, but more for an understanding of how and maybe why the bug was introduced; and more over which versions of your app are affected. + +Most of the time the bug was recently introduced and your CI notified you that stuff has been broken. + +In order to find out when, how and by whom the build was broken, you'll have to dig into your git history and run your specs to see if they pass or not. + +Running your specs for every commit in your history manually is very time consuming and boring. Luckily there are better ways, using plain old git. + +## Binary search + +Before I dive into git, it's important you understand how binary search works. If you already know this stuff, skip right to the next section. + +You have a sorted array. This means there is some order to the elements you have. Presume you have an array of ints: + + :::ruby + a = [1, 3, 4, 7, 33, 42, 54, 76, 89, 91] + +Now, we want to find the position (`n`) of `7` in this array using binary search so that `a[n] == 7`. + +Binary search uses a divide and conquer strategy. You split the array in the middle. We have 10 elements, so a logical place would be to split the array at position `n = 5`, which has the value `42`. + +Comparing `7 <=> 42` tells us that, because we have an ordered array, the value `7` should be in the first half of the array. + +We can ignore the right half of the array for searching, and repeat this step for the left part, specifically: + + :::ruby + [1, 3, 4, 7, 33] + +So, let's split this part up again. We get `4`. This is less than `7`, so if we continue looking we should take the right part. + + :::ruby + [7, 33] + +Okay, again, we continue our search. We know have to split the array one way or the other, and we end up picking `n = 4`. We hit `33`. Surely the value of `7` must be on the left part of this. + +_Note that although I don't show the whole array, I'm still using the index positions for the entire `a` array._ + + :::ruby + [7] + +Now, there's not much to pick for us. This is 7. Right here at `n = 3`. Done! + +Notice that the last step does not involve checking the value of the last element. We only have 1 element left, so we're finished. + +If we had been looking for a value of 18, we would have also found `n = 3`. This means that we can search for non-existing values, which then return the index right before where that number should be inserted. This works because the array was ordered, so we can safely make such assumptions. Nice, huh? + +## How does binary search relate to finding bugs? + +Well, in the example above we were looking for an integer value. The test we use to evaluate is simple: + + :::ruby + value <=> a[n] + +When we want to find the commit that broke our build we need a more clever way of comparing values. + +This is where your test suite comes in. You have a failing spec now, so basically we're looking for the point in git history where that spec failed for the first time. + +## Git bisect + +Because it's not feasible to do a linear search over your entire commit history, we'll have to start by marking a _good_ and a _bad_ commit. + +First we'll have to find a location in your git history where you know the app did not exhibit this bug. + +More than likely, this place is your last stable release. If you used `git tag` to tag your release, you should be able to find out quickly if that release contains the bug. + + git checkout v1.2.1 + rspec spec/features/fancy_spec.rb + => 0 failures + +Good. We now our latest commit was broken, so let's get started with that binary search! + +You don't have to keep track of the git history and binary search position all by yourself: git does this for you. All you have to do is compare each commit that is presented to you with an expected result. E.g. does the `fancy_spec.rb` spec pass or not? + +## Bisecting steps + +First, let git know you want to do a binary search. + + git bisect start + +Next, let git know which commit is good, and which one is bad. + + git good v1.2.1 + git bad e8ab31 + +Git will respond with something like this: + + Bisecting: 19 revisions left to test after this (roughly 4 steps) + [0e70ee6aefe428fa897ec7e48273c3fe4d0bf7fb] Did some funky stuff in `config/application.rb`. + +Git has determined that commit `0e70ee` is right in between `v1.2.1` and `ebab31`. Check if this revision is broken and report back to git. + + rspec spec/features/fancy_spec.rb + > 1 failure + git bisect bad + +And git will respond: + + Bisecting: 9 revisions left to test after this (roughly 3 steps) + +You can continue this until git tells you the commit that first broke this spec. + +When you're done bisecting (or if you just feel like doing something else), just tell git: + + git bisect reset + +## Usage in the field + +Git bisect is a very powerful tool to find specific points in your code. + +In this example we were looking for a commit that broke a specific test. You can look for all kinds of things: + +* When did the layout on that page break? +* At what point did we upgrade that gem to a buggy release? +* Who added this ugly piece of code?! + +Doing a binary search over your git history is a fast and efficient way of finding these kind of things out. \ No newline at end of file diff --git a/content/posts/2012-12-18-instagram-governments-and-cypherpunks.md b/content/posts/2012-12-18-instagram-governments-and-cypherpunks.md new file mode 100644 index 0000000..a58d01e --- /dev/null +++ b/content/posts/2012-12-18-instagram-governments-and-cypherpunks.md @@ -0,0 +1,101 @@ +--- +title: "Instagram, Governments and Cypherpunks" +kind: article +slug: instagram-governments-and-cypherpunks +created_at: 2012-12-18 +tags: + - rant + - cypherpunk + - privacy + - instagram +--- +The whole internet collectively fell over Instagram earlier this week when they released their new _Terms of Service_. + +> Some or all of the Service may be supported by advertising revenue. To help us deliver interesting paid or sponsored content or promotions, you agree that a business or other entity may pay us to display your username, likeness, photos (along with any associated metadata), and/or actions you take, in connection with paid or sponsored content or promotions, without any compensation to you. + +Today [Instagram officially responded][1] to all the screaming and shouting, trying to control the damage. + +> From the start, Instagram was created to become a business. + +Well, that was unexpected. It's the same with Facebook, Twitter, Gmail, Hotmail, etc. All these services are here for you to use for free - awesome! + +But nobody ever got rich from giving stuff away for free, and these are businesses we're talking about. + +Let's see how this works over at Facebook. + +## Exhibit A: Facebook + +Facebook is a good example of how you are the product being sold. We all know Facebook tries to show you ads that you are likely interested in. Based on what you put in your profile advertisers can target specific audiences. + +That might sound all well and good to you, but did you also know that your friends might see a _sponsored_ post in their newsfeed? And that that _sponsored_ post appears as if it's one posted by _you_? + +![Facebook sponsored post](https://s3-eu-west-1.amazonaws.com/ariejannet/images/related-post.png) + +Here you see a post by a friend of mine who apparently likes some mobile company's ad. They didn't like this add. I asked them. + +So basically Facebook and an advertiser are using you, your name and your profile picture to promote an advertisement to one of your friends. + +## Exhibit B: Google + +Another example, more in line with Facebook, is Google. All those free tools: Search, Gmail, Google+, Analytics, Web Master Tools, maps. They are all geared towards either exposing ads to you, or gathering data on you to expose more focussed ads. + +The plus side here, I would say, is that Google actually delivers usable services, like search, mail, calendars and such. But still, look at the boatloads of money Google is making selling ads based on your private details and online behaviour. + +## Exhibit C: Twitter + +Twitter is a company that has struggled for a long time to find a way to monetize itself. Hopes were high that they would find a new an innovative way, but they have not. + +It remains to be seen what's going to happen exactly with twitter, but we can all assume that our tweets are being analyzed and that Twitter will do anything to sell you to their advertisers. + +## Exhibit D: Instagram + +To come back to Instagram, they're in the same boat as Twitter and Facebook. + +They have been growing their user base for quite some time now and now comes the time to move in and cash on all those users. + +The new terms of service should not come as a surprise. + +Instagram wants to use your data to match you up with advertisers and it even wants to show ads endorsed by you, just like Facebook already does. + +## So what's happening out there? + +Social Media are nice, they connect people and can start revolutions. But that is not why those media exist. They are marketing and data mining platforms on a massive scale. + +Have you ever wondered why social media companies get multi million dollar investments? Because with a huge, world-wide user base, there is a huge advertising potential. + +_If you are not buying a product or service, but are receiving one for free, then you are the product or service begin sold._ + +I think that companies like Facebook and Instagram are wading in a gray area of privacy. Their practices may be perfectly legal, but they _are_ ethically questionable. + +## On government spying and eavesdropping + +You might sometimes worry about governments eavesdropping and keeping tabs on innocent citizens – and you should. + +If you look at the sheer amount of data social media gather on you, imagine what kind of data a government agency with the proper tools and hardware can do. + +But the issue is not really that companies and governments are gathering data - you are feeding it to them with every status update and photo you upload to the internet. With every email you sent. + +So, what's to be done? + +## Cypherpunks to the rescue? + +Now, I won't claim to know a whole lot about cypherpunks and the crypto wars, but these people have fought (and still are) fighting for our digital rights and freedom. + +We are collectively giving away our personal data to companies and governments and most of the time we don't even know it. But then some image sharing company changes its terms that will allow them to display adds alongside your name and we freak out. + +Here's quite from the Cypherpunks manifesto: + +> Privacy is necessary for an open society in the electronic age. ... We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy ... We must defend our own privacy if we expect to have any. ... Cypherpunks write code. We know that someone has to write software to defend privacy, and ... we're going to write it. + +To be clear, I'm not a cypherpunk by any means. Here's a snippet from Wired magazine, 1993: + +> The people in this room [cypherpunks] hope for a world where an individual's informational footprints -- everything from an opinion on abortion to the medical record of an actual abortion -- can be traced only if the individual involved chooses to reveal them; + +And this is where the problem lies. Yesterday Will Wheaton, of Star Trek fame, [wrote about][2] this as well. + +Will has a great point that we've come to a point where it's okay that somebody else shares your personal details with a third party on the internet – without your knowledge, without your consent. And there is nothing you can do about it. + +I'm not sure about you, but this sure frightens me. + +[1]: http://blog.instagram.com/post/38252135408/thank-you-and-were-listening +[2]: https://plus.google.com/108176814619778619437/posts/3o79SJWv4kG \ No newline at end of file diff --git a/content/posts/2013-03-08-review-commits-in-your-feature-branch.md b/content/posts/2013-03-08-review-commits-in-your-feature-branch.md new file mode 100644 index 0000000..05ac80a --- /dev/null +++ b/content/posts/2013-03-08-review-commits-in-your-feature-branch.md @@ -0,0 +1,42 @@ +--- +title: "Review commits in your feature branch" +kind: article +slug: review-commits-in-your-feature-branch +created_at: 2013-03-08 +tags: + - git + - github + - hub +--- +Github pull requests are awesome, but you can't use them all the time, mostly when working on code not hosted at github. + +The following snippet makes it easy to see the commits in your current (head) branch that are not yet in the base branch. + +To see what commits are made in your current feature branch, but which have not been merged into develop yet: + + :::text + $ gpr develop + * 5246248 (HEAD, origin/feature-branch, feature-branch) Implements the awesome feature (50 minutes ago) + * 4f55b7c Write specs for awesome feature (2 hours ago) + +## Snippets + +To achieve this, add the following alias to `~/.gitconfig`: + + :::ini + [alias] + lg = log --graph --pretty=format:'%Cred%h%Creset %Cblue<%ae>%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative + +And the following alias to your `~/.bashrc` or `~/.zshrc`: + + :::bash + alias gpr="git --no-pager lg HEAD --not $1" + +## Bonus tip + +After reviewing the commits in your feature branch with `gpr`, use [`hub`][hub] to attach your code to a github issue: + + :::bash + hub pull-request -i 42 -h ariejan:feature-branch -b you:develop + +[hub]: http://defunkt.io/hub/ \ No newline at end of file diff --git a/content/posts/2013-03-22-my-first-post.md b/content/posts/2013-03-22-my-first-post.md deleted file mode 100644 index dde7963..0000000 --- a/content/posts/2013-03-22-my-first-post.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: "My first post" -created_at: 2013-03-22 -kind: article -tags: [ 'one', 'two' ] ---- - -This is my post. - -* With -* A list - -And some - -> quoted stuff for fun and profit. - -Thanks! \ No newline at end of file diff --git a/content/stylesheet.css b/content/stylesheet.css deleted file mode 100644 index 0329a0d..0000000 --- a/content/stylesheet.css +++ /dev/null @@ -1,101 +0,0 @@ -* { - margin: 0; - padding: 0; - - font-family: Georgia, Palatino, Times, 'Times New Roman', sans-serif; -} - -body { - background: #fff; -} - -a { - text-decoration: none; -} - -a:link, -a:visited { - color: #f30; -} - -a:hover { - color: #f90; -} - -#main { - position: absolute; - - top: 40px; - left: 280px; - - width: 500px; -} - -#main h1 { - font-size: 40px; - font-weight: normal; - - line-height: 40px; - - letter-spacing: -1px; -} - -#main p { - margin: 20px 0; - - font-size: 15px; - - line-height: 20px; -} - -#main ul, #main ol { - margin: 20px; -} - -#main li { - font-size: 15px; - - line-height: 20px; -} - -#main ul li { - list-style-type: square; -} - -#sidebar { - position: absolute; - - top: 40px; - left: 20px; - width: 200px; - - padding: 20px 20px 0 0; - - border-right: 1px solid #ccc; - - text-align: right; -} - -#sidebar h2 { - text-transform: uppercase; - - font-size: 13px; - - color: #333; - - letter-spacing: 1px; - - line-height: 20px; -} - -#sidebar ul { - list-style-type: none; - - margin: 20px 0; -} - -#sidebar li { - font-size: 14px; - - line-height: 20px; -} diff --git a/layouts/default.haml b/layouts/default.haml new file mode 100644 index 0000000..e94b8b2 --- /dev/null +++ b/layouts/default.haml @@ -0,0 +1,47 @@ +!!! 5 + + + + +%html.no-js + + %head + %meta{ :charset => "utf-8" } + %meta{ "http-equiv" => "X-UA-Compatible", :content => "IE=edge,chrome=1" } + %title ariejan.net - #{@item[:title]} + %meta{ :name => "description", :content => "" } + %meta{ :name => "author", :content => "" } + %meta{ :name => "generator", :content => "nanoc #{Nanoc::VERSION}" } + %meta{ :name => "viewport", :content => "width=device-width" } + + -# Place favicon.ico and apple-touch-icon.png in the root directory + + %link{ :rel => "stylesheet", :href => "/css/style.css" } + %script{ :src => "/js/vendor/modernizr.js" } + %body + /[if lt IE 7 ] + %p.chromeframe + You are using an outdated browser. + Please upgrade your browser or + activate Google Chrome Frame + to improve your experience. + + #sidebar + %ul + %li asdf + %li narf + + #content + = yield + + %script{ :src => "//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" } + :javascript + window.jQuery || document.write('