Rails Migration Column Type Polymorphic - ruby-on-rails

I've got a little situation that I'd like to take care of, but I don't even know if it's possible.
In my app, I have one model that can take a parameter value, this parameter can be either a string or an integer or a boolean.
Instead of doing two string column (in my migration) with one labeled as "type" (the type - really !) and the other labeled as "value" (for the... value ! Yes !) i was wondering if it was possible that there is only the column value remaining, with polymorphics types in there.
The Model represent a property on a taks. Theses properties can be : is this task's open (type "boolean" and value "true" for exemple) ? Does this task has a percentage (type "integer", value "20") ? Note that the table also have a column name.

Polymorphism in Rails is more about associations where the class of the related item is unknown - not database columns with a dynamic type.
While is possible to implement a dynamic column in a relational database using two string columns value and value_type and use a custom getter to typecast value its going to get really messy. Another possibility is to use a something like a HSTORE or JSON column type but again do you really need it? Is there a better way to structure your business logic than trying to jam everything into a single column?
I'm guessing that what you may be looking for is something like an enum to denote the state of your model.
class Ticket < ActiveRecord::Base
enum status: [ :open, :closed, :pending, :active ]
end
This would use an integer column named status.
If you wanted to store additional information it would be prudent to use an additional database column. Like if you want to monitor the progress you would create an integer database column named tickets.progress.

For your answer, yes it is possible, but don't do it that way.
It is possible to use string for all different cases:
"integer:100", "integer:23", etc.
"boolean:true", "boolean:false", etc.
you will need to identify each data type(use concat)and convert to a right type and only then you can use the value.
But it is completely wrong and its implementation is going to be very complicated. I strongly recommend not to use this example or something similar.
I think that this is a wrong database structure.
In general, if you have a case in which you have a column that you think can contain more than one data type - something is wrong in your database and you should make a 'redesign' of your database.
Usually the best practice is to separate to different columns. In your case, your column can get different types, each type represents different information. I think your table should have these columns:
status_id - will reference to another table, Status that will contain all possible statuses(if there are more than 2 options).
progress/percentage - integer.
My answer isn't relevant for relational databases.

Related

Data type and approach for Rails attributes with occasional second value

I have a use case where a user clicks on a word and sees information about its base word. Very occasionally there are words with two different but equally accepted spellings with identical meanings, and I'd like to display the alternate spelling when this happens (examples for Spanish: 'video' and 'vídeo', or 'quizá' and 'quizás').
Similarly, there are times where a base_word will need two inflection_forms.
The BaseWord model has attributes for base_word:string, inflection_form:string (i.e., 'ND3XX'), and the language_id. A base_word has_many :inflections to handle related words, but it seems silly to create a new inflection for a differently spelled word with an identical grammatical role.
I tried serializing both of the fields into an Array, and then later as a Set, but in both cases I had trouble querying the database for base_words where base_word was equal to one of the set/array members.
What is the most logical way to handle this case?
Since you are using Postgres you can use it's built in Array type which can be queried using SQL queries. cf http://edgeguides.rubyonrails.org/active_record_postgresql.html#array
Update 1 : Query example
Yes, its not as simple but, assuming you add an inflections column of type array to your migration, you should be able to hide the complexity with something like:
class BaseWord < ActiveRecord::Base
...
scope :has_inflection, -> (word) { where('? = ANY(inflections)', word) }
And then use it like any other scope. For ex.
BaseWord.has_inflection('vídeo').order('base_word').limit(5)

How do I properly store multiple checkboxed values using SimpleForm?

Basically I want to create an option in my form that accepts checkboxes (0 to all values accepted).
I love the structure of enums, because I get the performance speed of storing the integer in the DB, but I can reference the symbol in my code. However, I doubt I can use enum when I am storing multiple values like a checkbox.
So the way I imagine it working best is to just store it as a string that is also an array. So something like this:
# refactor_rule :string default([]), is an Array
Then my form looks like this:
<%= f.input :refactor_rule, collection: ["dry", "concise", "performant"], as: :check_boxes, class: "form-control" %>
The issue with this approach is when I store just 1 or 2 of the options (i.e. not all), this is what the attribute looks like:
q.refactor_rule
=> ["", "dry", "concise"]
Which I hate, because of the empty value at [0].
So my questions are as follows:
What's the most performant way to achieve this? Note that the options in my checkbox are static, but the field needs to accept multiple not 1?
How do I only store the values checked and not empty values?
Is there any way to take advantage of Rails built-in enum functionality even though I am storing multiple values?
Instead of an enum (performant but allows only a single value to be stored) or a serialized array (slow and storage space demanding) you should consider storing the boolean values as a bit field. This approach converts multiple boolean values (flags) to a single integer column where each flag has its own bit position in the integer. This of course has a massive positive impact on performance - you can search for any combination of flags using the & operator (in MySQL) instead of trying to search for substrings in a text column.
Take a look at the flag_shih_tzu or the bitfields gems. Both gems enhance Active Record so that all flags act like separate "virtual" attributes of a model object and they provide convenient methods for searching through such attributes. For this to work you probably would have to rewrite the simple_form form to use 3 separate check boxes instead of a single one though.
You can always have something like this to "clean" your attributes :
q.refactor_rule.reject!(&:empty?)
Which is gonna reject all empty elements from you array. Mind the !. reject! replaces it without the empty elements, reject just returns it. Your call !
If you really need to store an array in database, you can do it like so, in your migration :
create_table :products do |t|
t.text :tags, array: true, default: []
end
(This is from this blog post from Plataformatec)

Is it possible to set a flexible data type using Rails and Postgres?

I'm setting up a question and answer model, and some questions will require Boolean answers, some will require Text, some Integers, some enum (I guess that's the same as integer, but I need to define the meaning in the model) and some Datetime). Is it possible for me to set a flexible model column in the Answer model (e.g. one answer column that accepts any data, and only sets it according to the answer_type of a question object)? Or would it be better to set up a YesNoAnswer model, a TextAnswer model, an IntegerAnswer model etc etc, and then a method on the Question model that chooses which type of answer model it needs based on the answer_type column?
For what it's worth I'm planning to load pre-set questions into the DB using a YAML file that "know" what next_question to send the controller to depending on the answer to the previous question. See my earlier question here.
I had heard about variant data types, but I'm not sure whether that's possible with Rails.
Refer to here to list out the data types which were using in rails, btw, this lists out all postgres's data types, likely, we don't have what you expected.
My experience in a previous project to work with multiple data types is:
Define a value column (type is text, so it is enough to contain anything)
Define a value_type column (this is an enum: {:integer, :boolean,..} for eg)
Define validation callbacks to validate the value & value_type
Hope this helps for you

Issue of using field of type array in rails

There might be a group of records in a table where only two fields vary record by record, and rest fields remains same. This calls for a normalization by splitting by a table through a foreign key association. But, in Ruby-on-Rails, it would mean the creation of a model. So, is it still possible to lessen use of disk space?
May be, it is, because it would be reasonable that storing multiple values of one column in a record would require the column to be an array of any type. But declaring the field to be :array type results in an error. So, is there a way to work around it?
After generating a model, open the model's file. Insert one line for each field.
serialize :field_name
But ensure that the fields for which you are serializing, should be of type
:text
or
:string
If they aren't of such primitive data types, i.e. of another type like
:datetime
then it would return an error.
This step is not complete as a whole. You need to do one complementing step: de-serialize, because in the model-level storage, it is stored as a string starting with "---\n-", which is not suitable for array-type operations.
While reading data from the model, you need to perform the following step:
YAML.load(field_name)
where field_name refers to the field that was serialized.
The above step would return an array, on which you can perform normal array operations.

efficiently storing "answers"

I'm building out an Answer ActiveRecord class that can have different types of answers. That is, the answer could be an integer, float, date, string... whatever...
The way I see it there are two ways to store the answers
1)
Have an attribute named "value" that is serialized.
This is nice since you can always access the answer from the same place. It probably sucks for searching answers since the data has to be de-serialized in order to be used (is this a correct assumption?)
2)
have several attributes integerValue, floatValue, etc...
This is easiest to search (if you know what type you're searching (which you probably do))
This sucks since the value isn't in the same place and need some hokey method like:
def value
stringValue||floatValue||integerValue
end
and some validations to ensure that only the correct one is set
What would you do??
I'd do one of two things:
Use single-table inheritance. Your table does contain those integerValue, floatValue, etc. fields; but it also contains a "type" field, and you'll subclass the Answer model to make IntegerAnswer, FloatAnswer, StringAnswer, etc. And then you can simply map the value accessor to one of those fields in each subclass. Look in the Agile book or Google on single-table inheritance in Rails for more on how to implement this.
Use a non-relational database. Something like MongoDB or CouchDB would render this problem moot; you could make value anything you wanted, or have multiple values per answer, or skip it entirely. I personally like Mongo with the MongoMapper gem, and if I had to address your use case that's the direction I'd go. It is getting away from ActiveRecord and "mainstream" Rails though, so you'd have to be comfortable with living at least a little on the edge.
What converting the value to a string in the database.
And using single table inheritance to define the type and retrieve the appropriate value.
You'd have one model Answer with two fields : one "value", which is a string.
And one "type", which is a string too and gets the type of the value.
Then for the float, you' have the following :
class FloatAnswer < Answer
def value
attributes['value'].to_f
end
end
When you do a Answer.find(:all), if the element has FloatAnswer as value, you will have a FloatAnswer object. Not an Answer one.
So you keep only one database field and always have the datas in the appropriate format.

Resources