In rails application I have two models: Food and Drink.
Both food and drink have a name, which has to be stored in two languages.
How do I better realize translations for theese tables?
First solution I realized was to replace name column with name_en and name_ru.
Another solution is to encode with YAML hash like { :en => 'eng', :ru => 'rus' } and store yaml as a name.
What would you recommend, assuming content is not static?
Maybe there's good article?
The first option (name_en, name_ru) is the easiest to implement.
You could include a name method that returns the right value depending on the selected locale. You could even create a module, if you are going to use this on lots of models/fields:
class Food < ActiveRecord::Base
...
def name
self.send("name_#{I18n.locale}")
end
end
If in the future you must include an additional language, you will have to add a migration, of course. But that shoudn't be too troublesome.
The second one (encoding using YAML) seems a bit more cumbersome - you will not have to make an additional migration with it, but you loose other functionality. For example, search is made much more difficult - you can't use SQL any more for looking through descriptions, as they are coded on YAML instead of being plain text.
So I recommend having two fields.
Related
Tweet to tweets is no big deal.
And I assume Country would become countries without any fuss. I'd even credit them with Cactus becoming cacti (correct me if I'm wrong).
But what if I have class Barnabus? Do I call its table barnabuss, barnabi, or barnabusses?
What about zxzzy? How are non-english words handled?
Are there any non-messy options for overriding the decapitalization and pluralization?
I'm new to Rails and the modified table names have me a bit off-put.
Links to relevant documentation are appreciated. This is the only documentation I have seen relevant to this, and its not helpful for understanding odd exceptions for table names.
Forgive me if this is already covered, the results I got were not exactly what I'm trying to figure out.
String#pluralize uses a heuristic with the rather simple general rules for the English language as a base and adding some known common exceptions.
Using ActiveSupport::Inflector you can customize its behavior.
In Rails projects you can find the inflections config at config/initializers/inflections.rb.
If you'd like to create a Barnabus model with a corresponding barnabi table then all you have to do is uncomment some inflections.rb content and explicitly tell Rails how to treat this word.
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'barnabus', 'barnabi'
end
P.S. Thanks to Holger Just for the edit.
You can trust it for most of the cases but if you have some doubtful model name with awkward plural names, you can always specify the table name in Model. e.g.
class SomeName < ActiveRecord::Base
self.table_name = 'any_name'
end
If pluralization in views/error messages are your concern, in the situation that Rails fails magnificently with pluralizing a tricky table name you can add some localization to your app and tell it what you want it to use under various contexts. See http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
Here's an example of pluralization rules in action:
activerecord:
models:
barnabus:
zero: Barnabi
one: Barnabus
few: Barnabi
other: Barnabi
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 am making a simple retail commerce solution, where there are prices in a few different models. These prices contribute to a total price. Imagine paying $0.30 more for selecting a topping for your yogurt.
When I set the price field to
t.decimal :price, precision:8, scale:2
The database stores 6.50 as 6.5. I know in the standard rails way, you call number_to_currency(price) to get the formatted value in the Views. I need to programmatically call the price field as well formatted string, i.e. $6.50 a few places that are not directly part of the View. Also, my needs are simple (no currency conversion etc), I prefer to have the price formatted universally in the model without repeated calling number_to_currency in views.
Is there a good way I can modify my getter for price such that it always returns two decimal place with a dollar sign, i.e. $6.50 when it's called?
Thanks in advance.
UPDATE
Thanks everyone.
I've elected to use Alex's approach because it seems very 'hackish' to do the includes just for formatting the number. Using his approach, I did:
def price_change=(val)
write_attribute :price_change, val.to_s.gsub(/[\$]/,'').to_d
end
def price_change
"$%.2f" % self[:price_change]
end
Cheers.
UPDATE 2
Caveat Emptor. Once you do this, you lose the ability to do operations to the number because it's now a string.
Please beware if anyone is facing the same problem as me.
Just add a method in your model which is named like your attribute in the database like:
def price
"$%.2f" % self[:price]
end
which gives you full control over the formatting or use the Rails provided helper method
def price
ActionController::Base.helpers.number_to_currency(self[:price])
end
this should do the trick.
hope it helps!
You can use the helpers module, but you should not include the whole module, because it includes a lot of methods which you may not really need or overrides some of yours. But you can use them directly:
ActionController::Base.helpers.number_to_currency(6.5)
#=> "$6.50"
You could also define a method for the helpers, so you can easily use them.
def helpers
ActionController::Base.helpers
end
"#{helpers.number_to_currency(6.5)}"
Have a look at this railscast
I'd suggest going with a Presenter approach, like Draper (see this reailscast) does.
Another solution would be to implement your own method in your model, i.e. formatted_price and do the formatting on your own, (i.e. with the ActionView::Helpers::NumberHelper module). But since models represent the plain data in your rails application, it's kinda shady doing something like this and it interferes with the convention over configuration approach, I think.
try
include ActionView::Helpers::NumberHelper
to your model
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
Lets say you have a model like the following:
class Stock < ActiveRecord::Base
# Positions
BUY = 1
SELL = 2
end
And in that class as an attribute of type integer called 'position' that can hold any of the above values. What is the Rails best practice for converting those integer values into human readable strings?
a) Use a helper method, but then you're force to make sure that you keep the helper method and model in sync
def stock_position_to_s(position)
case position
when Stock::BUY
'buy'
when Stock::SELL
'sell'
end
''
end
b) Create a method in the model, which sort of breaks a clean MVC approach.
class Stock < ActiveRecord::Base
def position_as_string
...snip
end
end
c) A newer way using the new I18N stuff in Rails 2.2?
Just curious what other people are doing when they have an integer column in the database that needs to be output as a user friendly string.
Thanks,
Kenny
Sounds to me like something that belongs in the views as it is a presentation issue.
If it is used widely, then in a helper method for DRY purposes, and use I18N if you need it.
Try out something like this
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
end
It lets you to save position as an integer, as well as use select helpers easily.
Of course, views are still a problem. You might want to either use helpers or create position_name for this purpose method
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
def position_name
positions.index(position)
end
end
Is there a good reason for the app be converting the integer to the human readable string programmatically?
I would make the positions objects which have a position integer attribute and a name attribute.
Then you can just do
stock.position.name
#HermanD: I think it's a lot better to store the values in an integer column rather than a string column for numerous reasons.
It saves database space.
Easier/faster to index on an integer than a string.
Your not hard coding a human readable string as values in a database. (What happens if the client says that "Buy" should become "Purchase"? Now the UI shows "Purchase" everywhere but you need to keep setting "Buy" in the database.)
So, if you store certain values in the database as integers, then at some point, you're going to need to show them to the user as strings, and I think the only way you can do that is programatically.
You could move this info into another object but, IMHO, I'd say this is overkill. You'd then have to add another database table. Add another 'admin' section for adding, removing and renaming these values and so on. Not to mention that if you had several columns, in different models that needed this behavior, you'd either have to create lots of these objects (ex: stock_positions, stock_actions, transaction_kinds, etc...) or you'd have to design it generically enough to use polymorphic associations. Finally, if the position name is hard coded, then you lose the ability to easy localize it at a later date.
#frankodwyer: I'd have to agree that using a helper method is probably the best way to go. I was hoping their might be a "slicker" way to do this, but it doesn't look like it. For now, I think the best method is to create a new helper module, maybe something like StringsHelper, and stuff a bunch of methods in their for converting model constants to strings. That way I can use all the I18N stuff in the helper to pull out the localized string if I need to in the future. The annoying part is that if someone needs to add a new value to the models column, then they will also have to add a check for that in the helper. Not 100% DRY, but I guess "close enough"...
Thanks to both of you for the input.
Kenny
Why not use the properties of a native data structure? example:
class Stock < ActiveRecord::Base
ACTIONS = [nil,'buy','sell']
end
Then you could grab them using Stock::ACTIONS[1] #=> 'buy' or Stock::ACTIONS[2] #=> 'sell'
or, you could use a hash {:buy => 1, :sell => 2} and access it as Stock::ACTIONS[:buy] #=> 1
you get the idea.
#Derek P. That's the implementation I first went with and while it definitely works, it sort of breaks the MVC metaphor because the model, now has view related info defined in its class. Strings in controllers are one thing, but strings in models (in my opinion) are definitely against the spirit of clean MVC.
It also doesn't really work if you want to start localizing, so while it was the method I originally used, I don't think it's the method for future development (and definitely not in an I18N world.)
Thanks for the input though.
Sincerely,
Kenny
I wrote a plugin that may help a while ago. See this. It lets you define lists and gives you nice methods ending in _str for display purposes.