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).
Related
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
I'm reading "Rails AntiPatterns" at the moment, and one of the first patterns mentioned is the Single Responsibility Principle. At this point I've encountered SRP enough times to realize that it's a fundamental concept for beginners like me to understand, which is why it's so frustrating that it's not clicking yet.
The book gives an example of an Order class:
class Order < ActiveRecord::Base
def self.find_purchase
#...
end
def self.find_waiting_For_review
#...
end
def self.find_waiting_for_sign_off
#...
end
def self.advanced_search(fields, option = {})
#...
end
def self.simple_search
#...
end
def self.advanced_search
#...
end
def to_xml
#...
end
def to_json
#...
end
def to_csv
#...
end
def to_pdf
#...
end
end
To illustrate SRP, the book recommends extracting out the 4 instance methods into a separate OrderConverter class. This makes sense to me. But at the same time, this OrderConverter class could still have multiple reasons to change:
If the application no longer requires one of the 4 formats mentioned,
the corresponding method would have to be deleted.
If the application
needed to convert to other formats, more methods would need to be
implemented.
If the method used to convert Order instances to different formats is
changed (assuming they all use the same method with a different parameter
which corresponds to the format required).
Wouldn't it be even more "correct" to separate each of these methods into a separate converter class (i.e. PdfConverter, CsvConverter, XmlConverter, etc.)? That way, the only reason for each converter class to change would be if the conversion method itself changed. If a new format was needed, a new class could be created. And if an old format is no longer needed, the class could simply be deleted. And should the original Order model really be responsible for finding instances of itself? Couldn't you separate the 'find' methods into a separate 'OrderFinder' class?
I've read the SRP chapter of Sandi Metz's "Practical Object-Oriented Design In Ruby", and she recommends the following test to see if a class has a single responsibility:
How can you determine if the Gear class contains behavior that belongs somewhere
else? One way is to pretend that it's sentient and to interrogate it. If you
rephrase every one of its methods as a question, asking the question out to make
sense. For example, "Please Mr. Gear, what is your ratio?" seems perfectly
reasonable, while "Please Mr. Gear, what are your gear_inches?" is on shaky
ground, and "Please Mr. Gear, what is your tire(size)?" is just downright
ridiculous.
But taken to an extreme, if a model has more than one attribute (i.e. a car has a # of doors and a color) with corresponding attr_accessors, isn't this already a violation of SRP? Doesn't each new attribute add another possible reason for the class to change? Clearly the answer is not to separate each of these attributes into separate classes. So then where does one draw the line?
You write about the OrderConverter class but you didn't show the source of that class. I assume that this class contains methods to_xml, to_json, to_csv and to_pdf.
Wouldn't it be even more "correct" to separate each of these methods into a separate converter class (i.e. PdfConverter, CsvConverter, XmlConverter, etc.)?
Yes, it's propably a good idea to separate these methods to converter classes: each converter class will be responsible for only one format (one responsibility).
... if a model has more than one attribute (i.e. a car has a # of doors and a color) with corresponding attr_accessors, isn't this already a violation of SRP?
Nope, these attributes (like color, no of doors, ...) are not a set of responsibilities! The responsibility of Car class is describing a Car (holding an information about a car). Each instance of car will describe one car. If a car for example is a model class (let's say you want to store a cars in DB and one instance of car is one row in DB) then you have to change the Car class if you want to change a way of describing a car in your system.
But if a car will have defined for example a producer, and a producer will be described by name and address then I would extract the details of producer to other classes because this is a responsibility of describing a producer of a car and not a Car itself.
One more thing. The SRP is not a pattern. This is a Principle (first in SOLID). This term was introduced by Robert Cecil Martin. Here http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod you can find more information.
You seem to decide that extracting colour and number of doors into seperate classes is too far, which I'd disagree with. Indeed, if car is a very simple car class, maybe it is somewhat over the top. However, there are only a certain number of valid colours, only certain numbers of doors are valid. What if I want to be able to identify my car colour by any of a series of aliases?
CarColor seems like a perfectly valid class to me. It can store all the logic of what is and is not a valid color, provide aliases etc.
Consider an example I've dealt with recently concerning names in a series of places in different forms:
Database 1: Joan Smith
Right, I'll take the name, stick it in a String and put it in my person. No point making a fuss and wrapping one thing in it's own class.
Database 2: Smith, Joan
Ok, so I'll override the setter, check for this format and flip it if it's right.
Database 3: person: { title: Mrs, first: Joan, last: Smith }
Right, we'll just stick the title on the beginning, concatenate the last 2 together and then glue the whole thing into our damned string.
Database 4: foreign name that doesn't follow your structure and the whole concept of first, middle, last takes on a completely different meaning.
...turns out names are complicated.
This can all be nicely avoided by taking your supposedly simple structures and delegating to another class to handle what a name means, when are two names the same, how do we print them etc.
Prematurely wrapping all singleton values in their own classes can be somewhat overkill - but I've found that a bias towards wrapping when in doubt tends to serve me well later even if it feels silly at the time, especially when the value permeates through the rest of the code and so changing it later affects everything.
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
Normally one would build a related model instance through its parent object:
#child = #parent.children.build(params[:child])
But when we're using STI and we want to do this while at the same time building it as one of the subclasses the syntax seems to break down. This is the best way to do it as far as I can tell (ignore the security problems around not checking the type against an approved list):
#child = params[:type].classify.constantize.new(params[params[:type]])
#child.parent = #parent
Is this the best way to go about it? I'm using a single controller to build all the different subclass types so I need to supply the type as a parameter.
I usually use this trick:
class BaseModel < ActiveRecord::Base
private
# this will enable you to set the type field
def attributes_protected_by_default
super - [self.class.inheritance_column]
end
end
Now in your controller:
#parent.children.build(params[:base_model])
Make sure params[:base_model] hash has a field called type. I usually add a hidden form field to store the type.
Make sure you add the relevant checks to ensure the correct sub types are created.
Note : This trick will not work in Rails 3.
I am using single table inheritance in my rails application, and want to explicitly set the type of an instance.
I have the following;
class Event < ActiveRecord::Base
class SpecialEvent < Event
which is implemented through single table inheritance.
SpecialEvent.new works as expected, but I want to be able to do things like
Event.new(:type => 'SpecialEvent')
So I can create different sub_types easily in the application.
However this doesn't work and seems to set :type to nil, not the value I set it to; I suspect this is because by calling Event.new it is overwriting the :type argument.
Has anyone got a good way of doing this?
If you're trying to dynamically instantiate a subtype, and you have the type as a string, you can do this:
'SpecialEvent'.constantize.new()
from "Pragmatic - Agile Web Development with rails 3rd edition", page 380
There’s also a less obvious constraint (with STI). The attribute type
is also the name of a built-in Ruby method, so accessing it directly
to set or change the type of a row may result in strange Ruby
messages. Instead, access it implicitly by creating objects of the
appropriate class, or access it via the model object’s indexing
interface, using something such as this:
person[:type] = 'Manager'
man, this book really rocks
No, I want to create instances of
sub-types, where I want to
programmatically determine which
sub_type they are
– HermanD
You could use a factory pattern, although I have heard recently that people frown on the overuse of this pattern. Basically, use the factory to create the actual types you want to get
class EventFactory
def EventFactory.create_event(event_type)
event_type.constantize.new()
end
end
To me it sounds like you'll need some mojo in the event#create action:
type = params[:event].delete(:type)
# check that it is an expected value!!!
die unless ['Event', 'SpecialEvent'].include(type)
type.constantize.new(params[:event])
Apparently, Rails does not allow you to set Type directly. Here's what I do...
klass_name = 'Foo'
...
klass = Class.const_get(klass_name)
klass.new # Foo.new
I believe .constantize is a Rails inflector shortcut. const_get is a Ruby method on Class and Module.
Up front I'll agree that STI is often NOT the best way to deal with things. Polymorphism, yes, but it's often better to use a polymorphic association than STI.
That said, I had a system in which STI was important. It was a judicial system and things like court cases were remarkably similar across their types and generally shared all their essential attributes. However, a civil case and a criminal case differed in the elements they managed. This happened at several levels in the system so abstracted my solution.
https://github.com/arvanasse/sti_factory
Long story short, it uses a factory method to leverage the common approach described above. As a result, the controller can remain neutral/ignorant of the particular type of STI class that you're creating.
You can use the Rails safe_constantize method, which will ensure the object/class actually exists.
For example:
def typeify(string)
string.classify.safe_constantize
end
new_special_event = typeify('special_event').new