DSL in scala using case classes

I really don't think you need the builder pattern in Scala. Just give your case class reasonable defaults and use the copy method.

i.e.:

employee.copy(address = Address("abc","12222"), 
              department = Department("HR"))

You could also use an immutable builder:

case class EmployeeBuilder(address:Address = Address("", ""),department:Department = Department("")) {
  def build = emp(address, department)
  def withAddress(address: Address) = copy(address = address)
  def withDepartment(department: Department) = copy(department = department)
}

object EmployeeBuilder {
  def withAddress(address: Address) = EmployeeBuilder().copy(address = address)
  def withDepartment(department: Department) = EmployeeBuilder().copy(department = department)
}

I created a DSL using reflection so that we don't need to add every field to it.

Disclamer: This DSL is extremely weakly typed and I did it just for fun. I don't really think this is a good approach in Scala.

scala> create an Employee where "homeAddress" is Address("a", "b") and "department" is Department("c") and that_s it
res0: Employee = Employee(a=b,null,c)

scala> create an Employee where "workAddress" is Address("w", "x") and "homeAddress" is Address("y", "z") and that_s it
res1: Employee = Employee(y=z,w=x,null)

scala> create a Customer where "address" is Address("a", "b") and "age" is 900 and that_s it
res0: Customer = Customer(a=b,900)

The last example is the equivalent of writing:

create.a(Customer).where("address").is(Address("a", "b")).and("age").is(900).and(that_s).it

A way of writing DSLs in Scala and avoid parentheses and the dot is by following this pattern:

object.method(parameter).method(parameter)...

Here is the source:

// DSL

object create {
  def an(t: Employee.type) = new ModelDSL(Employee(null, null, null))
  def a(t: Customer.type) = new ModelDSL(Customer(null, 0))
}

object that_s

class ModelDSL[T](model: T) {
  def where(field: String): ValueDSL[ModelDSL2[T], Any] = new ValueDSL(value => {
    val f = model.getClass.getDeclaredField(field)
    f.setAccessible(true)
    f.set(model, value)
    new ModelDSL2[T](model)
  })

  def and(t: that_s.type) = new { def it = model }
}

class ModelDSL2[T](model: T) {
  def and(field: String) = new ModelDSL(model).where(field)

  def and(t: that_s.type) = new { def it = model }
}

class ValueDSL[T, V](callback: V => T) {
  def is(value: V): T = callback(value)
}

// Models

case class Employee(homeAddress: Address, workAddress: Address, department: Department)

case class Customer(address: Address, age: Int)

case class Address(name: String, pincode: String) {
  override def toString = name + "=" + pincode
}

case class Department(name: String) {
  override def toString = name
}