README.md

# GenObject

A library for creating stateful objects backed by GenServer processes with inheritance support.

GenObject provides a macro-based DSL for defining object-like structures that maintain state in GenServer processes. Objects support field access, updates, lazy operations, and merging. The library integrates seamlessly with the [Inherit](https://github.com/DockYard/inherit) library to provide powerful inheritance modeling capabilities.

## Features

- **Stateful Objects**: Objects backed by GenServer processes with automatic lifecycle management
- **Field Operations**: Get, put, and merge operations with both synchronous and asynchronous variants
- **Lazy Operations**: Functions that compute values based on current object state
- **Inheritance Support**: Integration with the Inherit library for object inheritance patterns
- **Process Safety**: All operations are process-safe through GenServer messaging
- **Performance**: Asynchronous variants for high-performance scenarios

## Installation

Add `gen_object` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:gen_object, "~> 0.1.0"},
    {:inherit, "~> 0.1.0"}  # For inheritance features
  ]
end
```

## Quick Start

### Basic Object Definition

```elixir
defmodule Person do
  use GenObject, [
    name: "",
    age: nil,
    email: nil
  ]
end

# Create a new person object
person = Person.new(name: "Alice", age: 30)
# Returns: %Person{name: "Alice", age: 30, email: nil, pid: #PID<...>}
```

### Field Access

```elixir
# Get the complete object
current_person = Person.get(person)
# Returns: %Person{name: "Alice", age: 30, email: nil, pid: #PID<...>}

# Get a specific field (more efficient)
name = Person.get(person, :name)
# Returns: "Alice"

# Can also use PID directly
age = Person.get(person.pid, :age)
# Returns: 30
```

### Field Updates

```elixir
# Synchronous update (returns updated object)
person = Person.put(person, :age, 31)
person = Person.put(person.pid, :email, "alice@example.com")

# Asynchronous update (returns :ok immediately)
:ok = Person.put!(person, :age, 32)
:ok = Person.put!(person.pid, :name, "Alice Smith")

# Verify async updates
updated_person = Person.get(person)
```

### Lazy Updates

Lazy updates allow you to compute new values based on the current object state:

```elixir
# Increment age based on current value
person = Person.put_lazy(person, :age, fn p -> p.age + 1 end)

# Create display name from existing fields
person = Person.put_lazy(person, :display_name, fn p ->
  "#{p.name} (#{p.age})"
end)

# Asynchronous lazy update
:ok = Person.put_lazy!(person, :age, fn p -> p.age + 1 end)
```

### Merging Multiple Fields

```elixir
# Synchronous merge
person = Person.merge(person, %{
  name: "Alice Johnson",
  age: 35,
  email: "alice.johnson@example.com",
  location: "San Francisco"
})

# Asynchronous merge
:ok = Person.merge!(person, %{age: 36, location: "New York"})

# Lazy merge based on current state
person = Person.merge_lazy(person, fn p ->
  age_group = if p.age < 18, do: "minor", else: "adult"
  %{
    age_group: age_group,
    can_vote: p.age >= 18,
    display_name: "#{p.name} (#{age_group})"
  }
end)
```

## Inheritance with the Inherit Library

GenObject uses the [Inherit](https://github.com/DockYard/inherit) library to provide powerful inheritance modeling:

### Basic Inheritance

```elixir
defmodule Animal do
  use GenObject, [
    name: "",
    species: "",
    age: 0
  ]
  
  def speak(%__MODULE__{} = animal) do
    "#{animal.name} makes a sound"
  end
end

defmodule Dog do
  use GenObject
  inherit Animal, [
    breed: "",
    trained: false
  ]
  
  # Override parent method
  def speak(%__MODULE__{} = dog) do
    "#{dog.name} barks! Woof!"
  end
  
  def sit(%__MODULE__{trained: true} = dog) do
    Dog.put(dog, :position, :sitting)
  end
  
  def sit(%__MODULE__{trained: false} = dog) do
    {:error, "#{dog.name} is not trained to sit"}
  end
end

# Dog inherits all fields from Animal plus its own
dog = Dog.new(
  name: "Rex", 
  species: "Canis lupus", 
  breed: "Labrador", 
  age: 3,
  trained: true
)

# Use inherited and own methods
Dog.speak(dog)  # "Rex barks! Woof!"
Dog.sit(dog)    # Updates position to :sitting
```

### Multi-level Inheritance

```elixir
defmodule LivingThing do
  use GenObject, [
    alive: true,
    birth_date: nil
  ]
end

defmodule Animal do
  use GenObject
  inherit LivingThing, [
    name: "",
    species: ""
  ]
end

defmodule Mammal do
  use GenObject
  inherit Animal, [
    warm_blooded: true,
    fur_color: nil
  ]
end

defmodule Dog do
  use GenObject
  inherit Mammal, [
    breed: "",
    trained: false
  ]
end

# Dog inherits from the entire chain
dog = Dog.new(
  name: "Buddy",
  species: "Canis lupus",
  breed: "Golden Retriever",
  fur_color: "golden",
  birth_date: ~D[2020-01-15],
  trained: true
)
```

### Complex Inheritance Patterns

```elixir
defmodule Vehicle do
  use GenObject, [
    make: "",
    model: "",
    year: nil,
    mileage: 0
  ]
  
  def drive(%__MODULE__{} = vehicle, distance) do
    Vehicle.put_lazy(vehicle, :mileage, fn v -> v.mileage + distance end)
  end
end

defmodule Car do
  use GenObject
  inherit Vehicle, [
    doors: 4,
    fuel_type: :gasoline
  ]
  
  def honk(%__MODULE__{} = car) do
    "#{car.make} #{car.model} honks: BEEP BEEP!"
  end
end

defmodule ElectricCar do
  use GenObject
  inherit Car, [
    battery_capacity: 0,
    charge_level: 100,
    fuel_type: :electric  # Override parent default
  ]
  
  def charge(%__MODULE__{} = car, amount) do
    ElectricCar.put_lazy(car, :charge_level, fn c ->
      min(100, c.charge_level + amount)
    end)
  end
  
  # Override parent method
  def drive(%__MODULE__{} = car, distance) do
    car = super(car, distance)  # Call parent implementation
    # Reduce charge based on distance
    ElectricCar.put_lazy(car, :charge_level, fn c ->
      max(0, c.charge_level - div(distance, 10))
    end)
  end
end

# Create an electric car with full inheritance chain
tesla = ElectricCar.new(
  make: "Tesla",
  model: "Model 3",
  year: 2023,
  battery_capacity: 75,
  doors: 4
)

# Use methods from all levels of inheritance
tesla = ElectricCar.drive(tesla, 100)    # Inherited and overridden
tesla = ElectricCar.charge(tesla, 20)    # Own method
message = Car.honk(tesla)                # Parent method
```

## Advanced Usage

### Custom GenServer Callbacks

You can override GenServer callbacks while still using GenObject functionality:

```elixir
defmodule TimestampedObject do
  use GenObject, [
    data: nil,
    created_at: nil,
    updated_at: nil
  ]
  
  # Override init to set timestamps
  def init(opts) do
    now = DateTime.utc_now()
    opts = opts
    |> Keyword.put(:created_at, now)
    |> Keyword.put(:updated_at, now)
    
    super(opts)  # Call GenObject's init
  end
  
  # Override handle_call to update timestamps
  def handle_call({:put, field, value}, from, object) do
    result = super({:put, field, value}, from, object)
    case result do
      {:reply, updated_object, state} ->
        updated_state = TimestampedObject.put(state, :updated_at, DateTime.utc_now())
        {:reply, updated_object, updated_state}
      other -> other
    end
  end
end
```

### Supervision

GenObjects can be supervised like any GenServer:

```elixir
defmodule MyApp.ObjectSupervisor do
  use Supervisor
  
  def start_link(_opts) do
    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
  end
  
  def init([]) do
    children = [
      {Person, [name: "Default Person"]},
      {Dog, [name: "Default Dog", breed: "Mixed"]}
    ]
    
    Supervisor.init(children, strategy: :one_for_one)
  end
end
```

## Performance Considerations

- Use asynchronous operations (`put!/3`, `merge!/2`, etc.) when you don't need the return value
- Use `get/2` for single fields instead of `get/1` when you only need one field
- Batch multiple updates using `merge/2` instead of multiple `put/3` calls
- Lazy operations are computed synchronously, so complex computations may block

## API Reference

### Creation and Lifecycle
- `YourModule.new/1` - Create a new object
- `YourModule.close/1` - Stop the object process

### Field Access
- `YourModule.get/1` - Get complete object state
- `YourModule.get/2` - Get specific field value

### Field Updates  
- `YourModule.put/3` - Update field synchronously
- `YourModule.put!/3` - Update field asynchronously
- `YourModule.put_lazy/3` - Update field with function synchronously
- `YourModule.put_lazy!/3` - Update field with function asynchronously

### Bulk Operations
- `YourModule.merge/2` - Merge multiple fields synchronously
- `YourModule.merge!/2` - Merge multiple fields asynchronously
- `YourModule.merge_lazy/2` - Merge with function synchronously
- `YourModule.merge_lazy!/2` - Merge with function asynchronously

All functions accept either a PID or an object struct containing a `:pid` field. Replace `YourModule` with your actual module name (e.g., `Person`, `Dog`, etc.).

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)  
5. Create a new Pull Request

## License

This project is licensed under the MIT License - see the LICENSE.md file for details.