tag:blogger.com,1999:blog-69158232024-03-05T21:31:55.717-08:00Skuunk WorksThis is my technical blog where I note things I have developed that I want to remember.skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.comBlogger81125tag:blogger.com,1999:blog-6915823.post-80853115628538865882020-01-22T21:12:00.000-08:002020-01-22T21:12:08.126-08:00What you can and can't learn from personal software projects.Ok, controversial click bait title aside, personal software projects do an enormous amount of good in teaching you how to learn new languages, write better code, architect code and may even make you a buck or two on the side (if you are lucky). However, there are some things that you are not likely to learn when writing personal software projects and I wanted to discuss a few of them here.<br />
<br />
<h2>
What they can't teach you</h2>
<h3>
How to communicate your ideas</h3>
By definition a personal project is personal. You are probably the only one working on it (though you can ask a friend or two to help, which is highly advisable if you can). When you want to add a new software pattern or framework, you just do it. While that's incredibly liberating, one of the benefits of working in a team is having to explain your decisions will make you more reflective and will give you a better sense of if you are doing things the right way or not. If you meet with resistance to the idea, it means you have to think through all the pros and cons. Also, working is probably about 80% communication and 20% coding in any case (or even less coding and more communication depending on your role).<br />
<br />
<h3>
Scale</h3>
It would be great if your personal project grew to the size of Uber or Google and had to handle millions of hits per second or billions of rows of data. Heck, even most non personal projects (e.g. professional ones) don't have to deal with scale on that level.<br />
<br />
These problems of scale will force you to deal with even simple issues in a complex manner. You may need to tweak your SQL code. You might need to denormalise your data. You might need to add a caching layer or use eventual consistency. You might need to add load balancers and use a cloud service to deliver your static assets.<br />
<br />
All of these are probably not worth spending the time implementing on your personal project.<br />
<br />
I even read somewhere that Facebook allows you to log in with the caps lock version of your password (e.g. if your password is "PassWord123" you can also log in with "PASSWORD123"). The reason being is that Facebook gets so many hits that redirecting users back to the error page if their caps lock is off costs them more money than letting them in.<br />
<br />
The other issue with scale is dealing with edge/corner cases. If you have a corner case affecting 1% of your users, it's not worth dealing with it if your project gets 50 hits a day. However if your website gets hit a million times a day that's 10 thousand people who will run into your 1% edge case.<br />
<br />
<h3>
Complexity</h3>
<div>
When you are writing a personal project, you are the "business" as well as the programmer. As the programmer, you probably will suit the business needs based on what is easiest to code. In non-personal projects you will get asked to do some pretty whacky things that probably don't match your model of the system. Sometimes you can push back, but it's really a process of negotiation and you will probably end up putting something together that may be good, but not perfect (what is perfect anyways?). </div>
<div>
<br /></div>
<div>
Not only that, but you know that other bit of funky code you wrote a few months back? The one you just optimised? Yeah, well, we don't need that anymore. You can remove it at your leisure. Ah, you had to change the base models to get it to work and now that affects the new code you have since written? Well, good luck with that!</div>
<div>
<br /></div>
<div>
Product managers aren't trying to mess you around. Most of the time they just don't know what is needed until something has been running for a while.</div>
<div>
<br /></div>
<div>
Other causes of complexity can be added by the other topics below.</div>
<br />
<h3>
Other people's code</h3>
Let's face it. Not everybody sees the world in the same way. Also, not everybody is going to approach (and solve) a problem in the same way either. Working on a personal project does not lend itself to seeing new ways of problem solving in the same manner (though you might come across some old code and say to yourself "What was I thinking?").<br />
<br />
If you are working with a group of programmers, well, you will run across this every day. If you are not, maybe you should consider working on an open source project (and/or reading lots of open source code). Occasionally, you might even learn something.<br />
;)<br />
<br />
<h3>
Legacy code and data structures</h3>
This is similar to the Other People's code issue, but you multiply time and complexity. Perhaps one of the libraries used has been end of lifed. Perhaps what you thought was necessary 6 months ago turns out not to be necessary now. Perhaps you are taking on a new role after the CEO fired the old development team. Or even worse, the people who built the old code are still there and don't realise there is a problem with it.<br />
<br />
Moving code around should be relatively simple, but depending on how it's written, it can be tricky. Updating data structures is even trickier (as data is largely seen as immutable and there can be a lot of it to shift). However, nothing is impossible.<br />
<br />
Personal projects are less likely to run into this. They tend to be short term and when you want to try something new, you will probably just abandon your project and start again with a new framework or language,<br />
<br />
<h3>
Working to a deadline</h3>
You know all those horrible programmers who wrote all that yucky code and those data structures that make no sense? Well, they probably did it because they had a deadline. You do get time constraints on personal projects, but they are more of a case of not having enough time to work on a project.<br />
<br />
With a deadline, things have to get done, but corners sometimes have to be cut. Ideally it is scope that is cut and not quality, but in reality, something's gotta give. Even cutting scope can lead to half built features that might not make sense no matter how well the code is written.<br />
<br />
As a whole though, it seems like businesses (at least the ones I have been in touch with lately) are more aware of the effects of deadlines on code quality and are using them more sparingly. In any case, this is something you are unlikely to encounter on a personal project.<br />
<br />
<h3>
Industry standards</h3>
<div>
Just as there are many different ways to implement something, there are also standards on the other hand to limit their use in practice. For example, it is a standard in Rails to use ActiveRecord for your models and to interact with them in a specific way. There are actually lots of other tools you can use (and there may be good reason for using them) but for the most part standards ensure some groups of programmers will implement things in a similar consistent manner. If standards are used properly, you should not be able to tell which member of the team wrote a particular piece of code without using git blame.</div>
<div>
<br /></div>
<div>
Again I recommend reading open source projects (or even joining one) to get a good idea of what the industry standards are like. Code does not happen in a vacuum and if no one else can understand your code, it does not matter how good its performance is.</div>
<br />
<h2>
What they can teach you</h2>
Ok, now I am going to do that thing where I list the same items, but from the opposite side...<br />
<br />
<h3>
How to communicate your ideas</h3>
<div>
You know that saying "A picture is worth a thousand words"? Well, the same holds true for code. A working example is worth many hours of discussion. If your personal project can be shared with your coworkers, it can give you a common vision and also a reference point.</div>
<br />
<h3>
Scale</h3>
<div>
While it is true you cannot tell exactly how things will scale on a personal project, there are tools out there which can hit an endpoint multiple times and at least give you an idea of how to handle load. Doing this against a personal project can help you identify potential pain points that you can avoid doing on production.</div>
<br />
<h3>
Complexity</h3>
Personal projects are not by nature complex, but can be used to help break down a complex problem. You might be able to break a complex issue into multiple personal projects each solving one problem to help you tackle the big problem you are having at work.<br />
<br />
<h3>
Prototyping</h3>
Essentially personal projects make for great prototypes. You can solve a problem relatively quickly without the baggage of existing code and data or even try out new concepts and languages.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-50243282601993967002020-01-21T22:14:00.003-08:002020-01-21T22:14:39.599-08:00Ecto "inheritance" using macrosOooh them's fighting words. Anyways, this is definitely not true inheritance by any means, but let me explain the problem and then the solution.<br />
<br />
Ecto is the primary way you connect to the database in Phoenix. However, Ecto differs in many significant ways from ActiveRecord in Rails. You can set up a schema file which lists the relationships between your "models" or "entities", but these schemas do not allow you to directly access the database. They are merely descriptions of how the data is expressed.<br />
<br />
To access the database, you need to use a data context which uses Ecto.<br />
<br />
So, from my article comparing Active Record to Ecto (<a href="http://www.skuunk.com/2020/01/comparing-rails-active-record-pattern.html">http://www.skuunk.com/2020/01/comparing-rails-active-record-pattern.html</a>)<br />
<br />
<pre><code class="elixir">
defmodule Schema.Article do
use Ecto.Schema
import Ecto.Changeset
alias Schema.Author
alias Schema.Comment
@permitted_fields [:title, :content]
@required_fields [:title, :content]
schema "article" do
belongs_to(:author, Author)
has_many(:comments, Comment)
field(:title, :string)
field(:content, :string)
timestamps()
end
def changeset(article, params) do
article
|> cast(params, @permitted_fields)
|> validate_required(@required_fields)
end
end
defmodule Context.Articles do
import Ecto.Query, warn: false
alias Context.Repo
alias Schema.Article
def find(id) do
Repo.get(Article, id)
end
...
#implement other data access methods as required
...
end
</code></pre>
This is all well and good and it does work, however you will soon discover that there are some very common methods you will need over and over again. Basically the CRUD operations (Create, Read, Update and Delete) but you might also have some other common variations (for example you can find by id or find by other attributes).<br />
<br />
Wouldn't it be nice if like in Rails, you could just "inherit" these methods on all your contexts so you would not have to write a custom find method each time. Well, using macros you can!<br />
<br />
In Elixir, macros are the metaprogramming framework which allows you to basically use code to write code. In fact the <span style="font-family: "courier new" , "courier" , monospace;">if</span> function in Elixir is basically a macro.<br />
<br />
On top of macros, you need to know how to <span style="font-family: "courier new" , "courier" , monospace;">use</span> code from other files. Read up on the <span style="font-family: "courier new" , "courier" , monospace;">use</span> macro for more details. <a href="https://elixir-lang.org/getting-started/alias-require-and-import.html#use">https://elixir-lang.org/getting-started/alias-require-and-import.html#use</a><br />
<br />
Anyways, let's maybe start with the code and then explain it later...<br />
<br />
<pre><code class="elixir">
defmodule Services.Data.Common do
defmacro __using__(values) do
schema = Keyword.get(values, :schema)
quote bind_quoted: [schema: schema] do
require Ecto.Query
alias Ecto.Query
alias Services.Repos.Repo
@schema schema
def find(id) when is_atom(preloads) or is_list(preloads) do
@schema
|> Repo.get(id)
end
# ... more common database functions
end
end
end
</code></pre>
<pre><code class="elixir">
</code></pre>
This is a common data services module. Any functions you create in here will filter through to any other modules that <span style="font-family: "courier new" , "courier" , monospace;">use</span> it. In fact this is how we import the Ecto.Schema functionality into our Schema module.<br />
<br />
To use this, we will call it from the Context module
<br />
<pre><code class="elixir">
defmodule Context.Articles do
import Ecto.Query, warn: false
alias Context.Repo
alias Schema.Article
use Services.Data.Common, schema: Articles
end
</code></pre>
<pre><code class="elixir">
</code></pre>
As you can see, we were able to remove the <span style="font-family: Courier New, Courier, monospace;">find</span> function from Articles as it is using Common. We are passing the schema into the macro so that we can also use this macro with Author or Comment (or any other schema).<br />
<br />
Okay, so this is really not inheritance, it's composition, but it has the same net effect. When you compile this, essentially we are adding all these functions to the data contexts.<br />
<br />
The one major difference from inheritance is you cannot then override <span style="font-family: Courier New, Courier, monospace;">find</span> from inside your own context (you will have to give it a different name I am afraid).<br />
<br />
So a little more explanation might be needed. Importing a module with the <span style="font-family: Courier New, Courier, monospace;">use</span> macro not only imports the functions, but also calls the <span style="font-family: Courier New, Courier, monospace;">__using__</span> function. Calling the <span style="font-family: Courier New, Courier, monospace;">quote</span> function inserts all this code into the virtual version of your module. <span style="font-family: Courier New, Courier, monospace;">bind_quoted</span> allows you to make sure that the variables you pass in are available at runtime (and are not precompiled). In this case, we want to make sure the <span style="font-family: Courier New, Courier, monospace;">schema</span> can be passed in at runtime so it is in fact variable.<br />
<br />
And that is how a little metaprogramming can go a long way.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-88821092641568043492020-01-19T19:45:00.002-08:002020-01-20T00:12:04.524-08:00Elixir - destructuring, function overloading and pattern matchingWhy am I covering 3 Elixir topics at once? Well, perhaps it is to show you how the three are used together.<br />
<br />
Individually, any of these 3 are interesting, but combined, they provide you with a means of essentially getting rid of conditionals and spaghetti logic.<br />
<br />
Consider the following function.<br />
<br />
<pre><code class='elixir'>
def greet_beatle(person) do
case person.first_name do
"John" -> "Hello John."
"Paul" -> "Good day Paul."
"George" -> "Georgie boy, how you doing?"
"Ringo" -> "What a drummer!"
_-> "You are not a Beatle, #{person.first_name}"
end
end
</code></pre>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">Yeah, it basically works, but there is a big old case statement in there. If you wanted to do something more as well depending on the person, you could easily end up with some spaghetti logic. Let's see how we can simplify this a little.</span><br />
<pre><code class='elixir'>
def greet_beatle(%{first_name: first_name}) do
case first_name do
"John" -> "Hello John."
"Paul" -> "Good day Paul."
"George" -> "Georgie boy, how you doing?"
"Ringo" -> "What a drummer!"
_-> "You are not a Beatle, #{first_name}"
end
end
</code></pre>
<br />
First thing we did was destructuring. We took the person map and took out the first_name. Not so much of a big deal at first, but this allows us to do something really neat in the next instance.<br />
<br />
But first I will digress. In Elixir, you can actually define a function multiple times with the same name. It can use the arity (number of arguments) to determine which function to run. For example:<br />
<pre><code class='elixir'>
def full_name(first_name, last_name) do
"#{first_name} #{last_name}"
end
def full_name(first_name, middle_name, last_name) do
"#{first_name} #{middle_name} #{last_name}"
end
</code></pre>
<br />
When you call <span style="font-family: "courier new" , "courier" , monospace;">full_name("John", "Smith")</span> the first function will run and you get "John Smith". When you call <span style="font-family: "courier new" , "courier" , monospace;">full_name("John", "Edgar", "Smith")</span> the second function will run and you will get "John Edgar Smith".<br />
<br />
However, more than that, you can combine function overloading with pattern matching. This gives you some more options. Let's combine these features into our example.<br />
<br />
<pre><code class='elixir'>
def greet_beatle(%{first_name: "John"}) do
"Hello John."
end
def greet_beatle(%{first_name: "Paul"}) do
"Good day Paul."
end
def greet_beatle(%{first_name: "George"}) do
"Georgie boy, how you doing?"
end
def greet_beatle(%{first_name: "Ringo"}) do
"What a drummer!"
end
def greet_beatle(%{first_name: first_name}) do
"You are not a Beatle, #{first_name}"
end
</code></pre>
So as you can see, we can completely eliminate our case statement from earlier (and case is basically a conditional). This allows us to have a flatter hierarchy and makes the code easier to read.<br />
<br />
The other advantage is that this can provide more robust error handling. Let's say the map you are passing in does not even have a first_name. In the first version of the code, this would give you an error. You can now add another function handler at the bottom.<br />
<br />
<pre><code class='elixir'>
def greet_beatle(_) do
"I'm sorry, I did not get your name"
end
</code></pre>
We will first run through the first 5 definitions which pattern match on the first_name attribute. If there is no first_name attribute, then we will move to the next definition. In this case I used the _ which is a pattern matching catch all.<br />
<br />
The other thing we get from destructuring is it helps us deal with highly nested data structures. Let's say that a Person, on top of having a first and last name, has an address (which may or may not be null) and that address has a street which you want to use in the greeting.<br />
<br />
e.g.<br />
<br />
<pre><code class='elixir'>
def greet(person) do
case person.address do
nil -> "Hello #{person.first_name}"
address -> "Hello #{person.first_name} from #{person.address.street} street."
end
end
</code></pre>
<br />
The above code will work, but is a little messy and hard to read. Not only that but person.address.street breaks the <a href="https://en.wikipedia.org/wiki/Law_of_Demeter">Law of Demeter</a><br />
<br />
Consider instead the following<br />
<br />
<pre><code class='elixir'>
def greet(%{first_name: first_name, address: %{street: street}}) do
"Hello #{first_name} from #{street} street."
end
def greet(%{first_name: first_name}) do
"Hello #{first_name}"
end
</code></pre>
Destructuring and pattern matching helps us with the law of Demeter and also makes the code flatter.<br />
<br />
If you come from Ruby, the above code might seem a little backwards at first. I admit that I sometimes still write my functions initially by passing in the whole map and I tend to destructure once I have worked out the logic inside. Always remember that the first time you write a function is not necessarily how it will look in the end though and I always look for pattern matching and destructuring opportunities in code I have already written.<br />
<br /><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-61429504752515323542020-01-12T13:48:00.000-08:002020-01-20T00:04:03.710-08:00Comparing Rails' Active Record Pattern with Phoenix/Elixir/EctoRails has a very well established Active Record pattern for dealing with the database. You have an Active Record model which maps to the database table, the schema of the model comes directly from the database schema and you place your model specific methods on the Active Record model. This file is also where you set your model relationships (e.g. has_many, has_one, belongs_to). Your instance of the model has all the methods built in.<br />
<br />
In Ecto/Phoenix it's a little different. First of all, the database schema doesn't automatically map to the "model". In fact we don't really have models (as Elixir is a functional paradigm). What happens in one file in Rails, happens in essentially two (or more). You have a schema file (where you have to list out all the attributes and relationships). Using the schema file, your "instance" is essentially a data structure (with no methods on it). If you want to transform the data on your struct, you would use a context module (basically a collection of functions which take the struct in one format and return it in another).<br />
<br />
In the project I am working on, we added another convention which was to have a contextual data layer which dealt with all the CRUD operations of our schema. I will probably do a follow up on this, but essentially using macros we created a pseudo inheritance pattern where all our data layer modules get default read and write actions (as well as return the data in tuples for error handling).<br />
<br />
Anyways, an example is worth a thousand lines of text, so let's see how we would implement a basic blog data model in Rails vs Phoenix.<br />
<br />
Let's start with 3 tables. Author, Article, and Comment.<br />
<br />
One author has many articles, one article has many comments and a comment also has an author as well.<br />
<br />
<i>Rails</i><br />
<pre><code class='ruby'>
class Article < ApplicationRecord
belongs_to :author
has_many :comments
end
class Author < ApplicationRecord
has_many :articles
has_many :comments
def full_name
"#{first_name} #{last_name}"
end
end
class Comment < ApplicationRecord
belongs_to :articles
belongs_to :comments
end
</code></pre>
<br />
In Rails, this is pretty much all your models need. All the attributes of each will come directly from the database schema. If there is any functionality you want to add to the model you can also do so directly here. For example all instances of Author will have the full_name method automatically.<br />
<br />
<i>Elixir</i><br />
<pre><code class='elixir'>
defmodule Schema.Article do
use Ecto.Schema
import Ecto.Changeset
alias Schema.Author
alias Schema.Comment
@permitted_fields [:title, :content]
@required_fields [:title, :content]
schema "article" do
belongs_to(:author, Author)
has_many(:comments, Comment)
field(:title, :string)
field(:content, :string)
timestamps()
end
def changeset(article, params) do
article
|> cast(params, @permitted_fields)
|> validate_required(@required_fields)
end
end
defmodule Context.Articles do
import Ecto.Query, warn: false
alias Context.Repo
alias Schema.Article
def find(id) do
Repo.get(Article, id)
end
...
#implement other data access methods as required
...
end
defmodule Schema.Author do
use Ecto.Schema
import Ecto.Changeset
alias Schema.Article
alias Schema.Comment
@permitted_fields [:first_name, :last_name]
@required_fields [:first_name, :last_name]
schema "author" do
has_many(:comments, Comment)
has_many(:articles, Article)
field(:first_name, :string)
field(:last_name, :string)
timestamps()
end
def changeset(author, params) do
author
|> cast(params, @permitted_fields)
|> validate_required(@required_fields)
end
end
defmodule Context.Authors do
import Ecto.Query, warn: false
alias Context.Repo
alias Schema.Author
def find(id) do
Repo.get(Author, id)
end
...
#implement other data access methods as required
...
# PS this is also an example of destructuring which I will cover later
def full_name(%{first_name: first_name, last_name: last_name}) do
"#{first_name} #{last_name}"
end
end
defmodule Schema.Comment do
use Ecto.Schema
import Ecto.Changeset
alias Schema.Article
alias Schema.Author
@permitted_fields [:text]
@required_fields [:text]
schema "comment" do
belongs_to(:article, Article)
belongs_to(:article, Author)
field(:text, :string)
timestamps()
end
def changeset(comment, params) do
comment
|> cast(params, @permitted_fields)
|> validate_required(@required_fields)
end
end
</code></pre>
<br />
Now at first glance, this is obviously much more verbose. You can cut down on this verbosity using macros (which I will demonstrate later) but for the purposes of illustration, it's actually in some way more "obvious" in that there is less hidden functionality here. You also get a bit more flex ability (for one thing, you can access multiple repos quite easily in Phoenix which is very hard to do in Rails).<br />
<br />
When you instantiate an Author, all you really get is a data struct.<br />
<br />
For example<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">author = Context.Authors.find(1)</span><br />
<br />
returns<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">%Author{first_name: "John", last_name: "Smith"})</span><br />
<br />
If you want to get the author's full name you do so in the following manner<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">full_name = Context.Authors.full_name(author)</span><br />
<br />
In Rails this would be<br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">author = Author.find(1)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">author.full_name</span><br />
<br />
Calling <span style="font-family: "courier new" , "courier" , monospace;">author.full_name</span> in Elixir would result in an error (as the struct only contains the first_name and last_name).<br />
<br />
At first glance, this would make it seem like an obvious win for Rails. Look at how much code you need to do the same thing in Elixir! Look at all that "boilerplate"! I agree, some of the boiler plate could have been avoided (I do like the fact that in Rails the schema is automatically based on the database schema).<br />
<br />
However, what if you created a new kind of struct/object that was based on a person (say an Admin as opposed to an Author) and you wanted to give that a full_name method, refactoring in Elixir is much easier. As the context is not directly tied to the model, you could create a User context which can take in any struct with a first_name and last_name and return the same result (there is no state to worry about in a functional context).<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">defmodule Context.Users do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> # PS this is also an example of destructuring which I will cover later</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> def full_name(%{first_name: first_name, last_name: last_name}) do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "#{first_name} #{last_name}"</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end</span><br />
<br />
Then you would have<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">author = Context.Authors.find(1)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">author_full_name = Context.Users.full_name(author)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">admin = Context.Admins.find(1)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">admin_full_name = Context.Users.full_name(admin)</span><br />
<br />
What I found in Phoenix and Elixir is that you can move great chunks of code around quite easily compared to Rails because there is no state on the models/structs. Everything is a series of data transformations. The functionality is separate from the data.<br />
<br />
Another thing that looks weird is having to define the changeset. The changeset is automatically defined for you in Rails. What we mainly use it for is to allow us to easily have different changesets for different situations (e.g. inserting vs updating). You might even have a multi phase scaffolding process which only requires some fields to be updated at a time (user sign up is one). In Rails, this is an all or nothing situation (e.g. you either have all the required fields at once, or the model is invalid).<br />
<br />
In practice, you might want to have a Data context which deals solely with tying the schema to the database (e.g. the CRUD operations) and then put any extra functionality into another context. These data contexts can be based off macros to give an inheritance like functionality. I will cover this in the next article.<br />
<br />
For myself, when I moved from Rails to Elixir, it made me realise how much was going on behind the scenes that I took for granted. Most of the time this isn't an issue, but it does lead to a little inflexibility (and this is always a trade off).<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-26055428750922477152016-08-23T21:21:00.001-07:002016-08-23T21:39:01.992-07:00JavaScript on Rails 2016Whilst I am primarily a JavaScript programmer, my framework of choice is Ruby on Rails. A lot of JS guys like Node or other frameworks which are JS all the way through, but there's a lot to like about Rails, even in 2016 and it's worth learning another language (Ruby) or two (SQL) in order to get the best environment on which to write your JavaScripts.<br />
<br />
Before I say what I like about Rails though, I will take you through what I don't like just to balance things out.<br />
<h3>
</h3>
<h2>
What I don't like about Rails</h2>
<h3>
Asset Pipeline still does not let you use ES6 Modules. </h3>
Even at this late stage, one still cannot access ES6 Modules in Rails in a "Railsy" manner. You can either bypass the whole Asset Pipeline or else separate out the JS using Webpack or whatever, but in a standard Rails project, the fact that the asset is generated with unique hash in the file name before the extension pretty much nullifies its use for ES6 Modules which need a static location to store the files. You can probably try putting the library files directly in the public directory, but nowadays a lot of these need to be built first which means some kind of build system needs to be involved. DHH, please get on this!<br />
<br />
<h3>
Turbolinks</h3>
Turbolinks works great in theory, but in practice, as soon as you try to use some third party code which depends on document.ready, you are up a certain creek without a paddle. Not only that, but your JS will live on in memory between pages and if you are not careful about how you write your code, the browser's memory could end up filling up and slowing the user's experience to a crawl (but this is a problem most SPAs face).<br />
<br />
<h3>
Gem versions of JavaScript libraries</h3>
<div>
This is really a convenience thing that is only convenient if you don't mind running out of date code. Because the asset pipeline makes it harder to pull in 3rd party libraries and their dependencies, some nice people out there will create gem packages of these JS libraries which you can install into your Rails app and voila! They (mostly) just work! Until you want to access a new feature of the library and the gem maintainer has decided to go on an extended holiday. Then you end up trying to pull it in yourself and find out you need to change all the embedded stylesheet links because of the Asset Pipeline (see above)</div>
<div>
<br /></div>
<h3>
CoffeeScript</h3>
<div>
I love coffee, I love scripts, but I hate CoffeeScript. Thankfully this seems to be on its way out as ES6 becomes more prominent (and ES6 did pull a few good things from CoffeeScript) but CoffeeScript is not JavaScript and it used to be the "defacto" Rails standard</div>
<div>
<br /></div>
<h2>
What I like about Rails</h2>
<div>
Now that we are done with all the things I don't like about Rails (as a JavaScript programmer), I can talk about the things I do like.</div>
<div>
<br /></div>
<h3>
Turbolinks</h3>
<div>
Wait, wasn't that in the dislike section? Yes it was, but there a number of things to like about Turbolinks as well.</div>
<div>
<br /></div>
<div>
Turbolinks allows you to (almost) get the speed benefits of an SPA (Single Page App) without the complexity. It also means you don't have to pivot techniques mid stream. Often when you start writing a Rails app you leave out the JavaScript sprinkles just to make sure your models and assumptions are interacting correctly and using Turbolinks, you can add in the JS goodness without having to rewrite everything as services.</div>
<div>
<br /></div>
<h3>
It's very thin</h3>
<div>
Compared to a lot of other web frameworks, Rails pretty much gets out of the way. If you spend the time modelling your data properly, the Rails layer should be very very thin. They say fat models and skinny controllers, but you can even get away with relatively thin models too if you are going to push a lot of the logic up to JavaScript layer.</div>
<div>
<br /></div>
<h3>
Ruby</h3>
<div>
Ruby as a language is a dream to work with and a lot of the functionality of underscore and lodash were inspired by Ruby's Enumerable methods (things like each and first and last and reject and I could go on...). Also the ActiveRecord framework ties in directly to how models in 3rd normal form relate to each other so that when you do have to work in the Rails layer it's quite simple.</div>
<div>
<br /></div>
<h3>
React-rails Gem</h3>
<div>
The react-rails gem is more or less plug and play. The only problem is the lack of ES6 Module support. https://github.com/reactjs/react-rails</div>
<div>
<br /></div>
<h3>
Jasmine Gem</h3>
<div>
The jasmine gem (https://github.com/jasmine/jasmine-gem) is beginning to show it's age a little bit (still no ES6 support) but it's a very handy way to test your JavaScript components (react or otherwise). If you assume all the JS is available in memory as objects and then only instantiate instances as needed (which is indeed the way Rails works with Ruby objects) when you are golden.</div>
<div>
<br /></div>
<h2>
Postscript</h2>
<div>
Even as a modern Rails developer, you will find yourself writing lots of JavaScript. Vice versa, there is plenty to like about Rails if you are a JavaScript developer. What you lack in monolingualism, you gain in terms of having each language perfectly suited to its task. This is akin to listening to Italian Opera and Norwegian Death Metal.</div>
<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-9097245982723644132016-07-26T18:40:00.004-07:002016-07-26T18:41:44.632-07:00React: Viewless componentsI have been using Facebook's React library for a while now (https://facebook.github.io/react/) and I like it for the most part.<br />
<div>
<br /></div>
<div>
The most controversial aspect is the fact that the View is mixed into the Logic using what looks like HTML in the "JavaScript" file using JSX.</div>
<div>
<br /></div>
<div>
While it is possible to write React components in plain JavaScript, you must include a render() method.</div>
<div>
<br /></div>
<div>
However, it is actually possible to do the following...</div>
<div>
<br /></div>
<div>
(in ES6 syntax)</div>
<div>
<br /></div>
<div>
<div>
render() {</div>
<div>
return (null);</div>
<div>
}</div>
</div>
<div>
<br /></div>
<div>
Now, why would you want to do such a thing? I thought the whole point was to use React to tie together your view and logic layers (and shouldn't you be abstracting out your heavy logic to plain JS classes?).</div>
<div>
<br /></div>
<div>
Well, the main reason you might want to do it is because of React's component lifecycle.</div>
<div>
<br /></div>
<div>
In my case, I wanted to trigger some JS to run when the component was loaded, and I happen to be using Turbolinks and React-Rails in a Rails project.</div>
<div>
<br /></div>
<div>
This JS had no view component, but I could not rely on document.ready because of Turbolinks, and I didn't want to put an inline script tag in the page (because who wants to mix JS and HTML together?). I was however happy to use the react_component helper to add in a React component and then trigger it on componentDidMount.</div>
<div>
<br /></div>
<div>
It's also a handy way to add functionality onto some components which for whatever reason need to be rendered by the server (i.e. adding a JS slide show component onto a bunch of rendered divs without having to pass all the images into react_component).</div>
<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-16079131161181535752016-04-07T18:36:00.003-07:002016-04-07T18:39:17.034-07:00Testing React components with Jasmine in Rails with the react-rails gemSo in my latest project, we are using Ruby on Rails and the react-rails gem to implement our React components.<br />
<br />
While react-rails is fine for the most part (and for simple components), there is a severe limitation in that you cannot use ES6 modules (i.e. using the import and export classes) because of the way asset-pipeline works.<br />
<br />
Asset-pipeline precompiles all your JS into one mega JS file and therefore doesn't allow you to call individual JavaScript components in separate files (because they don't pushed to the server). This is a severe problem which I hope gets fixed soon, but in the meantime don't hold your breath.<br />
<br />
There are workarounds using Browserify and/or Webpack, but they are messy (Google them for more info).<br />
<br />
In any case, react-rails does allow simple React components in Rails apps and it mounts them all in the window/global namespace, so you can use a good 75% of React within a Rails application.<br />
<br />
So now, we come to testing...<br />
<br />
Facebook uses a testing framework called Jest (https://facebook.github.io/jest/docs/tutorial-react.html). It looks pretty good, but unfortunately does require the use of the import/export syntax...<br />
<br />
But hey, React components are just JavaScript right? That means you can test them with any library (like Jasmine https://github.com/jasmine/jasmine-gem). Right?<br />
<br />
Well, they are JavaScript, but they are also kind of closed off and it can be hard to actually unit test and mock them. I created a React component called DailyQuiz and for testing I tried to simply instantiate it normally.<br />
<br />
var dailyQuiz = new DailyQuiz();<br />
<br />
I was then able to see all the attributes on dailyQuiz in the console as if it were a regular JS object.<br />
<br />
Until that is, I ran into setState in the code. Unfortunately, it gave me some guff about<br />
<br />
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the component.<br />
<br />
So, basically, this means we can't treat a React component as a plain JS object because there is a lot happening behind the scenes. In a way this makes sense because React does a lot of calculations before outputting to the DOM, so these restrictions are probably needed.<br />
<br />
So rather than trying to fake the whole mounting of the component, I decided it would be better to actually mount it. I used jasmine-jquery (https://github.com/travisjeffery/jasmine-jquery-rails) to help me manipulate the DOM with jasmine.<br />
<br />
I will show you the code snippet and then talk you through it.<br />
<pre>
beforeEach(function() {
deferred = new jQuery.Deferred();
spyOn($, 'ajax').and.returnValue(deferred);
setFixtures('[div id="react-fixture"]
test[/div]
');
dailyQuiz = ReactDOM.render(
React.createElement(DailyQuiz, {
url: "/",
saveUrl: "/save"
}),
document.getElementById('react-fixture')
);
deferred.resolve(data);
});
</pre>
<div>
<br /></div>
* note for this example I had to replace the < and > with [ and ] for rendering purposes... Blogger is giving me some hassle...<br />
<br />
So basically I am using $.ajax with the promise syntax and then spying on it so I can intercept the ajax call in my spec. I then use jasmine-jquery's setFixtures to create a place to mount the component, I mount the component with plain JS (not JSX) and the resolve the AJAX call.<br />
<br />
From there, you can actually call dailyQuiz and it will reflect the state of the component.<br />
<br />
The downside of this approach is that the data you use to populate your component has to actually work (you can't just stub any old data through it). There may be a way to spy on the subcomponents, but I haven't found a way yet.<br />
<br />
The benefits are that you can test the actual state of the actual component. It's just not a headless component (it needs to be mounted). You can also use jasmine-jquery to test the DOM meets expectations.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-48435122566254943112016-03-17T13:26:00.002-07:002016-03-17T13:27:11.055-07:00Rolify, Devise, Rspec and Capybara Webkit in RailsSo I was using Rolify, Devise, Rspec and Capybara Webkit in Rails and I ran into a problem with the Warden login_as helper when trying to do a feature spec with :js => true (if you didn't understand any of that, you are probably on the wrong blog).<br />
<br />
Anyways, while the I was able to log in an admin, Rolify was not able to determine the roles. This is in spite of the fact that I added a role in Factory Girl.<br />
<pre></pre>
<pre>FactoryGirl.define do
factory :admin do
email { Faker::Internet.email }
password "password"
password_confirmation "password"
trait :administrator do
after(:create) {|admin| admin.add_role(:administrator)}
end
end
end
</pre>
<pre></pre>
This worked fine with js turned off, so why was it not working now?<br />
<br />
So the answer had to do with the following line in rails_helper.rb<br />
<br />
config.use_transactional_fixtures = true<br />
<br />
So I believe what happened in Capybara webkit is that it runs in a different thread and while Warden's login_as worked fine in a regular spec (as the admin object is passed into memory), the role information (which needs a database lookup) was not passed through and hence when I was checking that the admin was an administrator (current_admin.has_role? :administrator) it failed.<br />
<br />
So here's the fix<br />
<br />
1) Install the database_cleaner gem<br />
<br />
2) set transactional fixtures to false<br />
config.use_transactional_fixtures = false<br />
<br />
3) Set up database cleaner as per http://devblog.avdi.org/2012/08/31/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/<br />
<br />
4) in your rails_helper.rb<br />
require 'support/database_cleaner'<br />
<br />
Your Capybara Webkit Rolify specs should now work with the Warden helpers.<br />
<br />
PS Sorry this isn't that well written. This is primarily a reminder for myself (I am still in mid project), but I thought I would post it in case anyone else ran into the same issue.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com1tag:blogger.com,1999:blog-6915823.post-37148301343519440012015-07-02T18:48:00.002-07:002015-07-02T18:51:38.890-07:00Global Warming is a Year 2000 problemGlobal Warming is a Year 2000 problem.<br />
<br />
What I mean is not that Global Warming has been solved (far from the case) nor that it is a problem that we could only have solved 15 years ago (though that might be closer to the truth).<br />
<br />
What I mean is that if we solve Global Warming, then people will think it was a hoax (or something we should not have worried about).<br />
<br />
Think back to the glory days of the 1990s. Media was full of stories about how all the world's computer systems were going to fail because programmers tried to save space on their systems by dropping the first 2 digits off the year field. Power stations and ATMs would stop working. Global financial crises would ensue (well, that did happen, but for other reasons...). Basically we were going back into the stone age unless we chucked large amounts of money at software developers to fix our computer systems and by and large that did happen.<br />
<br />
After we partied like it was 1999, apparently nothing happened. I am sure a couple of systems failed, but nothing major. We were all still alive and we still had electricity and we could still get money out of ATMs.<br />
<br />
Post 2000, everyone now thinks the whole thing was just a bunch of hooey.<br />
<br />
But maybe we did avert major disaster by all our hard work. We will never really know.<br />
<br />
I hope to an extent the same thing does happen with Global Warming. There are differences of course. We have not got a set date where the planet will be uninhabitable. A date when we can look back and laugh at ourselves for being so silly.<br />
<br />
Instead, we will look back at ourselves in shame for being so oblivious (and remember what things used to be like).<br />
<br />
There's probably not much we can do on an individual level. We will need to elect people who are aware of the problem and reach consensus (easier said than done).<br />
<br />
Let's just hope that in 7984 years we have a Year 10,000 problem to deal with ("Why on earth did they think they could save space by only allocating 4 digits to a year?").<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-14049850812445042002015-06-17T20:30:00.000-07:002015-06-17T20:30:06.869-07:00Source Location, i.e. Where the heck is this method defined?Just a short one for now.<br />
<br />
Most of the time, if you are working on a well defined project, it's pretty easy to find where a function is if you want to change/tweak it. You can look in the class where it is defined and/or see if there are any mixins or decorators.<br />
<br />
Sometimes, it can be downright impossible to tell.<br />
<br />
Enter source_location<br />
<br />
Fire up your rails console and put in the following (for a class method :search on User)<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">User.method(:search).source_location</span><br />
<br />
if you have an instance method<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">User.new.method(:search).source_location</span><br />
<br />
This should give you something like the following<br />
<br />
<div class="p1">
<span style="font-family: Courier New, Courier, monospace;"><span class="s1">[</span><span class="s2">"/Users/yourdrive/Projects/prokect/vendor/cache/thinking-sphinx-9b71ce0f7d9d/lib/thinking_sphinx/search_methods.rb"</span><span class="s1">,</span></span></div>
<br />
<div class="p2">
<span style="font-family: Courier New, Courier, monospace;"><span class="s1"> </span><span class="s2">367</span><span class="s1">]</span></span></div>
<div class="p2">
<span class="s1"><br /></span></div>
<div class="p2">
<span class="s1">This should at least give you some idea of what is going on...</span></div>
<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-42449795403400238952015-06-14T20:30:00.004-07:002015-06-17T20:30:43.895-07:00Mixins in ES6I had been looking for a good way to do mixins in ES6.<br />
<br />
Looks like I finally found one.<br />
<br />
<a href="http://raganwald.com/2015/06/10/mixins.html">http://raganwald.com/2015/06/10/mixins.html</a><br />
<br />
Essentially,<br />
<br />
create the mixin as a const<br />
<br />
Use Object.assign to add the properties to the Class<br />
<br />
<pre style="background: rgb(48, 48, 48); border: 0px; color: #f2f2f2; font-family: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-size: 14px; font-stretch: inherit; line-height: inherit; margin-bottom: 30px; overflow: auto; padding: 20px; text-shadow: none; vertical-align: baseline;"><code class="language-javascript" data-lang="javascript" style="border: none; font-family: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Terminal, monospace; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px 0px 30px; padding: 0px; vertical-align: baseline;"><span class="kr" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">const</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">BookCollector</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">=</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">addToCollection</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">name</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">)</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">collection</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">().</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">push</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">name</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">);</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">return</span> <span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">;</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">},</span>
<span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">collection</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">()</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">return</span> <span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">_collected_books</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">||</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">_collected_books</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">=</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">[]);</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">}</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">};</span>
<span class="kr" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">class</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Person</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">constructor</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">first</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">,</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">last</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">)</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">rename</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">first</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">,</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">last</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">);</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">}</span>
<span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">fullName</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">()</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">return</span> <span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">firstName</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">+</span> <span class="s2" style="border: 0px; color: #dd1144; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">" "</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">+</span> <span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">lastName</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">;</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">}</span>
<span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">rename</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">first</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">,</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">last</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">)</span> <span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">{</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">firstName</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">=</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">first</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">;</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">lastName</span> <span class="o" style="border: 0px; color: #666666; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">=</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">last</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">;</span>
<span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">return</span> <span class="k" style="border: 0px; color: green; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: bold; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">this</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">;</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">}</span>
<span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">};</span>
<span class="nb" style="border: 0px; color: #0086b3; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Object</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">assign</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">(</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">Person</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">prototype</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">,</span> <span class="nx" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">BookCollector</span><span class="p" style="border: 0px; font-family: inherit; font-size: inherit; font-stretch: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; line-height: inherit; margin: 0px; padding: 0px; vertical-align: baseline;">);</span></code></pre>
(I am copying and pasting this for personal reference in case the original site link goes down).<br />
<br /><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-50484684726855089822015-05-28T23:45:00.002-07:002015-05-28T23:45:19.938-07:00The perils of over testingTest Driven Development (TDD) is a great thing. It allows us to write code "safe" in the knowledge that if we accidentally break something in another part of the system, we will be alerted to it and can re-write or fix accordingly.<br />
<br />
It's such a good thing that often in large teams people will insist on this idea of 100% code coverage (i.e. every piece of working code has a corresponding test). Also, some people are compelled to test every single permutation and variation of how someone will access their program/website.<br />
<br />
TDD does have some costs however. Some are evident, but others not so.<br />
<br />
<b>Cost 1 - Tests take time to run</b><br />
True, automated testing is faster than manual testing, but they do take time to run. This time means every time you make a change to the code, you need to wait for the suite to finish running in order to feel that your code is "safe" for deployment.<br />
<br />
Most of this time is a necessary evil.<br />
<br />
A lot of it is unnecessary...<br />
<br />
Should you write a feature that checks to see that a form is disabled if a text box is not checked? Well, it depends...<br />
<br />
If this is essential to the end user getting to a process, then possibly yes.<br />
<br />
If this is just to check a possible error condition that has little or no bearing on the final result, then possibly no.<br />
<br />
You could maybe write it as you are developing a feature and then remove it when you are done.<br />
<br />
Or else you can write an Object Oriented JS component (FormDisabler) and then test its logic with Jasmine.<br />
<br />
Bear in mind that testing JavaScript in a feature will bring up the whole stack which takes time. Time is valuable.<br />
<br />
One analogy is that testing is like packing for a trip. You might think that you will need to bring everything you have to cover every possible situation, but really you probably only need an overnight bag with a couple of changes of clothes. The cost is that the more you bring, the more you have to carry.<br />
<br />
<b>Cost 2 - Brittleness</b><br />
Over testing can lead to a very brittle code base where one small change can lead to many hours of fixing broken tests. In Agile development you will constantly make changes to the code base and often what is wanted in the end is not what is expressed in the beginning.<br />
<br />
If you test every single possible scenario, then a small business change can lead to a domino effect of broken tests.<br />
<br />
The key is to identify what aspects/methods of a feature are most important and to test those.<br />
<br />
Classes should be made bulletproof, but not features.<br />
<br />
Martin Fowler's Test Pyramid is a good guide.<br />
<br />
<a href="http://martinfowler.com/bliki/TestPyramid.html">http://martinfowler.com/bliki/TestPyramid.html</a><br />
<br />
<b>Summary</b><br />
At the end of the day, you have to use your brain and judgement to determine what tests are important and what tests are not. It is a subjective thing and can vary from team to team (and application to application). Back to the packing analogy, you should only pack what you need for the trip. If you are writing something mission critical that can never go down and the slightest mistake can lead to potential loss of human life, then by all means go for 100% test coverage. On the other hand, if a mistake in part of your app means a comment or review might not get posted, then test accordingly.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-24639643691358196962015-04-12T23:23:00.003-07:002015-04-12T23:23:50.285-07:00ECMAScript 6Today is a rather significant one for me as JavaScript/Rails programmer because Sprockets (<a href="https://github.com/rails/sprockets">https://github.com/rails/sprockets</a>) upgraded to version 3 meaning I can now use the Sprockets-es6 gem (<a href="https://github.com/josh/sprockets-es6">https://github.com/josh/sprockets-es6</a>) to transpile ES6 JavaScript to ES5.<br />
<br />
Now to those that didn't understand the last paragraph, here's what it means...<br />
<br />
The language we commonly call JavaScript is actually formally known as ECMAScript (<a href="http://en.wikipedia.org/wiki/ECMAScript)">http://en.wikipedia.org/wiki/ECMAScript)</a>. Since 2009 all the major browsers have been interpreting ECMAScript version 5. In fact they mostly still do.<br />
<br />
Version 6 is due to be formalised any day now (well, June 2015) which means that a whole new slew of functionality is due to be added to the language (class constructors, formal inheritance, fat arrows, etc...). Browser makers will start implementing these new features once the spec is complete.<br />
<br />
All of this sounds great, but for 2 things<br />
<br />
1) I want to start using ES6 today<br />
2) Even if the newly released browsers did come out today, people are not all going to upgrade their browsers as soon as the new ones come out. Typically there is about a year lag before a new feature (or set of features) has enough critical mass to be used with any confidence.<br />
<br />
So what can we do?<br />
<br />
Well thankfully there are transpilers which can take most of the features of ES6, and rewrite them as ES5. The 2 main ones are Babel (<a href="https://babeljs.io/">https://babeljs.io/</a>) and Traceur (<a href="https://github.com/google/traceur-compiler">https://github.com/google/traceur-compiler</a>). For various reasons, I chose to use Babel (because I found their resulting code more readable and because their gem, Sprockets-es6, seemed to have the easiest to add to our project flow). (Basically Traceur didn't work with Phantom which is what we use for testing and is actually running ES4!).<br />
<br />
Unfortunately, sprockets-es6 had a dependency on sprockets 3 which was in beta up until this weekend.<br />
<br />
And now it's out of beta!<br />
<br />
As I write this I am just testing it out on our develop server and hopefully I will go live with it soon.<br />
<br />
Happy days.<br />
<br />
If you want an example of what can be done with ES6, you can mess around with it here.<br />
<br />
<a href="http://www.es6fiddle.net/">http://www.es6fiddle.net/</a><br />
<br />
<br /><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-51634771307958985452014-09-15T03:50:00.002-07:002014-09-15T04:01:40.599-07:00Opal - Ruby to JavaScript compilerSo I just found out about the Opal Ruby to JavaScript compiler and I am very intrigued.<br />
<br />
<a href="http://opalrb.org/">http://opalrb.org/</a><br />
<br />
So for a few years now, the Ruby community has been encouraged to use CoffeeScript as a JavaScript "alternative" (<a href="http://coffeescript.org/">http://coffeescript.org/</a>) but for some reason, as an avid JavaScript programmer, it never appealed to me. CoffeeScript offers a small amount of syntactic sugar on top of JavaScript, but it never seemed worth the learning curve for me to take it on. I don't actually mind JS's brackets (as they let you know where things begin and end cleanly) and significant white space in a programming language has always seemed like a bad idea too (it's bad enough that a misplaced semicolon can stop a program from running properly, but try searching for an extra white space character you can't even see!).<br />
<br />
Opal on the other hand, is actually Ruby compiled to JavaScript (not Ruby/Python-ish syntax). It looks, feels, and even smells like Ruby because it is. The JavaScript it generates might be a little more verbose than that generated by CoffeeScript, but that's because it has to do more (Ruby and JavaScript are separate languages in the proper sense, not different dialects). It does however compile out its classes using the module pattern which means it is safely scoped (<a href="http://toddmotto.com/mastering-the-module-pattern/">http://toddmotto.com/mastering-the-module-pattern/</a>).<br />
<br />
It also has a "Native" bridge to interact with regular JavaScript which helps maintain clean separation (<a href="http://opalrb.org/docs/interacting_with_javascript/">http://opalrb.org/docs/interacting_with_javascript/</a>) as well as RSpec support for testing. It even supports method_missing.<br />
<br />
In any case, there are many other interesting things about it, which I won't go into here.<br />
<br />
It will be interesting to see where it leads...<br />
<br />
PS I see Opal as being more like ASM.js (<a href="http://asmjs.org/">http://asmjs.org/</a>) than CoffeeScript. It can actually use a quite limited subset of JS in order to be functional.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-34323898504647056802014-04-16T00:10:00.003-07:002014-04-16T00:17:29.086-07:00Checking performance in EaselJS appsSo one of the great things about <a href="http://www.createjs.com/#!/EaselJS">EaselJS</a> is the fact that you can create Flash like games in JavaScript. I have been doing so on <a href="http://www.activememory.com/">www.activememory.com</a> for the last couple of years.<br />
<br />
Unfortunately, it came to our attention that on some systems, the games ran rather slowly. According to their own docs, they admit that the time between ticks might be greater than specified because of CPU load (<a href="http://www.createjs.com/Docs/EaselJS/classes/Ticker.html#method_setInterval">http://www.createjs.com/Docs/EaselJS/classes/Ticker.html#method_setInterval</a>).<br />
<br />
So, is there any way we can track the actual time between ticks on a real system?<br />
<br />
Fortunately, yes, we can.<br />
<br />
On each element that uses the ticker, you can set up an addEventListener and set the method you want to fire on that interval.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">createjs.Ticker.addEventListener('tick', tickListener);</span><br />
<br />
In the method itself you will want a few instance (or global) variables<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">this.previousFrameTime</span><br />
<span style="font-family: Courier New, Courier, monospace;">this.totalFrames</span><br />
<span style="font-family: Courier New, Courier, monospace;">this.totalFrameTimes</span><br />
<br />
as well as a local variable<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">currentFrameTime</span><br />
<br />
On each tick, we get the current timestamp (<span style="font-family: Courier New, Courier, monospace;">new Date()</span> in JavaScript) and compare it to the previous.<br />
<br />
We can then store the total number of ticks in the game, and then compare it to the total amount of time between frames.<br />
<br />
This will give you the average frame time.<br />
<br />
But enough of my yakking, here is a code sample (adapted from my code)<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">createjs.Ticker.setFPS(50);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Theatre = function() {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> var me = this; <span style="color: lime;">//set scope to this object</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> var canvas = $('#canvas').get(0); <span style="color: lime;">// get the canvas object</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.stage = new createjs.Stage(me.canvas); <span style="color: lime;">// create a stage object</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.previousFrameTime = null; <span style="color: lime;">// initially null</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.totalFrames = 0;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.totalFrameTimes = 0;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> createjs.Ticker.addEventListener('tick', me.tickListener);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Theatre.prototype.tickListener = function(event) {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;">var me = this;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> var currentFrameTime = new Date();</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> <span style="color: lime;">// skip the first frame because we haven't set a </span></span><span style="color: lime;"><span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;">previousFrameTime</span><span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> yet</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> if (me.previousFrameTime) {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.totalFrames++;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.totalFrameTimes += currentFrameTime - me.previousFrameTime;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> }</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.previousFrameTime = currentFrameTime; <span style="color: lime;">// save this for the next tick</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> me.stage.update(); <span style="color: lime;">// update the stage with each tick</span></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="color: lime; font-family: Courier New, Courier, monospace; font-size: x-small;">// something else calls this when the game ends</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Theatre.prototype.finish = function() {</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> var me = this;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> console.log("average frame time in milliseconds:" + (me.totalFrameTimes / me.totalFrames);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">}</span><br />
<br />
So when you subtract 2 dates in JavaScript, it returns the difference in milliseconds.<br />
<br />
If your FPS is 50, then you should have values that are round about 20 (1000 / 50 = 20). Anything much larger is an indication that your game is running much more slowly than it should be.<br />
<br />
<br /><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-14569131519849656692014-01-11T04:16:00.002-08:002017-01-30T20:26:49.399-08:00Our git workflowIn my current gig in ABC Active Memory (<a href="https://activememory.com/">https://activememory.com/</a>) we have put together a git workflow which works really well for us, so I decided I would write about it here so that others could benefit.<br />
<div>
<br /></div>
<div>
The things you need to have in place though are Continuous Deployment (<a href="http://en.wikipedia.org/wiki/Continuous_delivery">http://en.wikipedia.org/wiki/Continuous_delivery</a>), Test Driven Development (<a href="http://en.wikipedia.org/wiki/Test-driven_development">http://en.wikipedia.org/wiki/Test-driven_development</a>), Agile (<a href="http://en.wikipedia.org/wiki/Agile_software_development">http://en.wikipedia.org/wiki/Agile_software_development</a>) and the desire to keep releasing small independent incremental features without any one of them blocking each other.<br />
<br /></div>
<h2>
Branches</h2>
<div>
So the key is branches (obviously). We have 2 main branches which are shared by everyone, and every new feature takes place in a feature branch. </div>
<div>
<br /></div>
<div>
Our 2 shared branches are </div>
<div>
<ul>
<li>acceptance</li>
<li>master</li>
</ul>
<h3>
</h3>
<h3>
Feature branch</h3>
<div>
When we start a new feature, we do all the work in our feature branch which we cut from <i>master</i>. Our feature branches are named by ticket number (i.e. 575-add-invoice-to-order) so that they can reference our JIRA tickets (though any ticketing system will do) and we can keep track of them.</div>
<div>
<br /></div>
<div>
If you have been working on a feature branch for a long time, it is worth occasionally rebasing or merging from <i>integration</i> to keep it fresh. </div>
<h3>
</h3>
<h3>
Acceptance</h3>
</div>
<div>
When we think the feature is ready for the business owner to inspect, we merge it into <i>acceptance</i> and once this build is green, we deploy it to the acceptance server for the business owner to look at and (hopefully) accept. If for some reason the business owner needs some changes, we do them in our feature branch and then re-integrate into acceptance and then redeploy. Rinse and repeat until accepted.</div>
<div>
<br /></div>
<div>
Periodically, we will wipe the <i>acceptance</i> branch (when there are no pending stories in it) and re-cut it from <i>master</i> to weed out any stories that never get accepted (due to changing business needs).</div>
<h3>
</h3>
<h3>
Integration</h3>
<div>
Once the feature has been accepted, we merge it from our feature branch into <i>master</i>. <b>Never merge <i>acceptance</i> into <i>master</i> as it may have other unaccepted stories in it.</b></div>
<div>
<br /></div>
<div>
Once <i>master</i> is green, deploy it to the staging server to test the actual build. Once the feature is checked on staging, we deploy to our production servers and do another feature check. <i>Master </i>should be deployed as soon as it is ready (i.e. don't let it hang around).</div>
<h2>
</h2>
<h2>
No downtime releases</h2>
<div>
We strive for no downtime releases most of the time, but occasionally we will need some scheduled downtime. If the feature one is working on does need downtime, we normally hold off on merging it into <i>master</i> until about an hour before deployment and we make sure everyone knows about it (it's quite rare though). We even deploy migrations without downtime provided we are only adding fields or tables (renames do require downtime unfortunately).</div>
<h2>
</h2>
<h2>
Advantages</h2>
<div>
So what advantages do we get from this workflow? Well, mainly, none of us are blocking each other. All of us can release without having to worry about the state of our coworkers' stories/features. Some features take longer to get accepted than others and it gives the business owners more time to examine a feature before accepting it.</div>
<h2>
</h2>
<h2>
Downsides</h2>
<div>
The downsides are minimal once you get used to the flow, but there is a little bit of complexity to take on when you first get on board. There is a lot of merging going on and sometimes you might have to fix merge errors twice (once in <i>acceptance</i> and once in <i>master</i>).</div>
<div>
<br /></div>
<div>
The other downside is if your feature does actually have dependencies on another feature then you need to wait for it to get integrated before you start working. It is possible, if you can't wait, to cut from the other feature branch instead of <i>master</i>, but it is not a risk free approach (especially if the first feature gets cut or rejected or delayed).</div>
<div>
<br /></div>
<div>
This flow also works best if all your developers are full stack or vertical, rather than horizontal. It is the case in our team that some of us are more geared towards the front end and some of us are geared towards the back end. We normally get around this either by sharing the feature branch or else by doing code reviews.</div>
<div>
<br /></div>
<h2>
Summary</h2>
<div>
In summary, our git workflow is the following</div>
<div>
<ol>
<li>Feature branches for all new features</li>
<li>Merge feature branch into <i>acceptance</i> and deploy on acceptance server for the business owner to accept</li>
<li>Merge feature branch into <i>master</i> and make sure build is green.</li>
<li>Deploy on staging and manually check</li>
<li>Deploy to production and manually check</li>
</ol>
<h3>
Additionally</h3>
</div>
<div>
<ol>
<li>Hotfixes are done on <i>master </i>deployed ASAP</li>
<li><i>acceptance </i>is periodically deleted and re-cut from <i>master</i></li>
</ol>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-22327621563186565042013-08-07T03:30:00.000-07:002013-08-07T03:30:03.420-07:00JavaScript ConstructorsEven though I don't agree with his thoughts about semi colons, this is a really good primer on JavaScript Constructors and Prototypes...<br />
<br />
<a href="http://tobyho.com/2010/11/22/javascript-constructors-and/">http://tobyho.com/2010/11/22/javascript-constructors-and/</a><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-63249847245776007132013-08-07T03:22:00.001-07:002013-08-07T03:24:01.297-07:00IE console.log(). It works, except when it doesn't...So I made an interesting discovery today.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">console.log()</span> in IE9 only works when the console is open!<br />
<br />
As a web developer, and especially a JavaScript developer, the most useful tool in your arsenal is <span style="font-family: Courier New, Courier, monospace;">console.log()</span>. And while you should (for the most part) remove any <span style="font-family: Courier New, Courier, monospace;">console.log()</span> calls from your code as soon as you have finished debugging, sometimes your library code might contain it, or other times if you are writing a framework for an intermediary to use, you might leave a couple of messages for your intermediaries using <span style="font-family: Courier New, Courier, monospace;">console.log()</span>.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">console.log()</span> first made an appearance in Firefox's Firebug (or at least that is when I first noticed it) and has been in most major browsers, including Internet Explorer since version 8. Whilst developing, I have happily had my console open so I can debug things.<br />
<br />
Anyways, fast forward to the current app I am working on. I am lucky enough to be working on a project that uses HTML5 canvas and the project I am working on is used by a game designer (who can put in some pieces of JavaScript code) so naturally, not only do I wrap certain parts in <span style="font-family: Courier New, Courier, monospace;">try/catch</span> blocks, I want to let the game designer know his code snippet has failed so I do so using console.log.<br />
<br />
On production, we were getting some weird bugs with IE9 and I could not for the life of me figure them out because every time I opened the console, lo and behold, they disappeared. This was driving me nuts!<br />
<br />
Until today when by chance, I opened up the site in IE9 with the console closed and I got a JavaScript debugging error telling me that console was not defined?<br />
<br />
Huh?<br />
<br />
I opened the console to check, and it went away. I closed the console again but I still didn't see the error. Curious...<br />
<br />
Anyways, after some googling, I found that <span style="font-family: Courier New, Courier, monospace;">console.log()</span> does not work in IE if the console has not been opened.<br />
<br />
D'oh!<br />
<br />
Anyways, I basically got around it by creating a mock console object if the console is not present.<br />
<br />
i.e.<br />
<br />
<pre class="default prettyprint prettyprinted" style="background-color: #eeeeee; border: 0px; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif; font-size: 14px; line-height: 18px; margin-bottom: 10px; max-height: 600px; overflow: auto; padding: 5px; vertical-align: baseline; width: auto;"><code style="border: 0px; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif; margin: 0px; padding: 0px; vertical-align: baseline;"><span class="kwd" style="background-color: transparent; border: 0px; color: darkblue; margin: 0px; padding: 0px; vertical-align: baseline;">if</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">(!(</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">window</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">console </span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">&&</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> console</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">.</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">log</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">))</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: So there you have it... baseline;"> </span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">{</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
console </span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">=</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">{</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
log</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">:</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="kwd" style="background-color: transparent; border: 0px; color: darkblue; margin: 0px; padding: 0px; vertical-align: baseline;">function</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">(){},</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
debug</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">:</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="kwd" style="background-color: transparent; border: 0px; color: darkblue; margin: 0px; padding: 0px; vertical-align: baseline;">function</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">(){},</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
info</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">:</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="kwd" style="background-color: transparent; border: 0px; color: darkblue; margin: 0px; padding: 0px; vertical-align: baseline;">function</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">(){},</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
warn</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">:</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="kwd" style="background-color: transparent; border: 0px; color: darkblue; margin: 0px; padding: 0px; vertical-align: baseline;">function</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">(){},</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
error</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">:</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;"> </span><span class="kwd" style="background-color: transparent; border: 0px; color: darkblue; margin: 0px; padding: 0px; vertical-align: baseline;">function</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">(){}</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">};</span><span class="pln" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">
</span><span class="pun" style="background-color: transparent; border: 0px; margin: 0px; padding: 0px; vertical-align: baseline;">}</span></code></pre>
<pre class="default prettyprint prettyprinted" style="background-color: #eeeeee; border: 0px; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif; font-size: 14px; line-height: 18px; margin-bottom: 10px; max-height: 600px; overflow: auto; padding: 5px; vertical-align: baseline; width: auto;">(credit: <a href="http://stackoverflow.com/questions/10183440/console-is-undefined-error-in-ie9" style="background-color: transparent;">http://stackoverflow.com/questions/10183440/console-is-undefined-error-in-ie9</a>)</pre>
<pre class="default prettyprint prettyprinted" style="border: 0px; font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif; font-size: 14px; line-height: 18px; margin-bottom: 10px; max-height: 600px; overflow: auto; padding: 5px; vertical-align: baseline; width: auto;"><span style="background-color: white;">
</span></pre>
So there you have it...<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-45688820683624989652012-06-21T21:33:00.000-07:002012-06-21T21:37:55.957-07:00Screen scraping with NokogiriSo a few months ago I put together a side project at <a href="http://www.myrecipesavour.com/">http://www.myrecipesavour.com/</a> . Basically, the site allows you to put in the URL of a cooking recipe page and will then parse the recipe for your collection.<br />
<br />
So it turns out, reading data from another site is very easy with Nokogiri.<br />
<br />
The source code is available here <a href="https://github.com/abreckner/MyRecipeSavour">https://github.com/abreckner/MyRecipeSavour</a><br />
<br />
There is a lot I am going to cover in the next few posts based on this code base (like Devise and Heroku), but for now we are focussed on this file <a "="" href="https://github.com/abreckner/MyRecipeSavour/blob/master/app/models/site.rb">https://github.com/abreckner/MyRecipeSavour/blob/master/app/models/site.rb</a><br />
<br />
So we are going to look at the add_recipe method.<br />
<br />
First we need to require a few packages<br />
<pre style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; padding: 0px;"><div class="line" id="LC1" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="nb" style="border: 0px; color: #0086b3; margin: 0px; padding: 0px;">require</span> <span class="s1" style="border: 0px; color: #dd1144; margin: 0px; padding: 0px;">'open-uri'</span></div>
<div class="line" id="LC2" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="nb" style="border: 0px; color: #0086b3; margin: 0px; padding: 0px;">require</span> <span class="s1" style="border: 0px; color: #dd1144; margin: 0px; padding: 0px;">'rubygems'</span></div>
<div class="line" id="LC3" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="nb" style="border: 0px; color: #0086b3; margin: 0px; padding: 0px;">require</span> <span class="s1" style="border: 0px; color: #dd1144; margin: 0px; padding: 0px;">'nokogiri'</span></div>
<div class="line" id="LC4" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
</div>
</pre>
Unfortunately, I haven't yet figured out a heuristic for separating a recipe web page into a recipe's components (Title, Ingredients, Instructions, Amounts, etc...) but as a workaround, I maintain a catalogue of CSS selectors which define these elements per domain. When I read the page, I use NokoGiri to parse those elements for me using the CSS selectors<br />
<br />
i.e.<br />
<span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;"> html</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">=</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="no" style="border: 0px; color: teal; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">Nokogiri</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">::</span><span class="no" style="border: 0px; color: teal; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">HTML</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">(</span><span class="nb" style="border: 0px; color: #0086b3; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">open</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">(</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">url</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">)</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">read</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">) # open the page</span><br />
<span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">title</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">=</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">html</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">css</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">(</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">site</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">title_selector</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">)</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">text</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">strip # read the title</span><br />
<br />
I then populate a recipe object with these pieces<br />
<br />
<br />
<pre style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; padding: 0px;"><div class="line" id="LC36" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">recipe</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="no" style="border: 0px; color: teal; margin: 0px; padding: 0px;">Recipe</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">new</span></div>
<div class="line" id="LC37" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">recipe</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">name</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="n" style="border: 0px; margin: 0px; padding: 0px;">title</span></div>
<div class="line" id="LC38" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
...</div>
<div class="line" id="LC41" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">recipe</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">save</span></div>
</pre>
<br />
<br />
My code around the ingredients and instructions is a little more complex as my Recipe model has many Ingredients and Instructions (eventually I am going to allow users to manipulate them individually). Each ingredient/instruction is parsed based on a line break, so I need to pull in the ingredient array from Nokogiri and then merge it into a string separated by line breaks.<br />
<br />
<span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">ingredients</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">=</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">html</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">css</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">(</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">site</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">ingredient_selector</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">)</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">children</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">inject</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">(</span><span class="s1" style="border: 0px; color: #dd1144; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">''</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">){</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">|</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">sum</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">,</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">n</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">|</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">sum</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">+</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">n</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">text</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">+</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="s2" style="border: 0px; color: #dd1144; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">"</span><span class="se" style="border: 0px; color: #dd1144; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">\n</span><span class="s2" style="border: 0px; color: #dd1144; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">"</span><br />
...<br />
<span class="no" style="border: 0px; color: teal; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">Ingredient</span><span class="o" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; font-weight: bold; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">.</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">multi_save</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">(</span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">ingredients</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">,</span><span style="color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; white-space: pre;"> </span><span class="n" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">recipe</span><span class="p" style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; margin: 0px; padding: 0px; white-space: pre;">)</span><br />
<br />
The reason I convert it to a string and then back into an array is so that the user can later edit the ingredients via a textarea. It's fair to say that I actually write the multi_save code from a textarea for input before I did the screen scrape and I wanted to reuse it.<br />
<br />
The other interesting piece of this add_recipe method is that I store a new Site in case the user tries to add a recipe from an "uncatalogued" site. This automatically builds up a list of the sites people are interested in saving recipes from and allows me to catalogue it at a later date<br />
<br />
<br />
<pre style="border: 0px; color: #333333; font-family: Consolas, Menlo, 'Liberation Mono', Courier, monospace; font-size: 12px; line-height: 16px; padding: 0px;"><div class="line" id="LC14" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site_domain</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="no" style="border: 0px; color: teal; margin: 0px; padding: 0px;">URI</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">parse</span><span class="p" style="border: 0px; margin: 0px; padding: 0px;">(</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">url</span><span class="p" style="border: 0px; margin: 0px; padding: 0px;">)</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">host</span></div>
<div class="line" id="LC15" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="no" style="border: 0px; color: teal; margin: 0px; padding: 0px;">Site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">find_by_domain</span> <span class="n" style="border: 0px; margin: 0px; padding: 0px;">site_domain</span></div>
<div class="line" id="LC16" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="k" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">if</span> <span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">nil?</span></div>
<div class="line" id="LC17" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="no" style="border: 0px; color: teal; margin: 0px; padding: 0px;">Site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">new</span></div>
<div class="line" id="LC18" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">domain</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="n" style="border: 0px; margin: 0px; padding: 0px;">site_domain</span></div>
<div class="line" id="LC19" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">url</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="n" style="border: 0px; margin: 0px; padding: 0px;">url</span></div>
<div class="line" id="LC20" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">user</span> <span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">=</span> <span class="n" style="border: 0px; margin: 0px; padding: 0px;">current_user</span></div>
<div class="line" id="LC21" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="n" style="border: 0px; margin: 0px; padding: 0px;">site</span><span class="o" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">.</span><span class="n" style="border: 0px; margin: 0px; padding: 0px;">save!</span></div>
<div class="line" id="LC22" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="kp" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">false</span></div>
<div class="line" id="LC23" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="k" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;">else</span></div>
<div class="line" id="LC23" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="k" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;"> ... #Nokogiri scraping code goes here</span></div>
<div class="line" id="LC23" style="border: 0px; margin: 0px; padding: 0px 0px 0px 10px;">
<span class="k" style="border: 0px; font-weight: bold; margin: 0px; padding: 0px;"> end</span></div>
</pre>
<br />
<br /><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-33657535729707819162012-06-11T03:23:00.000-07:002012-06-11T03:23:36.319-07:00How DRY is too DRY?One of the earliest development principles we learn as programmers is Do not Repeat Yourself or DRY for short.<br />
<br />
Copy and Paste are supposedly your worst enemies. Rather than rely on copy and paste, you create functions and subroutines and call them from your code so you don't have to reimplement it continuously. It also has the added advantage that if you need to make a change to that subroutine, you only need to make that change once. (Note: I realize that functions and subroutines are different entities, but for the purpose of this article they are interchangeable).<br />
<br />
Sometimes you come across 2 pieces of functionality which are very similar, so instead of copying and pasting, you merely instantiate your function/subroutine with different variables to factor out/handle the differences, even where the core functionality is the same.<br />
<br />
For example, you might have a Customer object and a Vendor object. Both customers and vendors have addresses and you need to send mail to them both from time to time so you might decide to create a format_address function which they both can use, rather than copying and pasting the format_address code from one to the other. You might even move Address out to its own object which has a Customer or Vendor parent and put the format_address function on that.<br />
<br />
This becomes second nature after a while and is almost as rewarding as recycling. Hey, you are not wasting things by re-use right? An intermediate developer will try and re-use and keep things DRY as much as possible.<br />
<br />
However, there are times when you may not want to actually keep things DRY. The primary example is integration tests. When you are running integration tests, you might need to instantiate large numbers and different types of objects to simulate the system running as a whole. This code might look quite similar between tests so the temptation is to move it out into a subroutine and reuse it.<br />
<br />
This could be a mistake however. When hooking up test code in this manner, you are creating hidden dependencies in your code which can make things very difficult to change should the requirements change in only one area of your code. People might try and edit one object and find their tests failing for some unknown reason.<br />
<br />
The other time that DRY can work against you is if you try and use it between objects that are really conceptually different and unrelated. The example that most readily comes to mind is in something like CSS. Just because 2 objects may look similar (i.e. they both have the same color, rounded corners, and font) does not mean they are related and attempts to DRY up the CSS code too much means changing the design later can be difficult. The same goes for code that is more procedural rather than object oriented (i.e. a lot of front end interactive code where form elements interact with the user and each other depending on the user's actions). In those cases you must really use your judgement to decide whether or not to DRY it up (and there is a high probability you won't get it perfectly right either).<br />
<br />
So remember, while beginners Copy and Paste and intermediates DRY everything, an experienced programmer knows there may be a time for both.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-55543660445633925222012-06-04T21:10:00.000-07:002012-06-04T21:11:19.097-07:00We know JavaScript is weird... enough already!So it looks like JavaScript is close to being considered a "real" language nowadays. There are frameworks which allow you to do MVC (like Backbone.js), you can use it on the server (with Node.js) and you can even use it to interact with datastores (via MongoDB).<br />
<br />
So why is it that almost every job posting you see for a JavaScript gig and/or every interview you go to that has a JavaScript component, asks you to interpret/debug (without using a browser) some esoteric fault of JavaScript that you probably wouldn't run into in a 100 years because you actually write decent JS?<br />
<br />
Like<br />
<pre> var cities = ["NY", "SF"];
cities.length = 1;
console.log(cities); // outputs ["NY"]
</pre>
<pre></pre>
or
<br />
<pre> var a = 1 + 1 + "1"; // equals "21"
var b = "1" + 1 + 1; // equals "111"
</pre>
<pre></pre>
It's as if they are trying to say to you "Look at that piece of crap language you are programming in! You must be an idiot!" while at the same time offering you a job in said language whilst wanting to build up their systems in it.<br />
<br />
Also, there are so many of these quirks in JS (and web development in general) that just because you may not have seen one, it does not mean that you are a bad programmer and you don't know JavaScript.<br />
<br />
Instead, ask them to do FizzBuzz at a console with a text editor and a browser (whilst looking over their shoulder to see that they are not cheating) and really look at how well their code is written. Ask them to generate a recursive function. Ask them to create an instance of an object and add some functionality to it via prototype.<br />
<br />
In short, ask them to do something they do in real life and what you probably want them to do for you.<br />
<br />
Don't remind them how shitty JS is, believe me, they know this more than you.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-91866620417567523332012-05-31T03:05:00.000-07:002012-05-31T03:05:19.875-07:00HTML Canvas LibrariesSo I was remarking to a coworker today that the HTML Canvas API is very low level and hard to use, and his reaction to that was actually positive (and he had a point). By being very low level, it means that pretty much everything is exposed and going forward you won't have to wait for browser vendors to update their libraries in order to get the latest features. Basically, the browser vendors are removing themselves from the equation.<br />
<br />
However, application developers are left with a bit of a dilemma. Do we really want to reinvent the wheel every time we build an app? Why is it that you have to redraw everything every frame? Wouldn't it be easier to work with objects rather than pushing pixels?<br />
<br />
Well, while the browser vendors might have removed themselves from the library equation, fortunately a number of other people are stepping in. It looks like there are a myriad of 3rd party libraries out there now for manipulating the HTML 5 Canvas, some of which are more sophisticated than others.<br />
<br />
I haven't had time to delve into any of them for real yet, but from what I have seen so far, these seem to be the most advanced (as far as being Adobe Flash replacements).<br />
<br />
CreateJs - <a href="http://www.createjs.com/#!/CreateJS">http://www.createjs.com/#!/CreateJS</a><br />
This looks to be the most complete library out of all the ones I have seen with support not only for Canvas, but also tweening, sounds, and preloading. It also supports a stage object. EaselJs is the part that manipulates the canvas.<br />
<br />
oCanvas - <a href="http://ocanvas.org/">http://ocanvas.org/</a><br />
This also looks good, and the code samples I have seen look very similar to ActionScript. The "o" in oCanvas stands for Object.<br />
<br />
Paper.js - <a href="http://paperjs.org/">http://paperjs.org/</a><br />
This one also looks interesting. I need to look at it more. They also created a superset of JavaScript called PaperScript for handling objects more easily. Not sure if this is a good or bad thing.<br />
<br />
There are a myriad more as well. The only problem with having so many libraries out there is that you need to make sure you choose the right one when you start your project. I am not sure how inter-operable they all are and what would happen to you if you chose the wrong library.<br />
<br />
<br /><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-85455401005549443432012-05-12T00:01:00.000-07:002012-05-12T00:01:27.670-07:00Working on a side projectThere are many pluses when working on a side project. You get to work with the latest technology. You get to decide what features go in and what doesn't. You can show it off to potential employers. The list is endless. However there are some tips to remember as well. We will cover these here.<br />
<br />
<h3>
Time management</h3>
<div>
Unless you are unemployed, your time is now a precious resource, which means that you are now a resource to your own project. Try and organize blocks of time during the week when you can work (i.e. a few hours on Saturday or an hour on Thursday night) and stick to them. Try to break up your work into small chunks and aim to have a feature done in that block. This will help motivate you.</div>
<div>
<br /></div>
<h3>
Feature Management</h3>
<div>
Related to time management, it's important to maintain a list of what you want to do and be able to check items off this list. Ask friends for feature ideas and add them to the list. Save anything that's a large feature for the weekend and try and do the smaller features during the week.</div>
<div>
<br /></div>
<div>
Also while developing your site.app, try and maintain a list of every little thing that is bugging you. If it bugs you, it is sure to bug the user.</div>
<div>
<br /></div>
<div>
You also have to be judicious when you are not actually working on the site in figuring out which features really add value. Remember, your time is not limitless...</div>
<div>
<br /></div>
<h3>
Site/App Design</h3>
<div>
I am not a designer, but I know my way around CSS pretty well. I found Twitter Bootstrap to be a good framework to help me get started. There are tons of resources online as well. You probably won't get the design done in one go so it's important to remember that you will iterate on it repeatedly.</div>
<div>
<br /></div>
<h3>
Friends</h3>
<div>
Your friends are your best resource. Invite them early and get feedback ASAP. They will give you ideas for improvements and features and also help you determine which features to implement first (if everyone asks for the same feature, it must be important). At the end of the day though, you are in charge.</div>
<div>
<br /></div>
<h3>
Tests</h3>
<div>
It is tempting at first to leave out automated unit tests (because, hey, you are having fun right?) and that is fine for a little while, but don't let it drag on too long. Remember that you may be showing this side project to a potential employer, and you wouldn't hire someone that doesn't write tests would you?</div>
<div>
<br /></div>
<div>
That's all I can think of for now, but I will probably add to this as I continue working on my project. Happy coding!</div><div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-67843146682512683182012-05-10T04:26:00.003-07:002012-05-10T04:29:34.918-07:00For whom do you write code?Ok, this was going to be called "Who do you write code for?" but I was told never to end a sentence with a preposition.<br />
<br />
Anyways, I was thinking the other day about who the audience is for the code I write and I came up with the following. Bear in mind I live by the principles of KISS (Keep It Simple, Stupid) and YAGNI (You Ain't Gonna Need It). I developed these attitudes after reading tons of other people's code (as well as my own code 6-12 months down the line.<br />
<br />
So in descending order...<br />
<br />
<h3>
1. The User</h3>
Obviously you are writing code for someone to use (or a service to consume). I am not going to go into UX or HCI at this stage, just that as far as priority goes, this guy is the top. Make sure the user gets good and timely feedback for everything he does and that the steps he has to take make sense to him.<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
2. The Compiler/Interpreter</h3>
<div>
The code you write has to be compiled or interpreted by a computer before the user can use it. Don't worry too much about optimization at first (YAGNI, remember?) but don't write code that you know will perform badly off the bat. Try and stay away of O of N types of loops which could blow up in complexity, follow good OO techniques, and write tests where possible.</div>
<h3>
</h3>
<h3>
<br /></h3>
<h3>
3. Your coworkers (or you, 6+ months down the line)</h3>
<div>
Whenever I come up with a "clever" solution, I ask myself, "Will someone else understand this code without me explaining it to them?". Of course the caveat is that that someone else is also a coder and not a plumber, but I normally think of the most junior developer in the team (in terms of experience, not necessarily age) and whether or not he will comprehend it. Sometimes that person is me (ok, most of the time). The other thing I learnt is that code you wrote 6 months ago, is no longer yours. Someone else who looks a lot like you wrote it, but for all practical purposes it was not you.</div>
<h3>
</h3>
<h3>
<br /></h3>
<h3>
4. (and finally...) You</h3>
You are at the bottom of the list, but face it, you were really at the top all along. You write code because it's interesting, it's exciting, and it's fun. You like brain teasers and you like solving new and interesting problems and you never wanted to be an accountant (not that there is anything wrong with accountancy, you also get to work with number and you get paid more). So write code for yourself, but remember that you are also at the bottom of the list.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com0tag:blogger.com,1999:blog-6915823.post-50897312047910738772012-05-08T21:43:00.000-07:002012-05-08T21:43:48.948-07:00Speeding up RSpecSo today I have been looking into getting our enormous battery of tests to run faster. I have yet to find anything that works for Cucumber, but I did find an interesting way to speed up RSpec which is detailed here.<br />
<br />
<a href="https://makandracards.com/makandra/950-speed-up-rspec-by-deferring-garbage-collection">https://makandracards.com/makandra/950-speed-up-rspec-by-deferring-garbage-collection</a><br />
<br />
Basically, it seems that by not collecting garbage too frequently, you can make your tests run much faster (at the expense of memory management of course). We observed a 30% reduction in the time it takes to run an RSpec test suite.<br />
<br />
I did try to implement this on Cucumber, however because we need to store much more in memory to set up and tear down our objects, it meant that I kept running out of memory when I wasn't using the default Garbage Collection and the tests took even longer (so, buyer beware). I suppose if you had a small set of features though you might see some benefit.<div class="blogger-post-footer">http://www.skuunk.com</div>skuunkhttp://www.blogger.com/profile/10041594734097301869noreply@blogger.com2