massive

Massive assignment for haxe.


Keywords
assignment, massive, web
License
MIT
Install
haxelib install massive 1.0.2

Documentation

Massive.hx - Mass(ive) assignment for Haxe

Motivation

A lot of modern web frameworks - such as Ruby on Rails1 and Yii22 provide a feature called Mass assignment. It's a convenience feature which maps out an assignment of attributes from one model to another, 'populating' it for a certain scenario.

I dislike writing a bunch of assignment statements consecutively. It feels like a bit of a code smell and I want it to go away. This is more for personal use than anything else.

Solution

A build and initialization macro with some syntax sugar to make your code puurrttyyy~.

Caution!
Using Massive assignment in a improper manner can lead to security vulnerabilities.3 4 5

Usage

Given a model ExampleModel;

class ExampleModel{

  public var propertyA:Int;
  public var propertyB:Int;
  public var propertyC:Int;
  private var propertyD:Int;

  public function new(a:Int,b:Int,c:Int,d:Int){
    this.propertyA = a;
    this.propertyB = b;
    this.propertyC = c;
    this.propertyD = d;
  }

}

and two instances of the model;

var oldModel = new ExampleModel(1, 2, 3, 4);
var newModel = new ExampleModel(3, 4, 10, 55);

Selective Assignment

We can selectively copy attributes from newModel to oldModel;

function example(){
  @:mass(propertyA, propertyB) oldModel = newModel;
}

Which will map out to the following at compile time;

function example(){
  oldModel.propertyA = newModel.propertyA;
  oldModel.propertyB = newModel.propertyB;
}

Absolute Assignment

We can also copy all public writable attributes from newModel to oldModel that oldModel can hold;

@:mass function example(){
  @:mass oldModel = newModel;
}

Which will map out to the following at compile time;

function example(){
  oldModel.propertyA = newModel.propertyA;
  oldModel.propertyB = newModel.propertyB;
  oldModel.propertyC = newModel.propertyC;
}

Scenarios

Scenarios are an addition to massive assignment, traditionally to work with ActiveRecord instances or instances of a model from the database. They are an attempt to simultaneously 'raise' the abstraction level by defining the whitelisted attributes in the class model, so you can reuse the whitelists across multiple @:mass assignments, and to coerce the programmer to explicitly define what attributes are 'safe' in a certain context.

The scenario implementation assumes that your ExampleModel has a function public inline function scenarios():Map<String,Array<String>>. Inlining isn't mandatory but there's no reason not to.

class ExampleModel{
  ...
  public inline function scenarios() return [
    "exampleScenario" => ["propertyB", "propertyC"]
  ];
  ...
}

Then, when you want to assign to a ExampleModel instance in a constrained monomorph, ie an explicit ExampleModel instance, use a @:scenario("scenario") in a parent node of the AST, ie a function;

@:scenario("exampleScenario") function closure(){
  @:mass oldModel = newModel;
}

Which will then map to (roughly) the following code using Reflection;

function closure(){
  for(p in ["propertyB", "propertyC"]){
    var val = Reflect.getProperty(newModel, p);
    Reflect.setProperty, oldModel, p, val);
  }
}

Credits

A big thankyou to back2dos for the tink libraries - they are extremely useful and this language extension was based off of the tink_await library.

TODO

  • Check at compiletime if object B has all props from object A?
  • Only update props from B that intersect with A, warn otherwise
  • Allow assignment to anonymous structures
  • Allow assignment from anonymous structures
  • public readable check for getting vals from B