![image[1] image[1]](http://unplugged.giggio.net/image.axd?picture=ir-logo.png)
Este é o quinto post sobre IronRuby. Veja os anteriores:
- Rodando Ruby com C#: Parte 1
- Rodando Ruby com C#: Parte 2 – chamando uma classe Ruby no C#
- Rodando Ruby com C#: Parte 3 – chamando uma classe C# a partir do Ruby
- Rodando Ruby com C#: Edição especial: ASP.Net MVC 2.0, .Net 4.0 e Visual Studio 2010
Este é o post que eu queria escrever desde o começo. Nele vou mostrar um exemplo de uso real do IronRuby na sua aplicação, vou jogar uma idéia, que vai dar a vocês muitas outras idéias.
A idéia é que tenho uma aplicação feita com C#, mas tenho um pequeno pedaço da minha regra de negócios que quero deixar o usuário editar. Uma opção alternativa seria criar um formulário onde o usuário poderia tentar compor uma regra com textboxes, checkboxes e outros controles, e funcionaria, só que eu limitaria a liberdade do usuário na criação da regra. Com IronRuby posso dar o potencial do cliente escrever a regra em código.
Observação: A princípio eu ia fazer com ASP.Net MVC, usando C#. Por algum motivo não está rodando. Os testes funcionam, e funciona com uma aplicação console, mas não funciona com ASP.Net, seja MVC ou Webforms. Se alguém quiser testar, e não funcionar, votem lá no Codeplex para que o time possa olhar. O bug está aqui. Clique aqui para baixar a solution para testar. O projeto original com MVC está lá, assim como o de WebForms.
Como ficou o projeto:
Criei uma camada de apresentação em console app, só com C#, uma camada de domínio com C# e IronRuby, e os testes, só em C#, e às vezes passando código em IronRuby para testar.
Criei um sistema de pedidos. O cliente pode solicitar um pedido. Vejam as entidades:
Dessas, vale a pena ver a de pedido:
public class Pedido
{
public Cliente Cliente { get; set; }
public Pedido(Cliente cliente)
{
if (cliente == null)
throw new InvalidOperationException("Cliente não pode ser nulo.");
Cliente = cliente;
}
private List<ItemPedido> _itens = new List<ItemPedido>();
public void Adicionar(ItemPedido itemPedido)
{
_itens.Add(itemPedido);
}
public IEnumerable<ItemPedido> Itens { get { return _itens; } }
public decimal Valor
{
get
{
return _itens.Sum(i => i.Quantidade * i.Produto.Preco);
}
}
public int Id { get; set; }
}
As classes do projeto são simples, sem grandes comportamento, praticamente só getters e setters. A classe de pedido é mais esperta e expõe apenas os itens como um Enumerable, mas só permite incluir itens através de seu método.
Há dois serviços também:
O ServicoCriacaoDePedido cria pedidos com o método Criar, passando a eles um objeto de valor chamado de CriarPedido:
Ele então retorna um pedido criado.
O serviço mantém ainda na propriedade RegrasPosCriacaoDoPedido uma lista estática de regras que acontecem após a chamada de criação do pedido. Você pode criar regras chamando o método Incluir, e pode limpá-las chamando o método Limpar.
Quando o serviço criar um pedido, ele submete o pedido ao método privado RodarRegraSobreOPedido. É lá que as regras são aplicadas. As regras podem ser envio de e-mails, descontos, etc. Vejam a classe de regras:
public abstract class AposCriacaoPedido
{
public AposCriacaoPedido()
{
Id = Guid.NewGuid();
}
public abstract void Rodar(Pedido pedido);
public string Codigo { get; set; }
public Guid Id { get; set; }
}
Tem um método rodar, que recebe um pedido e é abstrato, ou seja, vai ter que ser implementado por clases herdeiras. É neste método que acontece a regra. A classe tem ainda um Id para identificação. A parte onde você percebe que há algo diferente é na propriedade Codigo. É lá que fica o código do IronRuby. Ele fica lá apenas para referência, já que, vocês verão em breve, teremos classes IronRuby filhas desta classe.
É aí que entra o serviço de criação de regras, a outra classe de serviços, a da direita. Ela tem somente um método público chamado CriarRegraPosCriacaoPedido. Bamos vê-lo:
public AposCriacaoPedido CriarRegraPosCriacaoPedido(string codigo)
{
const string templateDoMetodo =
@"
include RegrasDinamicas::Model
class {0} < AposCriacaoPedido
def Rodar(pedido)
{1}
end
end";
var nomeClasseTemp = "AposCriacaoPedido_" + Guid.NewGuid().ToString().Replace("-", string.Empty);
var codigoCompleto = string.Format(templateDoMetodo, nomeClasseTemp, codigo);
_engine.Execute(codigoCompleto, _scope);
var classe = _runtime.Globals.GetVariable<RubyClass>(nomeClasseTemp);
var retornoCriacao = _operations.CreateInstance(classe);
var aposCriacaoPedido = (AposCriacaoPedido)retornoCriacao;
aposCriacaoPedido.Codigo = codigo;
return aposCriacaoPedido;
}
Notem que este método cria com código Ruby uma classe com nome aleatório. O template desta classe é uma string, que possui dois placeholders. O zero é o nome da classe, gerado via Guid, e colocado na variável nomeClasseTemp para depois ser substituído no template. O placeholder 1 é o corpo do método da regra, passado pelo usuário, e que fica na variável codigo. O código completo fica na variável codigoCompleto. A classe gerada herda da classe de regras, e portanto pode ser instanciada e feito cast dela de uma classe dinâmica do Ruby para a classe do C#. A partir daí, a classe de regra está gerada. Ela recebe na propriedade Codigo o código passado pelo usuário, e é então repassada para o chamador, que a coloca na coleção estática de regras do serviço de criação de pedidos, que a aplica sempre que um pedido é criado.
O resto desta classe de serviço é código de infra que já vimos nos outros posts.
Tem mais uns negócios interessantes no projeto, como repositórios, e um padrão MVC para escrita na console. Vale a pena dar uma olhada.
A partir daí é só utilizar. Vejam o controller de regras (um pouco resumido):
class RegrasController : IRegrasController
{
private IList<AposCriacaoPedido> _regras;
private IRegrasView _regrasView;
private readonly ServicoCriacaoDeRegras _servicoCriacaoDeRegras = new ServicoCriacaoDeRegras();
public RegrasController(IRegrasView regrasView)
{
_regrasView = regrasView;
}
public void ExibirRegras()
{
if (_regras == null)
_regras = ServicoCriacaoDePedido.RegrasPosCriacaoDoPedido.ToList();
var proximaAcao = _regrasView.Listar(_regras);
proximaAcao(this);
}
public void CriarNova()
{
var proximaAcao = _regrasView.ExibirNova();
proximaAcao(this);
}
public void CriarNova(string codigo)
{
var regra = _servicoCriacaoDeRegras.CriarRegraPosCriacaoPedido(codigo);
ServicoCriacaoDePedido.IncluirRegraPosCriacaoDoPedido(regra);
_regras = null;
ExibirRegras();
}
public void ExibirRegra(int posicaoSelecionada)
{
if (posicaoSelecionada == -1)
return;
var pedido = _regras[posicaoSelecionada];
var proximaAcao = _regrasView.ExibirRegra(pedido);
proximaAcao(this);
}
}
Posso listar as regras, que passa pelo método ExibirRegras:
Posso listar uma regra:
Notem no método CriarNova, que o serviço de regras é utilizado. Isso me permite criar uma regra (com multi line):
E entao executá-la ao inserir um pedido:
Note a mudança no preço. Ao alterarmos o preço de um produto, todos os pedidos mudam (pequeno erro de modelagem…).
O código está disponível para vocês baixarem. Depois vou postar sobre o MVC que usei no projeto, que achei que ficou interessante.