NoMethodError from serialization in Ruby on Rails - ruby-on-rails

I'm trying to create an application that has forms, who have questions, who, in turn have answers. The questions require a different type of answer, e.g. free text or a choice from a set of possible choices (multiple choice question). Different types of questions inherit from the base class Question. It's the multiple choice questions that are proving problematic.
There are going to be multiple choice questions with different sets of possible answers (say, 1-5, 1-8 or yes/no). I was thinking of creating a class MultipleChoiceQuestion < Question, and the answer choices are given as an array and then saved to the database using serialization. I am, however, unable to get the serialization working at all.
From googling around, I've figures that serialization should work by writing:
class MultipleChoiceQuestion < Question
serialize :choices, Array
end
Now if I open rails console, and type
q1 = MultipleChoiceQuestion.new
q1.choices
I only get a NoMethodError on choices. Is there something else I need to do to get serialization working? I've also tried adding a text column "choices" in the database by editing the migration file and migrating the database, but it doesn't help either.
I'm using Rails 3.2.6 and PostgreSQL 9.1.
Update: If I change the MultipleChoiceQuestion class to inherit from ActiveRecord::Base instead of Question, serialization works. But Question inherits from ActiveRecord::Base, so I don't understand what the problem is.

I usually do something like this:
class User < ActiveRecord::Base
serialize :preferences, Hash
end
def preferences
read_attribute(:preferences) || write_attribute(:preferences, {})
end
to ensure that its default vaue is what you expected. And I mark the column of type text that is being serialized so attribute is stored as yaml.
user.preferences[:key] = value

You could also add a new model (and some database tables) for the choices, e.g.:
class Choice < ActiveRecord::Base
belongs_to :multiple_choice_question
end
class MultipleChoiceQuestion < Question
has_many :choices
end

Related

Rails 5 nested attributes alternative way?

I'm creating an app that it needs to be customizable.
Ej:
I have:
Position scaffold
Category scaffold
Class scaffold
Type Scaffold
Now, the situation is, position table along other attributes, includes a category attr, a class attr and a type attr. The problem I have is that every category, class and type is created and owned by another controller and moedel.
Now, here is where my problem comes to life.
when I tried to create a position (even though I can actually see all categories, classes and types listed on my position view via form.select) I cannot save a position with a category, class nor type.
I tried nested_attributes and don't work quite good in this situation.
What is the appropriate method tho attack this from scratch base on my scaffolds?
Any help will be very much appreciated!
Thanks in advance!
It is a little tough to figure out what you need without seeing your code, but I'll try my best to help.
Keep in mind that Class and Type are reserved words in rails; Columns named _type are used for polymorphic relationships. And Class is used well for ruby classes. So as a general rule of thumb I would stay clear of those terms.
For you Position model:
class Position < ActiveRecord::Base
has_many :categories
has_many :types
has_many :classes
accepts_nested_attributes_for :categories, :types, :classes
end
Then for your other models:
class NameOfClass < ActiveRecord::Base
belongs_to :position
end
You also need to make sure that you add these nested attributes to the whitelist on you controllers. It should include any columns that you want to accept that will update other models. Keep in mind that this has to be the last thing listed.
In your Position Controller
PositionsController < ApplicationController
private
def position_params
params.require(:position).permit(:id, :position_column_a, :another_column,
categories_attributes: [:id, :position_id, :any_other_column_you_want],
types_attributes: [:id, :position_id, :again_any_other_column])
Again, before you do anything I would look into renaming your scaffolds. the best way is to delete them and then regenerate. This will do the trick:
rails d scaffold Type
If you just need a simple dropdown of a list of categories you can try enums. However, it is not good for options that need to be customizable for the user. I typically only used them for statuses.
https://bendyworks.com/blog/rails-enum-sharp-knife
You can also read the API docs: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Is an Empty Table for implementing Reverse Polymorphism and ActiveRecord::Base okay?

I have spent a lot of thought on this situation and cannot figure out what the best modeling system is:
There is a Test. A test can have a variety of of TestItems. These TestItems can (currently) consist of TrueFalseQuestions, MultipleChoiceQuestions, ShortAnswerQuestions, and TestInfo.
All of the models will implement some sort of Printable module. They will all be printable, but each model handles its printing in a different way. All models will also have a position as they are sortable in relation to all other models. All models can belong to a test.
All models of type XXXQuestion will print numbers when they print. The TestInfo will not do that.
MultipleChoiceQuestions will have Answers as children.
I have tried creating a TestItem class that uses reverse polymorphism and a shareable question module:
class TestItem < ActiveRecord::Base
belongs_to :test
belong_to :item, polymorphic: true
db_fields: :main_text, :position, :item_id, :item_type
def sort(params)
...
end
end
module QuestionPrintable
def get_print_number
...
end
def print
raise NotImplementedError
end
end
module Question
def self.included(klass)
klass.class_eval do
include QuestionPrintable
has_one :test_item, as: :item, dependent: :destroy
delegate :test, :main_text to: :test_item
end
end
end
class MultipleChoiceQuestion < ActiveRecord::Base
include Question
has_many :answers
def print
number = get_print_number
...
end
end
This would work, except that some models (like TrueFalseQuestion) would not actually expand the TestItem class. They would have no extra information in the TrueFalseQuestions table, but they would implement methods unique to TrueFalseQuestions. I realize I could also wrap a TestItem in a TrueFalseQuestion wrapper whenever it's instantiated but then I would need to store the kind of the question on the TestItem to know when to do that. So, in some sense, the TrueFalseQuestion < ActiveRecord::Base class is actually storing the kind implicitly just by existing. I don't know if that is a valid use of ActiveRecord::Base.
All the questions do share the printing features of a number (and several behaviors I anticipate needing, just not quite yet) that are not shared with other types of TestItems (i.e. TestInfo). Additionally, some Question types will store extra data right now. And I believe that all of them will store more data as this problem evolves. So I do think that abstraction is helpful. Is it okay to have an table that more or less exists to allow the implementation of a polymorphic ActiveRecord model?
Also, having the text on the TestItem prevents a crazy amount of joins to display the main text of all items for a test.
The big difficulty, is if I do this a different way (for example not having a TestItem class and just a bunch of shared modules or storing these all as TestItems with a :kind attribute), I need to start switching behavior on the class type or an attribute, and I try to avoid any code that tests on class type or has so much behavior switch based on a attribute value.
I think in general those solutions can be achieved with duck typing, which would work with my empty ActiveRecord class, but this one just has me puzzled.
EDIT:
Another solution that occurred to me, that would prevent switching on kind would be to use some sort of kind value in the TestItem and use it to create a wrapper:
class TestItem < ActiveRecord::Base
belongs_to :test
attr_accessor :main_text, :position, :kind
def wrapped_object
klass = kind.constantize
klass.new(_needed_params)
end
end
class TrueFalseQuestion # DO NOT INHERIT
attr_accessor :kind, :position
def print
...
end
end
I left out the various modules to not distract from the general solution, those can be easily implemented.
So now my potential debate is:
Empty Database Tables
Positives:
No wrappers needed
More extendable in the future
Negatives:
It's an empty table....
Possible YAGNI
Method that returns wrapped object
Positives:
Solves the immediate problem without introducing extra database tables
Allows for all the same abstractions in the previous solution
Negatives:
Relies on the kind attribute (maybe not bad in this case?)
If the domain changes this could easily become too complex to maintain

Two Mongoid models stored in one MongoDB collection

This is something I want to do but I can't figure out if it is possible.
I would have two Ruby classes (SuperNes and MegaDrive) that include the same Module (Console).
So I assume there will be common attributes and a few particular ones. I would like to store it in the same MongoDB Collection (with the store_in helper).
How will I ensure that, for example, SuperNes.all will return only SuperNes data and not MegaDrive ?
Thank you for your answers and for the time you spent reading me !
May be you can try inheritance in mongoid ,
class Console
include Mongoid::Document
end
class SuperNes < Console
end
class MegaDrive < Console
end

Rails: dynamic environmental settings without magic numbers

Short version: Where should I store environment-specific IDs? ENV['some-variable']? Somewhere else?
Long version:
Let's say I have a model called Books and a book has a Category. (For the sake of this question, let's say a book only has one category.)
class Book < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :books
end
Now let's say one category is called 'erotica.' And I want to suppress erotica books in my type ahead. That seems straight forward. But in production and in development 'erotica' has a different ID. I don't want my code to be ID dependent. I don't want it to be string dependent (in case 'erotica' is renamed pr0n or whatever).
I think I should have something like
def suppress_method
suppress_category_id = look_up_suppression_id
...
end
but where should 'look up' look?
Thanks!
Taking this approach will be brittle, what if you want to suppress multiple categories? Erotica and Politics? The best design here is for you to actually add 'suppressed' as a boolean to category in a migration, and maintain that in your application's administration interface. After you've done that you can add a named scope like:
class Category < ActiveRecord::Base
named_scope :not_suppressed, :conditions=>{:suppressed=>false}
# or for rails 3
scope :not_suppressed, where(:suppressed=>false)
end
Then just update your type ahead code to do:
Category.not_suppressed.find ...
Rather than
Category.find

How many classes is too many? Rails STI

I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.

Resources