I'm trying to figure out the "best" place to put multi-attribute search form logic in a Rails application. The search form in question has several attributes which may or may not have values, and the data types differ between attributes. (For example, there are search options to search for items with a price attribute between two numbers, date ranges, string values, etc.) Also, the model in question has several nested attributes through has_a/has_many relationships and some of those attributes also need to be searchable.
The Rails mantra of thick model, thin controller makes me hesitant to try to aggregate the search logic into the controller. However, it also doesn't seem appropriate to put logic relating to constructing the search conditions in the model(s). Finally, in the spirit of DRY, I'm hesitant to hard-code a bunch of specific attribute names into some module since I will need to apply similar search logic to several unrelated models. Perhaps a naming convention of the form fields in the search view could be used to construct the right conditions? (Something like using prefixes like "min_", "max_", "startdate_" indicating the data type and search condition operator and the suffix being the name of the model and/or attribute?)
I've searched for advice on this, but most of the advice seems inflexible (hardcoded attribute names, no support for nested attributes) or to use route-based searches which I don't think will work for my need (where 5-10 parameters may be used in a search at once).
Any suggestions on the "Rails way" of doing this?
If you are using 2.3.x, I've always found Searchlogic to be a solid and flexible starting point for searching ActiveRecord models where you don't need fulltext search. It supports associations, your own named scopes, etc.
If you are using Rails 3, the meta_search gem appears to offer similar functionality, but my experience with it is extremely limited.
Related
We have to create a request system which will have roughly 10 different types of requests. All of these requests will belong to the 'accounting' aspect of our application. Therefore we've called them "Accounting requests".
All requests share maybe only a few columns and each has up to 20 columns individually.
We started to wonder if having separate tables for each request type would be practical in terms of speed when we start to have to do very complicated joins or queries, for example, fetching ALL requests types into a single table and then sorting it.
Maybe it would be easier to just use Single Table Inheritance since it will have a type column and we'd be using one table to store all 10 accounting request types.
What do you think regarding using STI for this many polymorphic associations and requirements?
Essentially, it would have models like so:
AccountingRequest
BillingRequest < AccountingRequest
CheckRequest < AccountingRequest
CancellationRequest < AccountingRequest
Each subclass has roughly 10+ fields.
Currently reading about Multiple Table Inheritance here. This seems like the solution that fits my requirements in this case. Not sure yet though.
STI is a good fit if your models all share the same attributes.
However if your sub classes start having attributes specific to them and not applicable to others, then STI can result in a lot of null columns. In that case, I usually prefer to go with polymorphic association.
This railscast episode is a great example of the difference between the 2
You can use STI in that situation. But making STI will require all the columns into one single table and that's not the good think. The table will go very large in the number of fields.
I think you should divide into two tables like as below...
Request: A request table will be the polymorphic table which saved the information for the type of requests.
RequestItem: The request item table will save all the 20 fields records into the table and will have a foreign key of request table. The request item table will have two fields into the database that's called key and value.
It sounds do-able.
When I've looked into this, I found that making extensive use of value objects helped to control the non-applicability of some attributes to some of the types.
In my case I had types of products, some of which would not have particular measurements for example. In those cases I used a Null Object to indicate "Not applicable" where appropriate.
Edit: I also found the composed_of syntax very convenient: https://apidock.com/rails/ActiveRecord/Aggregations/ClassMethods/composed_of
For now I'm using a bit of NoSQL for such cases. Postgresql's JSONB type allows to store multilevel ruby hash. It also provides rich functionality: DB level constraints, indexes and query operators.
So common attributes are stored in standard way and child specific - in jsonb. Then you can use whatever you need on top of this: STI, Value Objects pattern, serialization or just create scopes for each child. I prefer the last one - my models are thin, most of constraints are DB level and all business logic is in service classes.
Pros:
Avoiding alter table on big tables when need to add one more child type
Keeping my queries efficient
Preventing storing and selecting unnecessary columns
Serialization out of the box for JSON APIs
Cons:
A bit of schemaless
Vendor lock
I'm working on a Rails 5 app for Guild Wars 2, and I'm trying to figure out a way to serialize and store all of the items in the game without duplicating code or table columns. The game has a public API to get the items from, documented
here: https://wiki.guildwars2.com/wiki/API:2/items
As you can see, all of the items share several pieces of data like ID, value, rarity, etc. but then also branch off into specific details based on their type.
I've been searching around for a solution, and I've found a few answers, but none that work for this specific situation.
Single Table Inheritance: There's way too much variance between items. STI would likely end up with a table over 100 columns wide, with most of them null.
Polymorphic Associations: Really doesn't seem to be the proper way to use these. I'm not trying to create a type of model that gets included multiple other places, I just want to extend the data of my "Item" model.
Multiple Table Inheritance: This looks to me like the perfect solution. It would do exactly what I'm wanting. Unfortunately, ActiveRecord does not support this, and all of the "workarounds" I've found seem hacky and weird.
Basically, what I'm wanting is a single "Item" model with the common columns, then a "details" attribute that will fetch the type-specific data from the relevant table.
What's the best way to create this schema?
One possible solution:
Use #serialize on the details (text) column
class Item
serialize :details, Hash
end
One huge downside is that this is very inefficient if you need to query on the details data. This essentially bypasses the native abstractions of the database.
I was in a similar situation recently. I solved by using Sequel instead of ActiveRecords.
You can find it here:
https://github.com/TalentBox/sequel-rails
And an implmentation example:
http://www.matchingnotes.com/class-table-inheritance-in-rails.html
Good luck
In a recruitment applicaiton, I have a model 'Job'
The Admin is able to create new Jobs.
As part of the description for the job, it will have around 4-5 bullet points summarizing it.
It will also have all the roles in the job, and the descriptions for it.
These are only meant to be created for showing purposes, not for filtering, etc.
Here's the question I'm having though, how would you let the user create these bulleted lists, and roles?
I can create data in rails like
bullet_list_1:string
bullet_list_2:string
role_1_title:string
role_1_dsc:string
etc...
However, if I do it this way, then on particular jobs there may be more bullet points, and on other jobs less bullet points.
How would you handle this situation?
I am thinking a has_many relationship might do it, but if there's an even better solution, would be all up for it!
My conclusion of features based on your question:
It must be flexible to allow any number of bullets, as well as other fields.
It must allow easy creation and showing(as you've mentioned), as well as editing - showing the same as creation.
Technology candidates:
ActiveRecord relationship(has_many)
This would be filtered out. There is no way to apply that because the fields are uncertain.
Text parsing by Markdown
This would fill basic needs. You can also show creation form for each fields and combine them to markdown when saving. However, for editing, it is easier to edit raw markdown instead of converting it back to the form like creation. Also, it will be less flexible when you want to customizing the view output, say showing part of them.
Documentation db like MongoDB
This would be a perfect fit. All bullets and other information can be saved in a key/value pair.
ActiveRecord alternative on documentation
If you are using PostgreSQL, you can consider hstore(for one level key/value) or json(for nested level key/value) data field. If not PostgreSQL, you can use a text field and serialize it as Hash or JSON, with less performance.
Conclusion:
If the "Job" model is the core business, I will consider MongoDB. If there are any other limitations, I will consider PostgreSQL hstore or JSON, and finally serialized text field.
I'm doing a fairly complicated model on Ruby on Rails right now, and I was wondering what the correct "Rails" way of doing "custom types" for attributes was. For example, I have a table businesses which has a string attribute region. But region can only be one of a predefined list of possibilities (that could later be expanded). My question is: where do I define this Region type?
I know I could make a specific regions table (i.e. a Region model) which could house all the options, and then I could make an association between the models that have regions to that table. The problem is that I have many of these types on my model, so I would end up with more than half the tables in my database being "custom type tables" that only store the possible values for these types. Is that practical?
I also read that you could do this through validations (i.e. validate when saving a record, that the variables were within the possible values). This seems very impractical, since I want to make this model expandable, and form views would need to load the possible values of types into select boxes, etc. If I used this method, every time I needed to add a new possible value for a type, I'd have to change the validation and the views.
Is there a standard way of doing something like this? Something like defining types (maybe models without DB backing?) where I could list all the possible values easily?
Thank you for any help or suggestions on this. It's been bothering me for a long time while doing RoR apps, and I'm tired of hacking around it.
I guess there are many different ways to do it. Personally I would keep things very simple and DRY.
In an initializer, set arrays in the global scope:
REGIONS = ["region A", "region B", "region C"]
In the models, use validations as you wrote. Check that the value is in the REGIONS array.
In the views, use Rails helpers to populate selects, radios etc. from the REGIONS array. If you always have the same select, write your own helper region_select for instance.
So this is probably a fairly easy question to answer but here goes anyway.
I want to have this view, say media_objects/ that shows a list of media objects. Easy enough, right? However, I want the list of media objects to be a collection of things that are subtypes of MediaObject, CDMediaObject, DVDMediaObject, for example. Each of these subtypes needs to be represented with a db table for specific set of metadata that is not entirely common across the subtypes.
My first pass at this was to create a model for each of the subtypes, alter the MediaObject to be smart enough to join into those tables on it's conceptual 'all' behavior. This seems straightforward enough but I end up doing a lot of little things that feel not so rails-O-rific so I wanted to ask for advice here.
I don't have any concrete code for this example yet, obviously, but if you have questions I'll gladly edit this question to provide that information...
thanks!
Creating a model for each sub-type is the way to go, but what you're talking about is multiple-table inheritance. Rails assumes single-table inheritance and provides really easy support for setting it up. Add a type column to your media_objects table, and add all the columns for each of the specific types of MediaObject to the table. Then make each of your models a sub-class of MediaObject:
class MediaObject < ActiveRecord::Base
end
class CDMediaObject < MediaObject
end
Rails will handle pulling the records out and instantiating the correct subclass, so that when you MediaObject.find(:all) the results will contain a mixture of instances of the various subclasses of MediaObject.
Note this doesn't meet your requirement:
Each of these subtypes needs to be represented with a db table for specific set of metadata that is not entirely common across the subtypes.
Rails is all about convention-over-configuration, and it will make your life very easy if you write your application to it's strengths rather than expecting Rails to adapt to your requirements. Yes, STI will waste space leaving some columns unpopulated for every record. Should you care? Probably not; database storage is cheap, and extra columns won't affect lookup performance if your important columns have indexes on them.
That said, you can setup something very close to multiple-table inheritance, but you probably shouldn't.
I know this question is pretty old but just putting down my thoughts, if somebody lands up here.
In case the DB is postgres, I would suggest use STI along hstore column for storing attributes not common across different objects. This will avoid wasting space in DB yet the attributes can be accessed for different operations.
I would say, it depends on your data: For example, if the differences between the specific media objects do not have to be searchable, you could use a single db table with a TEXT column, say "additional_attributes". With rails, you could then serialize arbitrary data into that column.
If you can't go with that, you could have a general table "media_objects" which "has one :dataset". Within the dataset, you could then store the specifics between CDMediaObject, DVDMediaObject, etc.
A completely different approach would be to go with MongoDB (instead of MySQL) which is a document store. Each document can have a completely different form. The entire document tree is also searchable.