Cleanly validating event schemas in RailsEventStore - ruby-on-rails

I am building a CQRS and event sourced application with Rails and RailsEventStore. In RailsEventStore it seems you are supposed to inherit classes that represent events from RailsEventStore::Event.
class UserRegistered < RailsEventStore::Event
…
end
This base class exposes an event_id, a data hash and an optional metadata hash. As far as I understand, this looks more like what Greg Young once called an event envelope and my domain model is supposed to only care for the actual event, which is stored within the envelopes data hash.
Now I would like to enforce a schema on the my event. But it seems like RailsEventStore wants me to model a subclass of RailsEventStore::Event (the envelope) for each event type differentiated by my domain model.
Validating the contents of the data hash in a subclass of the envelope feels hacky and dirty. I always need to override the initialize method and pass on stuff that I really don't care about.
class UserRegistered < RailsEventStore::Event
def initialize(event_id: SecureRandom.uuid, metadata: nil, data: {})
ensure_valid!(data)
super(event_id: event_id, metadata: metadata, data: data)
end
def ensure_valid!(data)
raise ArgumentError unless data[:user_id]
end
end
Also accessing all the events attributes through event.data[:my_attribute] doesn't feel nice. Adding lots of delegation methods for each envelope subclass seems to be a waste as well.
I would rather like to model a plain Ruby object or use something like Dry::Struct to enforce a schema on the content attributes of my event.
class UserRegistered < Dry::Struct
attribute :user_id, Dry::Types::String
end
Am I missing something here? How can I validate my events in a clean way?

An example of Dry::Struct schema can be found in sample app repository:
https://github.com/RailsEventStore/cqrs-es-sample-with-res/blob/e4983433bc5d71252da58a23da9374f17e1d5cb3/ordering/lib/ordering/order_submitted.rb +
https://github.com/RailsEventStore/cqrs-es-sample-with-res/blob/e4983433bc5d71252da58a23da9374f17e1d5cb3/ordering/lib/ordering/order_submitted.rb
While inheriting from RailsEventStore::Event is the simplest, it is not a requirement. A good example of that can be Protobuf integration:
https://railseventstore.org/docs/protobuf/#defining-events. It resembles an event envelope even more.
An event passed to RES is required to respond at least to event_id, data, metadata and type:
https://github.com/RailsEventStore/rails_event_store/blob/cfc91c9cb367e514ba1c6de1a711a7610780b520/ruby_event_store/lib/ruby_event_store/mappers/transformation/domain_event.rb#L9-L12. You can also see there how it is instantiated when loaded from the store.
This behaviour can be altered when you provide your own mapper or just replace RubyEventStore::Mappers::Transformation::DomainEvent responsible for that transformation.
Please compare:
https://github.com/RailsEventStore/rails_event_store/blob/cfc91c9cb367e514ba1c6de1a711a7610780b520/ruby_event_store/lib/ruby_event_store/mappers/default.rb
vs.
https://github.com/RailsEventStore/rails_event_store/blob/cfc91c9cb367e514ba1c6de1a711a7610780b520/ruby_event_store/lib/ruby_event_store/mappers/protobuf.rb

Related

Wrapping an ActiveRecord object as a PORO. How do get all the attributes?

I'm trying to wrap an AR object as a PORO as part of an API response for the purpose of preventing the client application from calling .save on the object. However, I want all the attributes and this object has like 50 columns. Is there an easy way to create attr_accessors for all of the attributes?
Say this is the beginning of my class:
module Something
class Apple
attr_accessor [...]
Say the AR model is also called Apple.
What goes inside there? Is there a way for me to quickly get all the attributes of the AR Apple as attr_accessors?
I wouldn't bother. It'd be simpler to use the readonly active record method.
docs
Apple.readonly.first
Apple.readonly.where(display: true)

How to dynamically add properties to a ROAR representer?

I am using ROAR to implement an API for a rails application. This application deals with tickets that can have attributes like a subject and a description, but also have user defined attributes. For simplicity lets assume a ticket looks like:
class Ticket
attr_accessor :subject, :description
def custom_attributes
# in reality these attributes depend on the current ticket instance
# they are not hard-coded into the class
[['priority', 'high'], ['Operating System', 'Ubuntu']]
end
end
The desired JSON output for such a ticket looks as follows:
{
"subject": "Foo",
"description": "Bar",
"customField1": "high",
"customField2": "Ubuntu"
}
Now you might already see the problem. All properties are immediate children of the root object, this means I can't write that up as representer:
class TicketRepresenter
property :subject
property :description
# Need to iterate over instance members on the class level here...
end
Is there some mechanic that ROAR offers to accomplish that? E.g. a callback that is executed in the context of an actual instance, e.g.
def call_me_on_write
represented.custom_attributes.each do |attribute|
add_property('customField1', attribute[1])
end
end
Is there something like this in ROAR that I have overlooked to accomplish this?
I looked in both the docs for ROAR and the docs for representable, but could not find anything.
Disclaimer
I tried to simplify the actual circumstances to make the question more readable. If you think that important information are missing, please tell me. I will thankfully provide more details.
Out of scope
Please do not discuss whether the chosen JSON format is a good/bad idea, I want to evaluate whether ROAR would support it.
I believe the best approach for the problem would be to use Roar's writer:. It completely turns control over the output to you by passing a handful of values it calls options to a provided lambda.
For example:
property :desired_property_name, writer: -> (represented:, doc:, **) do
doc[:desired_key] = represented.desired_value
end
There are a lot of uses not covered by the github readme but which are documented on the Trailblazer website. This one in particular can be found at http://trailblazer.to/gems/representable/3.0/function-api.html#writer.
Cheers!
I ended up dynamically creating classes from my basic representer:
class TicketRepresenter
property :subject
property :description
def self.create(ticket, context = {})
klass = Class.new(TicketRepresenter) # create a subclass of my representer
ticket.custom_attributes.each do |attribute|
# for each custom attribute in the actual instance insert a property into the created class
property "customField#{attribute.id}".to_sym
getter: -> (*) { attribute.value }
end
# return an instance of the class created above
klass.new(ticket, context)
end
end
Basically that means the actual representer class used to create the JSON is a different one for each Ticket.
If you wanted to read a Ticket back from JSON, it is neccessary to correctly initialize the representer so that the created representer class knows about your custom fields and also define setters.
You will now need to conventionally call the new create method instead of new.
If you need your representer to be created by ROAR (e.g. for a collection), you can use the Polymorphic Object Creation mechanism of ROAR.
Note: The code above does not exactly fit the example of custom attributes posted in my question, but you should get the idea (in the example an attribute did not have members like id and value, but was list consisting of key and value).

What is the best practice for model fetching from external source

So I have the ActiveRecord model like this
class User < ActiveRecord::Base
has_many :posts
end
And I have Api class that fetches the attributes from web for User and returns a hash, which in turn needs some preprocessing to fit into the user model. E.g. the response from api is {response: {id:20, stars:{count:20}}} needs to be processed to user.id=20 and user.stars_count=20.
Now there are 2 ways that I can easily see to do the fecting thing. One way is add a method to Api like get_user, that does all the processing returns a new User model with filled attributes. The other is to add fetch method to User that does all the processing.
In the first case I don't like that my Api class will be filled with a lot of get_model methods like get_user, get_post, get_all_posts_for_user etc. and quickly can become unmanagable. In the second case the model is filled with a lot of preprocessing stuff methods like fetch, fetch_all_posts and looks nasty too.
So what is the best practice to deal with this problem?
Take a look at Active Resource, you don't have to do what you are doing manually.

Force reload another model's methods in rails?

I have a model that defines methods based off of the entries in another model's table: eg Article and Type. An article habtm types and vice versa.
I define in Article.rb:
Type.all.each do |type|
define_method "#{type.name}?" do
is?(:"#{type.name}")
end
end
This works great! it allows me to ensure that any types in the type db result in the methods associated being created, such as:
article.type?
However, these methods only run when you load the Article model. This introduces certain caveats: for example, in Rails Console, if I create a new Type, its method article.type_name? won't be defined until I reload! everything.
Additionally, the same problem exists in test/rspec: if I create a certain number of types, their associated methods won't exist yet. And in rspec, I don't know how to reload the User model.
Does anyone know a solution here? Perhaps, is there some way to, on creation of a new Type, to reload the Article model's methods? This sounds unlikely.. Any advice or guidance would be great!
I think you'll be better off avoiding reloading the model and changing your api a bit. In Article, are you really opposed to a single point of access through a more generic method?
def type?(type)
return is? type if type.is_a? String # for when type is the Type name already
is? type.name # for when an instance of Type is passed
end
If you're set on having separate methods for each type, perhaps something like this would work in your Type class
after_insert do
block = eval <<-END.gsub(/^ {6}/, '')
Proc.new { is? :#{self.name} }
END
Article.send(:define_method, "#{self.name}?", block)
end

Should rails models be concerned with other models for the sake of skinny controllers?

I read everywhere that business logic belongs in the models and not in controller but where is the limit?
I am toying with a personnal accounting application.
Account
Entry
Operation
When creating an operation it is only valid if the corresponding entries are created and linked to accounts so that the operation is balanced for exemple buy a 6-pack :
o=Operation.new({:description=>"b33r", :user=>current_user, :date=>"2008/09/15"})
o.entries.build({:account_id=>1, :amount=>15})
o.valid? #=>false
o.entries.build({:account_id=>2, :amount=>-15})
o.valid? #=>true
Now the form shown to the user in the case of basic operations is simplified to hide away the entries details, the accounts are selected among 5 default by the kind of operation requested by the user (intialise account -> equity to accout, spend assets->expenses, earn revenues->assets, borrow liabilities->assets, pay debt assets->liabilities ...) I want the entries created from default values.
I also want to be able to create more complex operations (more than 2 entries). For this second use case I will have a different form where the additional complexity is exposed.This second use case prevents me from including a debit and credit field on the Operation and getting rid of the Entry link.
Which is the best form ? Using the above code in a SimpleOperationController as I do for the moment, or defining a new method on the Operation class so I can call Operation.new_simple_operation(params[:operation])
Isn't it breaking the separation of concerns to actually create and manipulate Entry objects from the Operation class ?
I am not looking for advice on my twisted accounting principles :)
edit -- It seems I didn't express myself too clearly.
I am not so concerned about the validation. I am more concerned about where the creation logic code should go :
assuming the operation on the controller is called spend, when using spend, the params hash would contain : amount, date, description. Debit and credit accounts would be derived from the action which is called, but then I have to create all the objects. Would it be better to have
#error and transaction handling is left out for the sake of clarity
def spend
amount=params[:operation].delete(:amount)#remove non existent Operation attribute
op=Operation.new(params[:operation])
#select accounts in some way
...
#build entries
op.entries.build(...)
op.entries.build(...)
op.save
end
or to create a method on Operation that would make the above look like
def spend
op=Operation.new_simple_operation(params)
op.save
end
this definitely give a much thinner controller and a fatter model, but then the model will create and store instances of other models which is where my problem is.
but then the model will create and store instances of other models which is where my problem is.
What is wrong with this?
If your 'business logic' states that an Operation must have a valid set of Entries, then surely there is nothing wrong for the Operation class to know about, and deal with your Entry objects.
You'll only get problems if you take this too far, and have your models manipulating things they don't need to know about, like an EntryHtmlFormBuilder or whatever :-)
Virtual Attributes (more info here and here) will help with this greatly. Passing the whole params back to the model keeps things simple in the controller. This will allow you to dynamically build your form and easily build the entries objects.
class Operation
has_many :entries
def entry_attributes=(entry_attributes)
entry_attributes.each do |entry|
entries.build(entry)
end
end
end
class OperationController < ApplicationController
def create
#operation = Operation.new(params[:opertaion])
if #operation.save
flash[:notice] = "Successfully saved operation."
redirect_to operations_path
else
render :action => 'new'
end
end
end
The save will fail if everything isn't valid. Which brings us to validation. Because each Entry stands alone and you need to check all entries at "creation" you should probably override validate in Operation:
class Operation
# methods from above
protected
def validate
total = 0
entries.each { |e| t += e.amount }
errors.add("entries", "unbalanced transfers") unless total == 0
end
end
Now you will get an error message telling the user that the amounts are off and they should fix the problem. You can get really fancy here and add a lot of value by being specific about the problem, like tell them how much they are off.
It's easier to think in terms of each entity validating itself, and entities which depend on one another delegating their state to the state of their associated entries. In your case, for instance:
class Operation < ActiveRecord::Base
has_many :entries
validates_associated :entries
end
validates_associated will check whether each associated entity is valid (in this case, all entries should if the operation is to be valid).
It is very tempting to try to validate entire hierarchies of models as a whole, but as you said, the place where that would be most easily done is the controller, which should act more as a router of requests and responses than in dealing with business logic.
The way I look at it is that the controller should reflect the end-user view and translate requests into model operations and reponses while also doing formatting. In your case there are 2 kinds of operations that represent simple operations with a default account/entry, and more complex operations that have user selected entries and accounts. The forms should reflect the user view (2 forms with different fields), and there should be 2 actions in the controller to match. The controller however should have no logic relating to how the data is manipulated, only how to receive and respond. I would have class methods on the Operation class that take in the proper data from the forms and creates one or more object as needed, or place those class methods on a support class that is not an AR model, but has business logic that crosses model boundaries. The advantage of the separate utility class is that it keeps each model focused on one purpose, the down side is that the utility classes have no defined place to live. I put them in lib/ but Rails does not specify a place for model helpers as such.
If you are concerned about embedding this logic into any particular model, why not put them into an observer class, that will keep the logic for your creation of the associated items separate from the classes being observed.

Resources