I have a GraphQL enum like:
class MediaFilterType < Types::BaseEnum
value "TWITTER", value: :twitter
value "FACEBOOK", value: :facebook
value "YOUTUBE", value: :youtube
end
And a field that receives an array of this type or nil. Actually when we receive nil, we should pass along that we are going to use all available values in the enum:
def dashboard(media_types: nil)
if media_type.nil?
# Here is the problem below
media_types = MediaFilterType.values.values.map(&:value)
end
...
DashboardQuery.new(media_types).call
end
So I have to do this conditional sanitization for every field just like dashboard field. Not a big deal, except for the duplication. But I think this is a responsibility that should be inside the Query (there is a collaborator that can do that sanitizing inside the Query object). But I think it would be worse to extract the values from the GQL enum type inside a business object. Actually don't even know if I should be extracting these enum values anyway.
Is it good practice to extract values from the GQL type like that?
If it is ok, there is another way to use the enum values inside a business object like a query object or a sanitizer without having them depend on the GQL enum type?
Should I create an Enum with the same values in my business layer?
Appreciate any help.
There is no definitive 'right' answer here. The issue is, graphql-ruby requires a fair bit of duplication in many other cases - think of your models and types. I would not be too scared of a tiny bit of duplication. However...
But I think it would be worse to extract the values from the GQL enum type inside a business object
Depends. If the values are only used within the API and literally nowhere else your implementation is already as good as it gets.
Should I create an Enum with the same values in my business layer?
That is an option. If you forgo documentation for the GraphQL enum types you can get rid of any duplication fairly elegantly. Assuming you add your media filters as enums on some model:
class Dashboard
enum media_types: [:twitter :facebook :youtube]
end
You can then do:
class MediaFilterType < Types::BaseEnum
Dashboard.media_types.each { |media_type| value(media_type.upcase, value: media_type.to_sym)}
end
And for your field:
types = media_types || Dashboard.media_types
DashboardQuery.new(media_types).call
Your model enum now serves as a single source of truth.
However, it is not uncommon that your API diverges a bit from your models with time, so in that case there really isn't a nice way around duplicating at least some things. Just to keep that in mind.
Related
I have a requirement to list all the type values of STI. So I would like to maintain all the possible values of type.
Is it a good practice to make the type field an Enum in the parent class?
I have a requirement to list all the type values of STI. So I would
like to maintain all the possible values of type.
class ParentClass < ActiveRecord::Base
def self.sti_types
#sti_types ||= self.select(:type).distinct
end
end
Is it a good practice to make the type field an Enum in the parent class?
I assume you mean, the database enum type, if that's the case, the answer is no, because it does not give you the flexibility to add new classes as you wish, this means if you want a new class, you would have to migrate the enum column to add this new value.
I don't think it is a good idea, because if later you add a new record with new type, you have to manually update the enum, if you just want to know all the current type values in the table you can simple make a method like this:
def
your_model_name.distinct.pluck(:type)
end
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).
Enum attributes are great and I want to use them. But mapping enum values to integer would make it hard to maintain both code and database. Also my database would be highly coupled with my code which I think I should consider that a bad thing.
I know I can use a hash to organize an enum attribute with key/value pairs, but still it would be a lot better to be able to use an array and map to string values in database.
Is there any way to map enum to strings by default?
Looking at the code for enum, you can do this (at least in 4.1+): https://github.com/rails/rails/blob/master/activerecord/lib/active_record/enum.rb#L96-98 by passing a hash, for example:
class Foo
enum name: {
foo: 'myfoo',
bar: 'mybar'
}
Altough with unexpected results when accessing it, see https://github.com/rails/rails/issues/16459
foo_instance.foo!
foo_instance.name
=> "foo"
foo_instance[:name]
=> "myfoo"
Update
This issue was fixed in Rails 5, see https://github.com/rails/rails/commit/c51f9b61ce1e167f5f58f07441adcfa117694301. Thanks Yuri.
As far as I know it's not possible with Active Record's built-in enum functionality. However, there are a few popular 3rd party gems that do this. The closest match to what comes with Active Record are probably enumerize and SimpleEnum.
However, if you're looking for something a little different, I'd recommend ClassyEnum (full disclosure: I wrote it). Here are some of my notes on the difference between enumerize and SimpleEnum vs ClassyEnum:
Class-less enums (enumerize, SimpleEnum) are great for simple use
cases where you just need a field to represent one of a fixed set of
values. The main issue I have with this solution is that it encourages
conditionals scattered throughout your models, controllers and views.
It's tempting to use these gems because they are the simplest to
implement, but the long term payoff just isn't there IMO for anything
but the simplest cases.
ClassyEnum was designed to solve the problem of having scattered
conditional logic related to the different enums. You can still use it
for simple collections that don't have logic, but when you do need to
add logic (and you almost certainly will), you can push that into the
individual enum classes and take advantage of polymorphism.
It seems that with Rails 5 API only, an enum attribute of a model will be save in database as integer, but will be published in public API as a string (with ActiveModel::Serializer).
For example,
Article model:
class Article < ApplicationRecord
enum status: [ :visible, :hidden ]
end
Article serializer:
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :status, :title, :body
end
Will publish the following json:
{
"id": "1",
"type": "articles",
"attributes": {
"status": "visible",
"title": "Enums are passed as string in a json API render",
"body": "Great!",
}
How about:
class Foo < ApplicationRecord
NAMES = [
:foo,
:bar
]
enum names: NAMES.zip(NAMES).to_h
end
The short answer is no. You will need to use a gem (such as magic-enum) if you want to do anything but store integers.
In DHH's own words on this from the comments on the pull request that added this feature:
It's pretty inefficient to store enums as text. You're going to repeat the same text over and over and over again. I'd consider that an anti pattern. People are better off doing a migration to ints if they want to use this.
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 am fairly new to Rails and I was curious as to some of the conventions experts are using when they need to construct a very complex SQL query that contains many conditions. Specifically, keeping the code readable and maintainable.
There are a couple of ways I can think of:
Single line, in the call to find():
#pitchers = Pitcher.find(:all, "<conditions>")
Use a predefined string and pass it in:
#pitchers = Pitcher.find(:all, #conditions)
Use a private member function to return a query
#pitchers = Pitcher.find(:all, conditionfunction)
I sort of lean towards the private member function convention, additionally because you could pass in parameters to customize the query.
Any thoughts on this?
I almost never pass conditions to find. Usually those conditions would be valuable to add to your object model in the form of a named_scope or even just a class method on the model. Named scopes are nice because you can chain them, which takes a little bit of the bite out of the complexity. They also allow you to pass parameters as well.
Also, you should almost never just pass a raw SQL condition string. You should either use the hash style (:conditions => { :name => 'Pat' }) or the array style (['name = ?', 'Pat']). This way, the SQL is escaped for you, offering some protection against SQL injection attacks.
Finally, I think that the approach you're considering, where you're trying to create conditions in whatever context you're calling find is a flawed approach. It is the job of the model to provide an interface through which the pertinent response is returned. Trying to determine conditions to pass into a find call is too close to the underlying implementation if you ask me. Plus it's harder to test.