How to store many optional fields in Rails? - ruby-on-rails

I'm working on an app that displays Products. Each Product is a model (Product) with some common info like name, brand etc. To separate what kind of Product we're looking at we have a product_type (like: Phone, TV, Sofa etc) which is basically a tinyint field (e.g. 1 = Phone, 2 = TV).
Now, depending on the product_type each Product can have different options: if it's a phone the Product should have extra info associated (like weight, does it have LTE, does it have a front-facing camera etc), a TV should have info like display size and so on.
My question is: What is the best /or/ easiest way to add this extra data to the Product, depending on the type of Product?
I was thinking of having an extra model ProductOptions with fields product_id, option_type, option_value (VARCHAR) which would store extra fields, but I'm not sure if this is optional for performance & search. In this case, searching would mean finding all ProductOptions that match a given criteria (e.g. po = ProductOption.where(:option_type => "LTE", :option_value => "yes") ) and then doing a Product.findAllIn(po) to find the actual products.
Or should I go with Postgres and use HStore? Is HStore efficient when searching?
Any ideas?
Rails newbie, all code is pseudo-code

If you're already looking at ProductType, you might make this Single-Table Inheritance and actually define different product types. That would be easier to code anyway. In this case:
class Product < ActiveRecord::Base
# common functionality goes here
end
class Sofa < Product
# sofa specific functionality goes here
end
You can then put all the options on the product table, but have a different attr_accessible, validations, and views for the different product types.
The best part about this setup is it 'just works' in rails, all you have to do is put a type column on the products table. Also, it lets you find all sofas like so:
Sofa.all
and all everything like so
Product.all
As far as database performance, well, you'll end up with a fair number of null fields, but all your attributes will be searchable (unlike serialization), and you only need to query on one table. So, I would expect this to outperform any other option I know of.
Plus, it's nice and object oriented. :)
Good luck, welcome to Rails!

Related

Rails Search One Column On Multiple Saved Terms (Saved Searches In Model)

One table, one column ('headline' in an RSS feed reader). On the front end, I want a text area in which I can enter a comma-separated list of search terms, some multi-word, like for 'politics':
rajoy, pp, "popular party", "socialist party", etc
This could either be stored as part of a separate search model or as a keyword column on the 'category' or 'story' models, so they can be edited and improved with different terms from the front end, as a story develops.
In the RSS reader, have a series of links, one for each story or category, that, on being clicked return the headlines that contain one (or more) of the search terms from the stored list.
In a later version, it would be good to find headlines containing several of the terms in the list, but let's start simple.
Have been doing lots of reading about postgres, rails, different types of searches and queries, but can't seem to find what I want, which I understand is basically "search 'headlines' column against this list of search terms".
Sounds like it might be an array thing that's more to do with controllers in Rails than postgres, or cycling through a giant OR query with some multi-word terms, but I'm not sure.
Does anyone have any better pointers about how to start?
Users
If this will be user specific, I would start with a User model that is responsible for persisting each unique set of search terms. Think logon or session.
Assuming you use the Category method mentioned before, and assuming there's a column called name. Each search term would be stored as a separate instance in the database. Think tags.
headlines that contain one (or more) of the search terms from the stored list
Categories
Since each Category has many terms, and all the queries are going to be OR queries, a model that joins the User and Category, storing a search term would be appropriate.
I'm also assuming you have a Story model that contains the actual stories, although this may not be persisted in the database. I'm predicting your story model has a heading and a body.
Terminal Console
rails generage model SearchTerm query:string user:references category:references && rake db:migrate
Models
On your existing User and Category models you would add:
# app/models/user.rb
has_many :search_terms
has_many categories, through: :search_terms
# app/models/category.rb
has_many :search_terms
has_many :stories
Rails Console
This will automatically make it possible for you to do this:
#user = User.last # this is in the console, just to demonstrate
#category = Category.find_by_name("politics")
#user.search_terms.create {query: "rajoy", category: #category}
#user.search_terms.create {query: "pp", category: #category}
#user.search_terms.where(category_id: #category.id).pluck(:query)
-> ['rajoy', 'pp']
Controllers
What you will want to do with your controller (probably the Category controller) is to parse your text field and update the search terms in the database. If you want to require commas and spaces to separate fields, you could do:
#user.search_terms.where(category: #category).delete_all
params[:search_term][:query].split(", ").map{|x| x.gsub("\"", "")}.each do |term|
#user.search_terms.create({category: #category, query: term})
end
Front End
Personally though, I'd make the front end a bit less complicated to use, like either just require commas, no quotes, or just require spaces and quotes.
Search
For the grand finale, for the Stories to be displayed that have search terms in their heading:
Story.where(#user.search_terms.where(category: #category).pluck(:query).map { |term| "heading like '%#{term}%'" }.join(" OR "))
I would recommend using pg_search gem rather than trying to maintain complicated queries like this.
Note: I'm sure there are errors in this, since I wasn't able to actually create the entire app to answer your questions. I hope this helps you get started with what you actually need to do. I encourage you as you work through this to post questions that have some code.
References
Rails guides: choosing habtm or has many through
gem 'pg_search'
Stack Overflow: Search a database based on query

Can I have a one way HABTM relationship?

Say I have the model Item which has one Foo and many Bars.
Foo and Bar can be used as parameters when searching for Items and so Items can be searched like so:
www.example.com/search?foo=foovalue&bar[]=barvalue1&bar[]=barvalue2
I need to generate a Query object that is able to save these search parameters. I need the following relationships:
Query needs to access one Foo and many Bars.
One Foo can be accessed by many different Queries.
One Bar can be accessed by many different Queries.
Neither Bar nor Foo need to know anything about Query.
I have this relationship set up currently like so:
class Query < ActiveRecord::Base
belongs_to :foo
has_and_belongs_to_many :bars
...
end
Query also has a method which returns a hash like this: { foo: 'foovalue', bars: [ 'barvalue1', 'barvalue2' } which easily allows me to pass these values into a url helper and generate the search query.
This all works fine.
My question is whether this is the best way to set up this relationship. I haven't seen any other examples of one-way HABTM relationships so I think I may be doing something wrong here.
Is this an acceptable use of HABTM?
Functionally yes, but semantically no. Using HABTM in a "one-sided" fashion will achieve exactly what you want. The name HABTM does unfortunately insinuate a reciprocal relationship that isn't always the case. Similarly, belongs_to :foo makes little intuitive sense here.
Don't get caught up in the semantics of HABTM and the other association, instead just consider where your IDs need to sit in order to query the data appropriately and efficiently. Remember, efficiency considerations should above all account for your productivity.
I'll take the liberty to create a more concrete example than your foos and bars... say we have an engine that allows us to query whether certain ducks are present in a given pond, and we want to keep track of these queries.
Possibilities
You have three choices for storing the ducks in your Query records:
Join table
Native array of duck ids
Serialized array of duck ids
You've answered the join table use case yourself, and if it's true that "neither [Duck] nor [Pond] need to know anything about Query", using one-sided associations should cause you no problems. All you need to do is create a ducks_queries table and ActiveRecord will provide the rest. You could even opt to use has_many :through relationship if you need to do anything fancy.
At times arrays are more convenient than using join tables. You could store the data as a serialized integer array and add handlers for accessing the data similar to the following:
class Query
serialize :duck_ids
def ducks
transaction do
Duck.where(id: duck_ids)
end
end
end
If you have native array support in your database, you can do the same from within your DB. similar.
With Postgres' native array support, you could make a query as follows:
SELECT * FROM ducks WHERE id=ANY(
(SELECT duck_ids FROM queries WHERE id=1 LIMIT 1)::int[]
)
You can play with the above example on SQL Fiddle
Trade Offs
Join table:
Pros: Convention over configuration; You get all the Rails goodies (e.g. query.bars, query.bars=, query.bars.where()) out of the box
Cons: You've added complexity to your data layer (i.e. another table, more dense queries); makes little intuitive sense
Native array:
Pros: Semantically nice; you get all the DB's array-related goodies out of the box; potentially more performant
Cons: You'll have to roll your own Ruby/SQL or use an ActiveRecord extension such as postgres_ext; not DB agnostic; goodbye Rails goodies
Serialized array:
Pros: Semantically nice; DB agnostic
Cons: You'll have to roll your own Ruby; you'll loose the ability to make certain queries directly through your DB; serialization is icky; goodbye Rails goodies
At the end of the day, your use case makes all the difference. That aside, I'd say you should stick with your "one-sided" HABTM implementation: you'll lose a lot of Rails-given gifts otherwise.

Best way to structure database objects with multiple features and attributes in Rails

I have a product model. Each product has a different feature set, and has many features.
Instead of creating a product model that lists all of it's features (since this would involve including a lot of features I do not need, and when I needed to add new features it would be difficult) my thought was to create one column that stores a "features array". So, be it this product is a laptop, and I wanted to know the screen size, I could call:
#laptop.features[:screen]
=> "15.6 inch"
The problem with this that I am not sure there is a simple and practical way to build a form that could accept various features, then map them to the array.
I found a railscast (#196) that explains there is accepts_nested_attributes_for built into rails that would basically have using both a Product model and Feature model and just associate the two records.
Which way would be better? Is there a common approach for this sort of problem? And is there a way to have a form in your view that would accept features? (even if they are not directly apart of the Product model's database structure)
I would definitely go with a more flexible solution of having a has_many relationship with features. Then you can easily call #product.features to get the products features and the flexibility really shines when you want to do something like assign multiple attributes to screen. If you are throwing hashes into your database you wouldn't be able to add two attributes (easily anyways) to screen.
Say you wanted #product.features[:screen] to show IPS of TFT in the future as well as size, then you would have to have nested hashes or something else that would be really ugly to process.
Perhaps a Features table that contains the features that you want to mention for the products, probably with a type attribute. Maybe "Type: Display, Key: Size, Value: 1920x1080", or "Type: HDD, Key: Capacity, Value: 2GB". You can use the type to create 'families' of keys. You can make your keys anything you want to track, and the value is just a string.
With that Feature list built, you create a joining table/model (assignments?) that tracks which product has which features.
Product (id, non-feature attributes)
has_many assignments
has_many features, through assignments
Feature (id, type, key, value)
has_many assignments
has_many products, through assignments
Assignments (id, product_id, feature_id, timestamps?)
belongs_to product
belongs_to feature
Given that you're linking a product id to a feature id, you can fiddle with your feature value text without breaking anything. Decide that "1920x1080" should be "1920px x 1080px" -- just change the feature record.

Tables representing an enumerated list of codes in Rails?

I've looked through similar questions but I'm still a little perplexed about something that seems to be a simple case in Rails.
Let's say I have a model class called Employee. One attribute of an employee is their security_clearance, which is a string that can be None, Some, or Full. No other values are valid. In other applications I'd probably represent this an Employees table that has a foreign key to the SecurityClearances table, which has exactly three rows. The SecurityClearances table has columns entitled code (e.g. SEC_CLEARANCE_NONE, SEC_CLEARANCE_SOME, ...) and value ("None", "Some", "Full").
How do I want to do this in Rails? Do I want has_one :security_clearance on Employee and belongs_to :employee on SecurityClearance? That doesn't seem to be quite right.
It seems nonoptimal to type out the string literals of None, Some, and Full everywhere, especially since the values to be displayed could change (for example, perhaps the string for the Some code will be change to be low clearance instead).
Update:
Now that I think about this some more, don't I really just want a belongs_to :security_clearance on Employee? That would do the trick, right? Employees need to know what their security clearance levels are, but security clearance levels have no tie to a particular employee.
Take a look at this plugin: http://github.com/mlightner/enumerations_mixin/tree/master
It allows you to define this like has_enumerated :security_clearance, besides also caching the SecurityClearance model, etc.
Without the plugin, though, you're right about the relationships.
Also check out the Enum Fields plugin from the GiraffeSoft folks:
http://giraffesoft.ca/blog/2009/02/17/floss-week-day-2-the-enum-field-rails-plugin.html

Mapping non db data to rails models

I have an app with an Account model. Each Account belongs_to a sport, which I would usually have as a Sport model and in the DB. But as this isn't really something that will change and is not administered by the end users I thought that it might be better to put it as an integer column in the Account model and map to a hash using a class variable.
However, I need each sport to have many player_positions (which are specific to each sport). So I thought maybe I could do something like:
##player_positions = {:rugby => [position_1, ..., ...]}
Is this good practice for static data like this or should I stick to putting it in the DB as it is relational?
I also thought maybe I could use a yaml file but not sure how I could set that up.
I would stick in the database because you are relating more than one thing to the sport (positions in addition to Account). It makes life easier if you want to use belongs_to and it will allow you to easily use SQL for counts and such (e.g., you can group accounts by sport and get a count of accounts by sport).
I have used an enumerated list of constants in some cases as opposed to tables. For example, I had a simple app with a user type of ADMIN, EDITOR and READER. Rather than put that in a roles table in the DB, I just made those constants on the User class and added a string column to the users table for role.
You don't have to use the database. http://railscasts.com/episodes/189-embedded-association might give you ideas.

Resources