Technology Musings

March 03, 2014

General / New Book Integrating Engineering, Philosophy, and Theology

JB

I thought some of you, my faithful blog readers, might be interested to know about a project I just completed with some other people - a new book about integrating engineering, philosophy, and theology.  It is titled Engineering and the Ultimate: An Interdisciplinary Investigation of Order and Design in Nature and Craft.

Engineering and the Ultimate book cover

February 12, 2014

Snippets / Creating a WSDL file for a RESTful Web Service

JB

NOTE - this is about XML-based REST services.  There *might* be a way to write WSDL for JSON, but I don't know it yet.

Most of my work is done in RESTful environments.  However, the Microsoft world seems to favor SOAP.  REST is simple.  It is so simple, that you don't even really need special APIs for the different services.  You just need the URL and the documentation.  However, SOAP is complex, and requires complex tooling. SOAP services are described using the WSDL format.  Usually what happens is that a program reads the WSDL file and spits out stubs that you can code against directly.

In any case, the people that come from a SOAP environment don't realize that you can do cool stuff without tooling, and the idea that I could just give them a URL to hit and they could just hit it directly boggles their mind.  So, if I have a handy-dandy REST service, they still want to ask me for the WSDL file.  There's really no reason, but that's what they want.  So, thankfully, WSDL 2 allows for us to create WSDL descriptions of RESTful services.

So, before I describe how WSDL works, let me first say that, if you are used to the simplicity of REST coding, WSDL will blow your mind.  WSDL is built around the complexities of SOAP.  There are a lot of things where you will say, "why did they put in that extra layer?"  The answer is, "because the SOAP protocol was built by crack addicts."

WSDL is grouped into 4 parts - services, bindings, interfaces, and types.

An interface is a list of operations you might want to perform.  This is, essentially, the API itself.  Our API will be for retrieving form submissions to our website.  The interface is a list of operations, where each operation is an individual API call.  In our example, we will have a "list" and a "get" operation in our inteface.  The interface relies on types in order to define the input and output of the operations.

A binding tells you how to make an API call for an operation.  This is significantly simpler in REST than in SOAP, as you really only need the URL path and the method (this is the relative URL - the absolute path will be defined in the service).  The binding will have a operation tag for each operation it binds.

A service defines where on the web you can go to in order to find the interfaces.  It defines a set of endpoints, each of which tells the top-level URL of the resources, like http://www.example.com/api, and what interfaces they support and which bindings to use on them (telling both the interface and the binding seems redundant to me, but then again so does writing a WSDL file to begin with).

So, below, is a simple REST web service.  I've excluded the "types" section, which I will discuss later.

<?xml version="1.0" ?>
<wsdl2:description
xmlns:wsdl2="http://www.w3.org/ns/wsdl"
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:whttp="http://www.w3.org/ns/wsdl/http"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:myexample="http://www.example.com/api"
targetNamespace="http://www.example.com/api"
<wsdl2:types>
<xs:schema
attributeFormDefault="unqualified"
elementFormDefault="unqualified">
<!-- omitted -->
</xs:schema>
</wsdl2:types>

<wsdl2:interface name="FormSubmissionsInterface">
<wsdl2:operation
name="listFormSubmissionsOperation"
pattern="http://www.w3.org/ns/wsdl/in-out"
whttp:method="GET">
<wsdl2:input element="formSubmissionSearchParameters" />
<wsdl2:output element="response" />
</wsdl2:operation>
<wsdl2:operation
name="getFormSubmissionOperation"
pattern="http://www.w3.org/ns/wsdl/in-out"
whttp:method="GET">
<wsdl2:input element="formSubmissionIdentifier" />
<wsdl2:output element="response" />
</wsdl2:operation>
</wsdl2:interface>

<wsdl2:binding name="FormSubmissionBinding"
interface="myexample:FormSubmissionBinding"
type="http://www.w3.org/ns/wsdl/http">
<wsdl2:operation
ref="myexample:getFormSubmissionOperation"
whttp:method="GET"
whttp:location="form_submissions/{id}.xml"
/>
<wsdl2:operation
ref="myexample:listFormSubmissionsOperation"
whttp:method="GET"
whttp:location="form_submissions.xml"
/>
</wsdl2:binding>

<wsdl2:service
name="FormSubmissionsServiceSSL"
interface="myexample:FormSubmissionsInterface">
<wsdl2:endpoint
name="FormSubmissionEndpoint"
binding="myexample:FormSubmissionBinding"
address="https://www.example.com/api"
/>
</wsdl2:service>
<wsdl2:service
name="FormSubmissionsService"
interface="myexample:FormSubmissionsInterface">
<wsdl2:endpoint
name="FormSubmissionEndpoint"
binding="myexample:FormSubmissionBinding"
address="http://www.example.com/api"
/>
</wsdl2:service>
</wsdl2:description>

So, starting at the bottom, we have 2 services defined - an HTTP service and an HTTPS service.  They both use the same binding and interface.  So, they are called exactly the same way, buy have a different base URL.  Depending on your client, the service name often corresponds to the main class name used to access the service.

The services implement the myexample:FormSubmissionsInterface.  This interface defines two operations: listFormSubmissionsOperation and getFormSubmissionsOperation.  Each one defines its input and output parameters.  Now, the output parameter is just a regular XML element from your schema (defined in the "types" section).  The input parameter is special.  It, too, is defined in your types section, but, when bound to an HTTP endpoint, rather than being treated as an XML document, it will be treated as a combination of URL pieces and query string parameters.  Here is mine:

                       <xs:element name="formSubmissionSearchParameters">
<xs:complexType>
<xs:all>
<xs:element name="since" type="xs:dateTime" minOccurs="0" />
<xs:element name="since_id" type="xs:integer" minOccurs="0" />
<xs:element name="until" type="xs:dateTime" minOccurs="0" />
<xs:element name="until_id" type="xs:integer" minOccurs="0" />
</xs:all>
</xs:complexType>
</xs:element>

Each of the elements under xs:all actually become query-string parameters.  WSDL treats it like an XML document, because of its SOAP roots.  But, when we specify an HTTP binding, then this gets transmogrified from an XML document into query-string parameters.  I have a separate type for the "get" operation, which defines the ID number of the form submission I want:

			<xs:element name="formSubmissionIdentifier">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:integer" />
</xs:sequence>
</xs:complexType>
</xs:element>

As we will see, in the bindings section, we can move a named parameter into the URL itself, rather than just being sent as a query string.

The output is simply an XML document, whose structure is defined in the types section.  I would need to teach you XSD documents to tell you how this works, but you can google it yourself.  The one thing I will say is that because my result document does not use namespaces at all, in the xs:schema declaration I set elementFormDefault to "unqualified".

The binding is where we tell WSDL that we are using REST rather than SOAP.  We tell it what interface we are binding, and the fact that we are using an HTTP binding.  Then, for each operation, we define the HTTP method and relative path for the binding.  We can substitute in our parameters into the URL path by saying {name-of-xml-input-element} in the location.  Any parameters given which are not inserted into the URL path will be put in the query string.  So, for instance

whttp:location="form_submissions/{id}.xml"

Says to take the "id" element and substitute it in the URL.  The other location,

whttp:location="form_submissions.xml"

just puts all of the parameters in the query string.

So that's it!  That's a WSDL file for a REST service!

I tested my service and WSDL using AXIS 2's WSDL2JAVA command:

wsdl2java.sh -uri path/to/wsdl -wv 2

Then I created a simple TestMe.java to make sure it ran right.  The example also shows how to use HTTP Basic Authentication with the service:

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.math.*;

import org.apache.axis2.databinding.ADBBean;
import org.apache.axis2.Constants;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlValidationError;
import org.apache.axis2.client.*;


import com.example.www.FormSubmissionsServiceStub;
import com.example.www.FormSubmissionsServiceStub.*;

import org.apache.axis2.transport.http.*;

public class TestMe {
public static void main(String[] args) throws RemoteException {
/* Setup Service */
FormSubmissionsServiceStub stub = new FormSubmissionsServiceStub();

/* If you need authentication */
HttpTransportProperties.Authenticator basicAuth = new HttpTransportProperties.Authenticator();
basicAuth.setUsername("USERNAMEHERE");
basicAuth.setPassword("PASSHERE");
basicAuth.setPreemptiveAuthentication(true);
final Options clientOptions = stub._getServiceClient().getOptions();
clientOptions.setProperty(HTTPConstants.AUTHENTICATE, basicAuth);

/* Setup Single Get */
BigInteger subm_id = BigInteger.valueOf(500);
FormSubmissionIdentifier myid = new FormSubmissionIdentifier();
myid.setId(subm_id);
System.out.println(TestMe.debugAxisObject(myid));

/* Run Single Get */
Response r = stub.getFormSubmissionOperation(myid);

/* Setup Multi Get */
FormSubmissionSearchParameters p = new FormSubmissionSearchParameters();
p.setSince_id(BigInteger.valueOf(3200));
System.out.println(TestMe.debugAxisObject(p));

/* Run Multi Get */
Response r2 = stub.listFormSubmissionsOperation(p);
}
}

Note that "Response" is not a generic Java class, it is actually com.example.www.FormSubmissionsServiceStub.Response, because the name of the output element is "response".

Other Resources:

October 30, 2013

Snippets / Getting my Inspiron 8000 to run Ubuntu 13.10

JB

This is for my own benefit if I need to reinstall again.

Links to helpful articles:

What I remember doing:
  1. I installed using a text-mode install from the network installer
  2. I booted into recovery mode root prompt (don't forget to remount / readwrite!)
  3. I modified /etc/default/grub to add "nolapic nomodeset" to the default parameter
  4. I made s imple xorg.conf file to say that Card0 should use vesa
Then everything seemed to work.  It shows it in a small box, though, and that is better than nothing.

July 31, 2013

Platforms / Uniqueness Methods for PostgreSQL

JB

Just finished packaging together a set of uniqueness functions for PostgreSQL, mostly focused on Cantor pairing.  Here is the introduction from the documentation:

uniqueness-pg

A collection of functions such as cantor pairing which help make unique identifiers for PostgreSQL.

What is a Cantor Pair? Let's say that you have a table which is uniquely identified by two numeric foreign keys. However, let's say that you need a single, unique key to refer to the row externally. Probably a better approach is to create a new primary key, but, if you can't, you can use a Cantor Pairing function to create a single number from the two.

select cantorPair(23, 50);
-- yields 2751
select cantorUnpair(2751);
-- yields {23,50};

Remember, in Postgres, you can even create indexes on these functions.

create table cantortest(a int, b int, c text, primary key(a, b)); create index on cantortest(cantorPair(a, b)); insert into cantortest values (1, 5, 'hello'); insert into cantortest values (3, 18, 'there');

select cantorPair(3, 18); -- =>  (yields 249)

select * from cantortest where cantorPair(a, b) = 249; -- uses index on large enough tables, or if enable_seqscan set to no.

select * from cantortest where cantorUnpair(249)[1] = 3; -- parses 
number out into a foreign key that can be looked up on its own

You can use more than two values with an array and the cantorTuple function:

select cantorTuple(ARRAY[5, 16, 9, 25]);
-- yields 20643596022
select cantorUntuple(20643596022, 4); -- NOTE - the 4 tells how many components
-- yields {5,16,9,25}

Additional functions include:

cantorPairBI - the regular cantorPair uses DECIMALs, which can be slow. If you know the final value will fit in a BIGINT, this version should be a little bit faster.

cantorUnpairBI - same as above but for unpairing

uniquenessSpace(key, spaces, space_index) - this is useful for when you need to UNION tables which each have their own PKs, but you want all of the rows to also have their own PKs. It is a super-simple function (key * spaces + space_index), but it is helpful because people can better see what/why you are doing things. So, for instance, if we had an employees tables and a customers table, but wanted a people view that had a unique PK, we can do:

CREATE VIEW people AS 
     SELECT uniquenessSpace(employee_id, 2, 0), employee_name from employees
     UNION ALL
     SELECT uniquenessSpace(customer_id, 2, 1), customer_name from customers;

To retrieve the original ID, use uniquenessUnspace:

select * from employees where employee_id = uniquenessUnspace(person_id, 2, 0); -- use same arguments as used on uniquenessSpace

These are both horrid names, and I am open to renaming them.

I am currently working on another uniquenessSpace, which doesn't require the number of tables to be known, only the index. However, this function is problematic because it relies on exponentials. The current version doesn't work because I need a prime filter. However, the goal is to overcome the problem in the previous example that if the number of tables change (i.e. we add on an additional supplier table or something), then all of the generated keys have to change (because there are more tables, which affects the second parameter). In an ideal setting, you will be able to do:

CREATE VIEW people AS 
     SELECT uniquenessSpace(employee_id, 0), employee_name from employees
     UNION ALL
     SELECT uniquenessSpace(customer_id, 1), customer_name from customers;

And then add on additional tables as necessary, without ever having to worry about changing keys. This can work by generating a key which is p0^employee_id, and p1^customer_id, where p0 is the first prime (2) and p1 is the second prime (3). As you can see, this gets out of hand pretty quickly, but PostgreSQL is surprisingly accomodative to this. Anyway, this is probably a generally bad idea, but I thought it should be included for completeness. I should probably rename this function as well.

June 27, 2013

General / Knowing When to Start Over

JB

A friend of mine shared this following quip on FB:

Quinn's design tip o' the day: You can't iterate your way out of local maxima. In other words, no amount of horse breeding will produce a Model T.


Sooner or later, you just have to throw it all away and start over.

May 29, 2013

Snippets / Adding a "Copy" context menu to a disabled UITextfield in iOS

JB

I have a project which has a lot of fields which, at any moment, may be enabled or disabled.  However, even when they are disabled, I want to show a context menu.  It turns out that no one has a solution, so I hacked one together for myself.  This code is adapted from a working project, but I have not tested the version presented here, but let me know if you have problems.  

The general problem you run into is this: if your UITextField is disabled, then it cannot receive touch events.  Therefore, it cannot fire the menu, even if you try to do so explicitly using Gesture recognizers.  The normal answer is to simply not allow any editing on the field.  However, while this prevents you from editing the field, it still *draws* the textfield as active.  We want it to *look* inactive as well, which you only get by disabling it.

So, what I did was to create a subclass of UITextField (JBTextField) which has a subview (a specialized subclass of UIView - JBTextFieldResponderView) that has a gesture recognizer attached.  We override hitTest on JBTextField to do this: if JBTextField is disabled, then we delegate hitTest to our JBTextFieldResponderView subclass.

As I said, this is from another project, which had a whole bunch of other stuff mixed in.  I have tried to separate out the code for you here, but I don't guarantee that it will work out-of-the-box.

@interface JBTextFieldResponderView : UIView
@property (strong, nonatomic) UIGestureRecognizer *contextGestureRecognizer;
@end

@interface JBTextField : UITextField
@property (strong, nonatomic) JBTextFieldResponderView *contextGestureRecognizerViewForDisabled;
@end
@implementation JBTextFieldResponderView
@synthesize contextGestureRecognizer = _contextGestureRecognizer;

-(id) initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];

  // Add the gesture recognizer
  self.contextGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureDidFire:)];
  [self addGestureRecognizer:self.contextGestureRecognizer];

  return self;
}

// First-level of response, filters out some noise
-(IBAction) longPressGestureDidFire:(id)sender {
  UILongPressGestureRecognizer *recognizer = sender;
  if(recognizer.state == UIGestureRecognizerStateBegan) { // Only fire once
    [self initiateContextMenu:sender];
  }
}

// Second level of response - actually trigger the menu
-(IBAction) initiateContextMenu:(id)sender {
  [self becomeFirstResponder]; // So the menu will be active.  We can't set the Text field to be first responder -- doesn't work if it is disabled
  UIMenuController *menu = [UIMenuController sharedMenuController];
  [menu setTarget:self.bounds inView:self];
  [menu setMenuVisible:YES animated:YES];
}

// The menu will automatically add a "Copy" command if it sees a "copy:" method.  
// See UIResponderStandardEditActions to see what other commands we can add through methods.
-(IBAction) copy:(id)sender {
  UIPasteboard *pb = [UIPasteboard generalPasteboard];
  UITextField *tf = (UITextField *)self.superview;
  [pb setString: tf.text];
}

// Normally a UIView doesn't want to become a first responder.  This forces the issue.
-(BOOL) canBecomeFirstResponder {
  return YES;
}

@end

@implementation JBTextField
@synthesize contextGestureRecognizerViewForDisabled = _contextGestureRecognizerViewForDisabled;

/* Add the recognizer view no matter which path this is initialized through */

-(id) initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  [self addDisabledRecognizer];
  return self;
}

-(id) initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  [self addDisabledRecognizer];
  return self;
}

// This creates the view and adds it at the end of the view chain so it doesn't interfere, but is still present */
-(void) addDisabledRecognizer {
  self.contextGestureViewForDisabled = [[JBTextFieldResponderView alloc] initWithFrame:self.bounds];
  self.contextGestureViewForDisabled.userInteractionEnabled = NO;
  [self addSubview:self.contextGestureViewForDisabled];
}

-(void) setEnabled:(BOOL) enabled {
  [super setEnabled:enabled];
  self.contextGestureViewForDisabled.userInteractionEnabled = !enabled;
}

// This is where the magic happens
-(UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  if(self.enabled) {
    // If we are enabled, respond normally
    return [super hitTest:point withEvent:event];
  } else {
    // If we are disabled, let our specialized view determine how to respond
    UIView *v = [self.contextGestureViewForDisabled hitTest:point withEvent:event];
  }
}

@end

Now, in interface builder, we can enabled copy on disabled fields just by setting the class to JBTextField, or, if built programmatically, by replacing [UITextField alloc] with [JBTextField alloc].

I hope this helps!

Additional Resources:

 

 

 

May 09, 2013

Platforms / My Beef with Model Validators in Rails

JB

When I first started using Rails, it was a dream come true.  I estimate that I reduced the amount of code I was writing by 60-75%.  This is probably still true.  However, as I've gotten to know the platform better, there are some philosophical issues that I have with Rails.  Sometimes it is more the Rails community and its conventions rather than rails itself, but nonetheless, it continually manifests itself.

My current beef - validators!  Having written lots and lots of code that interacts with lots and lots of systems, I have to say that you should never, ever, ever put in model validators.  Ever.  Validation is a process issue, not a data one.  In theory it should be a data issue, but it never ever works out that way.

Here's what happens.  Let's say I have a telephone number validator for a model.  It makes sure the numbers are in the format (918) 245-2234.  If not, it gives a nice error message.  However, now let's connect that same model to an external system.  Let's now say that the author of the external system was not as picky about phone number formats.  What happens now?  Well, every record that gets migrated from that system that isn't formatted according to your desires will now get kicked out!  And it might not even show up during testing!  In addition, your pretty error messages mean practically nothing on system->system communication.  So, what you have is the system randomly breaking down.

This causes even more problems in long-term maintenance.  I've had many bugs pop up because someone added a validator that broke several processes.  It worked in all of their tests, but they weren't thinking about all of the ways the system was used.  For instance, we decided to lock down the characters used in our usernames a little more.  The programmer decided to implement it as a validator.  Well, the problem is that several users already had usernames that didn't match the validator.  So what happened?  Those records just stopped saving.  Not just when there was someone who was at the terminal, where they could see the error message, but every job started erroring out because of users who had faulty names.

Because of these issues, I *always* do validation in my controller, and *never* in the model.  It is true that there are probably workarounds for all of these issues, but the point is that they are *not* that exceptional!  They are everywhere!  What would be nice is to have validators which could be run optionally, in specific situations.  So, a controller could declare that it was using :data_entry validations, and then the model would only run those validators.  So, it would look something like this:

In the model:

validates_presence_of :email, :for => [:data_entry]

In the controller:

uses_validators :data_entry

One of the issues I have found with dynamic languages such as ruby is that errors arise when "magic" is sprinkled in unexpected places.  Ruby magic is awesome, but when I do something simple and it won't save, it is sometimes hard to even locate where the error is.  Explicitly declaring which validator set you are using allows programmers to more easily see what they are doing, and only apply magic code where it goes.

Someday I'll write a package like this --- when I get extra time :)

May 07, 2013

Platforms /

Why the tabs-vs-spaces debate matters:

Inconsistent indentation: 2 spaces were used for indentation, but the rest of the document was indented using 1 tab.

Everyone - indent with tabs.  Then you can control the indentation using your editor's settings.  DO NOT indent with spaces.  And DO NOT use any programming language or configuration system that cares about indentation.

October 07, 2012

Amusing / A Review of Several Nerf and Nerf-Like Guns

JB

Usually I don't splurge on things like Nurf guns, but our family was challenged to a dual, and I decided we needed to load up.  The kids had been saving money, so they all got to purchase their own gun, and so my only purchase was for my own.  Anyway, I thought I'd post what everyone bought and a short review of each of them.  Here's what we bought:

Below is a picture of all of the guns:

We bought these at Target, and none of them cost more than $25.  In fact, most of them were $10 or less.

The gun with the most "cool factor" was the Nerf Retaliator.  This gun is modular and therefore can actually be transformed into four different styles below:

If you remove the stock and the barrel, you get a very nice basic gun similar to a Skorpion (bottom right).  If you have a stock without a barrel, you get a gun very reminiscent of a UMP45.  If you have a barrel but no stock, it looks a bit like an MP5.  With all of the attachments, it could be really any assault rifle, but it most reminds me of an M4 or FAL.  Now, I should point out, this is pretty much all for show - I didn't detect any advantage of having a barrel attached - but it looks pretty intimidating nonetheless.  

The Retaliator fires well, and with a long range , but is *very* erratic.  In our battle with our friends, I'm not sure I hit *anyone* with this gun.  It is mega-cool, and shoots pretty far, but don't count on actually hitting anything.

The gun that actually shot the best was the Vortex Proton - and I think it only cost $9.  The Proton is one of Nerf's new disc-firing guns.  The disks are Nerf disks so they don't hurt.  However, the disks are *very* aerodynamic, so they actually fly straight!  The Vortex is a single shot, and it takes a little bit to load, but nonetheless I had the best success with it because it was so accurate!  I could be sure to know that the disk would fly exactly to where I aimed it!  The only drawback, as I said, was that it was a single-shot, and didn't even have a spot to hold additional ammo.  So, reloading was problematic because it took time, and fumbling around with ammo was difficult.  I think if you got a multi-shot version of this you would have an amazing gun!  

Another good gun is the Buzz Bee RADS 12.  This is a twelve-shot gun that you pull back on a lever to cock it.  It shoots decently and doesn't cost very much.  Overall I would say that it is a decent purchase, but my 5-year-old was the one to purchase it, and it took too much hand strength to cock the gun.  He could do it, but only if he held it on the ground with one hand and pulled with the other - not very helpful in a gun battle.

The worst gun was the Buzz Bee double-barrel shotgun.  This was not worth buying.  Here are the problems with the gun:

 

  • It takes a *lot* of effor to load the gun.  You have to pull it apart really hard, and it is not always clear when it is fully cocked (pulling it apart is what cocks it)
  • The bullets have to be loaded into "shells" (to make it more realistic).  This is just a hassle.  My son likes this, but it is unhelpful.
  • The shells eject when you pull apart the gun.  Again, this aims for realism, but doesn't help during a battle, and is just more pieces to get lost.  In fact, in this case, they actually seem to be actively trying to get lost.
  • It only holds two shots.  This seems pretty obvious for a double-barrel shotgun, but the fact that it takes so long to load makes this really problematic.
  • It doesn't shoot very far.  It's moderately accurate, but, really, it has an effective range of about 6-12 feet.

Anyway, my 6-year-old likes it because of the "realism", but I think that if we do too many more battles, he will realize the problems with the gun.

The next gun is the Buzz Bee Tek-3.  This is a tiny gun with three shots.  However, it does not auto-rotate the barrel.  Between shots, you must BOTH cock the gun AND rotate the barrel.  However, for a tiny gun, it packs a decent punch and is relatively easy to use.  At Target, they had a 3-pack for $6.  That's right, 3 guns for $6.  It makes a good backup weapon (like a boot gun).

Also in the picture is an older Nerf gun that we already had - I believe it is a Nerf Dart Tag Strikefire.  This is an old gun, but still shoots decently.  It is a single-shot, muzzle-loaded pistol, but also has holding spots for five additional rounds.  This original came with Nerf Dart Tag darts, but I think it can fire most anything.  It works really well as a simple, basic gun.

Anyway, if you are looking for Nurf or Nurf-like guns, I hope this review was helpful to you!

 

October 01, 2012

Platforms / Rails, CoffeeScript, and Java

JB

I *hate* the direction that Rails is moving.  It is becoming a bloated piece of garbage.  I got into Rails precisely because Rails got out of my way most of the time, but was easy to use right where I needed it.  ActiveRecord was a thin shell over SQL.  Therefore, if there was a problem, I could easily diagnose it, because I knew SQL.  It added a few bits of magic, but those were usually pretty understandable.  Likewise with the javascript helpers and the like.  If you needed something done, just pop into Javascript and patch it up.

This contrasted heavily with J2EE.  I used J2EE for a while, but it turns out that if you want to use J2EE you have to commit to it like it's a religion.  If you don't know 20 different platform libraries, you can't even do a hello world in J2EE.  When you finally get it working, you don't know what the heck is even going on.  Some magic is happening somewhere, but it is beyond what is plainly visible.  This, and the fact that Ruby is very metaprogramming-ish, was the reason I chose Rails.

However, starting with Rails 3, the Rails team decided that it was simply going to do everything for you, and everything was going to be done in domain-specific languages.  Thus, instead of the required list of languages/platforms being Ruby/Rails/SQL/Javascript, it has balooned out to Ruby/Rails/SQL/Javascript/Coffeescript/SASS/ECO/Node.js, and probably a few others I'm forgetting.  The asset pipeline is kind of cool, but it's yet another place for things to go wrong.  I think the anti-CSS measure of auto-h()ing code is terrible.  If you wanted to go that way, instead you should use Perl's taint feature, and just give errors if you display tainted values.  The idea that random code should transform my strings is horrible.

Finally, I understand the reason for getting rid of the javascripty stuff from rails core, but some of those were really what made the platform really rock.  I think they have missed the mark - they put the ease-of-use in the wrong places, and instead have made sure that no one that doesn't have a relgious commitment to the software stack will ever find it usable.  It has basically gone the way of J2EE, and I am now looking for a new platform to love.