I'm creating an application consisting of a bunch of entries. These entries are going to have a bunch of fields (e.g. category, name, description etc.) and be of a certain type (category). So the user would first create a category with a title and description and then define what other fields an entry in that category can and should have.
Example:
Create category, title => 'Books', description => 'A description'. Defining extra fields, author (required), image (not required).
Create entry, when choosing category => 'Books' the form is regenerated and the fields for author and image are shown with validation defined in the category.
I hope somebody understands..
I was talking to a friend about this who recommended going for MongoDB in order to implement this, now I have an app installed with Mongoid and everything works just fine.
The question is, how would I implement this in the best way, making it as flexible as possible?
it's hard to answer to your because it is quite vague… here is what I can say about MongoDB:
MongoDB is already as flexible as possible (that is even its problem actually).
The problem is more likely to sometime restricts its flexibility i.e. check access rights, check that your jSON you are storing is in the right scheme and so on.
If your db is not too huge and you do not want to bother with many collections, you can store all your Books items (documents) (or even a document containing lists) into the same collection.
Related
Can fields in different but associated tables have the same names is Rails 4 and be distinct. For example, if I have a class Shipping and a class Receiving, where each has the field EnterTrackingNo, and they are associated via a one to one association on the field shipping_id, will there be any issues with this setup / the fields somehow overlap / interfere with one another?
Thanks
There will not be any issue as Rails will automatically add the table name to the SQL queries it builds when it needs to. You'll be able to access the attribute easily as either shipping.EnterTrackingNo, receiving.EnterTrackingNo, shipping.receiving.EnterTrackingNo, receiving.shipping.EnterTrackingNo, etc. and Rails knows which table you're talking about due to the way they're written.
Even when you search for an object, say you want to search for all Shippings with a Receiving item that has EnterTrackingNo == 3 you'd do
Shipping.includes(:receiving).where(receiving: { EnterTrackingNo: 3 })
The only thing to keep in mind is that if you use SQL fragments (writing a where as a String, for example) you MUST write it as table_name.attribute, otherwise you'll get a SQLException: ambiguous column name. For example:
Shipping.includes(:receiving).where("EnterTrackingNo = 3").references(:receivings)
would not work as Rails, and your DB, have no way of knowing WHICH EnterTrackingNo you're talking about. You'd have to write it as:
Shipping.includes(:receiving).where("receivings.EnterTrackingNo = 3").references(:receivings)
So they know you want the Receiving model's attribute.
You'll also notice I add references(:table_name) to the ones with SQL fragments. This is necesary as well since Rails can't tell it needs a join when you just give it a String.
I'm looking for some guidance on how to implement what I am referring to as "virtual associations" into my model in rails.
I'll preface this with the disclaimer that I have considered going down the real associations path (HABTM) but as far as I can see this would conflict with concepts that I already have implemented.
I currently have a Project model which can be associated with User objects via roles.
As an example, a Project may have many site_managers, construction_managers and project_managers.
A site_manager can also be a project_manager for the same or different projects (rules out single table inheritance).
With the Rolify gem this is fairly straightforward to implement. I can assign any of the roles above to a particular user in a s pecific project with sample_user.add_role(:site_manager, sample_project).
One of my goals is to be able to create a form where I can setup a new project, and assign users to roles from using a multi-select list. So as an example, my form would have the following input to assign selected users as site manager for the new project:
= f.input :site_managers, collection: User.all, input_html: { multiple: true } (Formtastic DSL)
This is where things get slightly complicated. I have managed to implement a custom getter/setter for the site_maanagers attribute where I can take a hash of user_ids passed by the form and fetch/update the appropriate records as needed.
However this implementation is far from being similar to that of a real association, where I could do things like adding a single user to the site_managers with sample_project.site_managers << sample_user.
At the moment I am also unable to set the array of site_managers using user instances. My custom setter only takes user_ids as the argument which is a bit cumbersome and not very intuitive when used outside of a form submission implementation. I can easily work around this by checking types inside the setter method but it feel hackery and not very Rails like.
I've tried ditching the whole custom getter/setter and going with a HABTM implementation that uses a join table to manage all these records but I am concerned that this won't scale well if/when we need to add more roles to the project (each role adds an extra column to the join table). It also ends up feeling like I am duplicating functionality/concepts that are already offered with Rolify so in some places I am checking for roles in a join table and in using Rolify in others (i.e. if a user is an admin or has access to a certain resource).
Is there something else I may have overlooked or this the only way of getting this done?
Thanks and I look forward to hearing some of your opinions.
Rog
I want to save settings for my users and some of them would be one out of a predefined list! Using https://github.com/ledermann/rails-settings ATM.
The setting for f.e. weight_unit would be out of [:kg, :lb].
I don't really want to hardcode that stuff into controller or view code.
It's kind of a common functionality, so I was wondering: Did anyone come up with some way of abstracting that business into class constants or the database in a DRY fashion?
Usually, when I have to store some not important information which I don't care to query individually, I store them on a serialized column.
In your case you could create a new column in your users table (for example call it "settings").
After that you add to user model
serialize :settings, Hash
from this moment you can put whatever you like into settings, for example
user.settings = {:weight_unit => :kg, :other_setting1 => 'foo', :other_setting2 => 'bar'}
and saving with user.save you will get, in settings column, the serialized data.
Rails does also de-serialize it so after fetching a user's record, calling user.settings, you will get all saved settings for the user.
To get more information on serialize() refer to docs: http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
UPDATE1
To ensure that settings are in the predefined list you can use validations on your user model.
UPDATE2
Usually, if there are some pre-defined values it's a good habit to store them in a constant inside the related model, in this way you have access to them from model (inside and outside). Acceptable values does not change by instance so it makes sense to share them between all. An example which is more valuable than any word. Defining in your User model:
ALLOWED_SETTINGS = {:weight_unit => [:kg, :lb],
:eyes_color => [:green, :blue, :brows, :black],
:hair_length => [:short, :long]}
you can use it BOTH
outside the model itself, doing
User::ALLOWED_SETTINGS
inside your model (in validations, instance methods or wherever you want) using:
ALLOWED_SETTINGS
Based on your question, it sounds like these are more configuration options that a particular user will choose from that may be quite static, rather than dynamic in nature in that the options can change over time. For example, I doubt you'll be adding various other weight_units other than :kg and :lb, but it's possible I'm misreading your question.
If I am reading this correctly, I would recommend (and have used) a yml file in the config/ directory for values such as this. The yml file is accessible app wide and all your "settings" could live in one file. These could then be loaded into your models as constants, and serialized as #SDp suggests. However, I tend to err on the side of caution, especially when thinking that perhaps these "common values" may want to be queried some day, so I would prefer to have each of these as a column on a table rather than a single serialized value. The overhead isn't that much more, and you would gain a lot of additional built-in benefits from Rails having them be individual columns.
That said, I have personally used hstore with Postgres with great success, doing just what you are describing. However, the reason I chose to use an hstore over individual columns was because I was storing multiple different demographics, in which all of the demographics could change over time (e.g. some keys could be added, and more importantly, some keys could be removed.) It sounds like in your case it's highly unlikely you'll be removing keys as these are basic traits, but again, I could be wrong.
TL;DR - I feel that unless you have a compelling reason (such as regularly adding and/or removing keys/settings), these should be individual columns on a database table. If you strongly feel these should be stored in the database serialized, and you're using Postgres, check out hstore.
If you are using PostgreSQL, I think you can watch to HStore with Rails 4 + this gem https://github.com/devmynd/hstore_accessor
I'm creating an application where products will be created by my customer (something like an e-commerce website), so I obviously require translated descriptions stored in database, I can't force my customer to learn git/yml.
I have two ideas on how to correctly localize descriptions (and eventually product name) and store them in database, but if there is a well-known approach that I should use, I would be really happy to know it.
The first idea seems the most logical for me, but I would like to make it "transparent" to me, I don't want to write joins everywhere, so some suggestion on how to achieve this, if it's the correct one, would be appreciated.
Idea 1:
Create a database table products (with name and description field set maybe to the default locale language), then a products_translations table which contains a table structured in this way:
products_translations
- id
- locale
- product_id
- name
- description
As an example: product_translation: { id: 1, locale: 'en', product_id: 3, name: 'toy', description: 'play' }
But I want to access to translations without the requirement to write a lot of IFs everywhere. So if I write product.name it should return en or it based on current locale.
Bonus: Are there any gems that can help me to achieve this?
Idea 2: The other idea is to have a table with name_locale1, name_it and so on, but I don't like this approach because will pollute my model objects with fields and I will have a giant table.
However, in this way I can avoid join on every query for that object.
If there is a greater approach which I don't know about (a database pattern or similar), it's ok that too, I'm not forced to strict to only these two ideas, however I have to choose between the two and I really don't know which could be better.
Important: I would like to keep translations stored in yml files, except for dynamic contents, which obviously require translations in database.
I agree with PinnyM that the first approach is the better of the two, but rather than implement your own schema, I would highly recommend you implement Globalize3 where most of the structural decisions have been taken for you (and by Mr Fuchs himself, no less). Also, with the rails helpers, you just call something like product.name on a model instance and the gem will figure out how to display it in the correct locale, awesome!
The first approach is the recommended one. As you surmised, the second approach is not as clean and requires more work on the coding end with no real gain since you still have to join on this monster table. To the contrary, the first method requires at most one join, while the second approach requires a join on each attribute you may want to add localization support.
You can simply append a scope on all your product calls such as:
scope :for_locale, lambda{|locale| joins(:product_translations).
where(product_translations: {locale: locale || 'en'}) }
and pass in the session locale (or wherever you are storing it).
I'm building a multilang application that has two essential models Category and Product, where a Category has many Products..
So I want the ability to display the same categories with more than a language, for example, consider a category called Cars, it should be presented as Vehicules for a user using the french version of the application.
How could I do that? Should I store them in different models? or should I add a lang column in the Category model ?
What I thought of doing is adding a lang column in the Category model and add a default_scope call to scope it to search for only the desired language, I have two questions though:
How can I get the used language from inside a model, an I18n call ? Which method should I call on it ?
A problem arises from using this technique, a product which references a category in french wouldn't show up in a search under the category in english, how can I resolve this issue ?
Thank you
The key question you have to ask yourself is whether it's important that the Cars category should be the same object (implying the same object and the same URL) as the Vehicules category or not.
If they should be the same category, then the only question is how you translate the name into different languages. If you have a relatively small number of languages to support, you could simply store them all on the model using different columns (name_en, name_fr, etc).
Or you could store the translated names separately, such as using the I18n modules.
Alternatively, if Cars and Vehicules are separate categories, then you could follow your suggestion of adding a lang attribute to the model.
Frankie Roberto gave you a good answer. I will just add some additional info.
First the easy part: getting current locale in a model.
If you set the language in a controller as I18n.locale = something, then you may read it in a model the same way. The I18n is a global constant, after all.
Now about searching:
I generally prefer the designs where you have one category, but the name of the category is translated to many languages. The design where one car belongs to category "Cars" and another one belongs to "Vehicules" is flawed IMHO. In the later case there is no sense of making it a single site - you could create a one-language application, and install it in multiple instances, where every one is translated to a single language.
If you have a short, static list of supported languages, you may add a column for every language: name_en, name_fr, name_pl.
If your list of languages will be large or unknown at the moment of designing your application, it would be easier to store the "name" (given as an example) as a serialized hash. So, you could get the intended translation as name["fr"], name["de"] and so on.
I have already learned (the hard way :-) ) that users usually are too lazy to provide all the translations (or they just do not know all the languages), so you should be prepared that some of your models will not have the data in a language you are trying to display or search.
For every 'translated' attribute you may want to prepare a method which will supply you with the most appropriate translation in case the required one is missing.
This method may work in a way similar to:
def translated_name
([I18n.locale] + other_languages).each do |l|
return name[l] unless name[l].blank?
end
return "" # or some default value - possibly from I18n.t("some.static.default")
end
Of course, you do not need to use I18n.locale, and you should define other_languages in a way which would (ideally) match user language preferences, possibly by analyzing the "Accept-Language" header.
I sometimes use the method name name_for(lang) or name_in(lang), if I have to support more languages for data than for interface (which I shall translate and put in I18n config files).
Ah, yes - searching. :)
If you have defined the "names" as separate attributes, you may just search the appropriate column.
If you have the "names" as a single, serialized hash, you may search the column as a simple text (however I am not sure whether YAML will not mangle the non-ASCII characters) or create another column with searchable data, and then (at the model-level, not at the SQL level) partition the models into groups: "The search string has been found in your language", "The search string has been found in some other language". I believe the user of multi-lang service will be happy with search results presented in this way.
i'm using this gem to solve this kind of problem: https://github.com/jo/puret