Translations for each model - ruby-on-rails

In my domain, many models have names, descriptions, etc. These properties need translations. I know how to represent this in a database. However I struggle finding a way to represent this with Rails.
|-------translations-table--------|
|translation_id|locale|translation|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|----------------------modelx-table---------------------|
|id|name_translation_id|description_translation_id|price|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|-------modely-table--------|
|id|name_translation_id|date|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

You do not need to create extra models for translations, yo just need to set up locales in .yml format, check this for further instructions
Update
Ok now I understood your point, you want to add translatable fields on your entities/models, so users can manage those translations through a UI right?, well your approach is correct, however there is a gem called Globalize that does the exact same thing but with more toys, and much more standardized as you want.

This is the solution I eventually came up with:
#Models
class Translation
has_many :translation_records
end
class TranslationRecord
(translation_records.find_by :locale => I18n.locale).text
end
class ModelX
belongs_to :name_translation, :class_name => 'Translation'
belongs_to :description_translation, :class_name => 'Translation'
def name
name_translation.current
end
def description
description_translation.current
end
end
#Migrations
class CreateTranslationRecords < ActiveRecord::Migration[5.0]
def change
create_table :translation_records do |t|
t.references :translation
t.string :locale
t.string :text
end
add_index :translation_records, :locale
end
end
class CreateTranslation < ActiveRecord::Migration[5.0]
def change
create_table :translations do |t|
# only id column
end
end
end
class AddTranslationToModelXs < ActiveRecord::Migration[5.0]
def change
add_reference :model_xs, :name_translation
add_reference :model_xs, :description_translation
end
end

Related

RAILS - CHANGE FIELD OF ANOTHER TABLE - BOOLEAN

Good afternoon. I'm new to rails and I'm using google translate to post in English here, so sorry if it's not very readable.
My question is, I have a User table, and a Setting table.
They are related (but I don't know if the relationship is correct), they can even confirm me, and I would like to know if:
when creating a user, I would like to automatically change the "email" and "push" fields of that user's settings table to true.
Would it be possible via a method that in the user model called: "setting_default"?
User model.
class User < ApplicationRecord
has_one :setting
before_save :setting_default
def setting_default
self.setting.update(:email, 'true')
self.setting.update(:push, 'true')
end
Setting Model
class Setting < ApplicationRecord
has_one :user
end
The Controller is normal, if you need it, I can put it in the post
My migration:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
class AddSettingsToUser < ActiveRecord::Migration[6.0]
def change
add_reference :users, :setting, null: true, foreign_key: true
end
end
Google translate has worked well for you here.
First off you'll want to change your Setting model to belong to the User:
class Setting < ApplicationRecord
belongs_to :user
end
Your settings DB table is missing a user_id field to tie the setting back to the user. I'm not used to the add_reference technique so I just do things myself in the migrations. This would work:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.integer :user_id
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
(Make note that your users DB table has a field setting_id that it does not need. I don't think it should be there. I would remove it. Unless it's a Rails 6 thing I'm not used to.)
Next it would probably be better to assign the values if the save succeeds (and not if it fails) so you'll want an after_save instead. And I'm simplifying your value assignment just in case you're having an issue there:
class User < ApplicationRecord
has_one :setting
after_save :setting_default
def setting_default
setting.email = true
setting.push = true
setting.save(validate: false)
end
private :setting_default
And to answer what seems to be your question, yes, what you're trying to do should be easily possible. This is a very common thing to do. It should work.
When you use one-to-one association you need to choose has_one in one and belongs_to in another model
Semantically user has one setting, but not setting has one user
So it's better to reverse them
To change your schema you need to write new migration
class ChangeOneToOneDirection < ActiveRecord::Migration[6.0]
def up
change_table :settings do |t|
t.belongs_to :user, foreign_key: true, null: false
end
User.where.not(setting_id: nil).find_each |user|
Setting.find(user.setting_id).update_columns(user_id: user.id)
end
change_table :users do |t|
t.remove :setting_id
end
end
def down
add_reference :users, :setting, null: true, foreign_key: true
Setting.find_each do |setting|
User.find(setting.user_id).update_columns(setting_id: setting.id)
end
change_table :settings do |t|
t.remove :user_id
end
end
end
After migration you can change User model
class User < ApplicationRecord
has_one :setting
after_commit :setting_default
private
def setting_default
setting&.update(email: true, push: true)
end
end
It's better to update associated model only if saves are in the database. And user can haven't setting. That's why after_commit and safe-navigator &

Rails / Papertrail: Changeset with association changes

I am stuck. I've been trying to figure out how to include the association changes (has_many, has_many through) on a model that has papertrail. I would like to call MyModel.versions.first.changeset and have any changes that took place on associated objects be included in the .changeset hash that is returned from that version of the object.
I've added the migrations for version associations:
class CreateVersionAssociations < ActiveRecord::Migration
def self.up
create_table :version_associations do |t|
t.integer :version_id
t.string :foreign_key_name, :null => false
t.integer :foreign_key_id
end
add_index :version_associations, [:version_id]
add_index :version_associations, [:foreign_key_name, :foreign_key_id], :name => 'index_version_associations_on_foreign_key'
end
def self.down
remove_index :version_associations, [:version_id]
remove_index :version_associations, :name => 'index_version_associations_on_foreign_key'
drop_table :version_associations
end
end
class AddTransactionIdColumnToVersions < ActiveRecord::Migration
def self.up
add_column :versions, :transaction_id, :integer
add_index :versions, [:transaction_id]
end
def self.down
remove_index :versions, [:transaction_id]
remove_column :versions, :transaction_id
end
end
I have added Papertrail to the associated objects, but as far as I can tell, there is no documentation discussing retrieving changes that took place on the associated objects. Can anyone assist on if this is possible using Papertrail?
I am trying to implement an audit trail of changes on a model and its associated objects that can be accessed in one changeset.
The information you need is ultimately stored in the relevant tables versions and version_associations.
However, paper_trail does not provide the methods for you to access the information in the way you want. But you can write a custom method yourself to get a list of the associations's versions of an object.
Let's say you have the following models:
class Article < ApplicationRecord
has_many :comments
has_paper_trail
end
class Comment < ApplicationRecord
belongs_to :article
has_paper_trail
end
You can find all the comment versions of an article object article this way:
PaperTrail::Version.where(item_type: 'Comment')
.joins(:version_associations)
.where(version_associations: { foreign_key_name: 'article_id', foreign_key_id: article.id })
.order('versions.created_at desc')
You can monkey patch the gem, or define this method as an instance method on the Article class so you can call it easily, eg article.comment_versions
Note, the above information isn't available in the article.versions.first.changeset, because if you change a comment but not the article, the article is not versioned, only the comment is.
But the method above allows you to access the history of changes to the associations.
Looks like this has been added as an experimental feature to the papertrail gem
check out the docs here
https://github.com/airblade/paper_trail/blob/v4.2.0/README.md#associations
This change will require the addition of a new database table for papertrail to keep track of associated models.

Spree Relation Issue

I'm trying to add product attachment functionality to a Spree store. E.g. a product has many attached documents: brochures, instruction manuals, etc. I can't get the relationship between documents and products to work.
I can use the Paperclip gem for the attachment functionality since Spree already uses it for images.
I have the "document" model: models/spree/document.rb:
class Spree::Document < ActiveRecord::Base
belongs_to :products, class_name: "Spree::Product"
has_attached_file :pdf
end
Then I try to relate the document model to the Spree::Product model in models/spree/product_decorator.rb:
Spree::Product.class_eval do
has_many :documents, dependent: :destroy
end
Then I add migrations:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :spree_documents do |t|
t.timestamps
end
end
end
class AddPdfToDocuments < ActiveRecord::Migration
def self.up
add_attachment :spree_documents, :pdf
end
def self.down
remove_attachment :spree_documents, :pdf
end
end
Now I go into the rails console to see if it worked:
#=> prod = Spree::Product.first
#=> prod.document
#=> PG::UndefinedColumn: ERROR: column spree_documents.product_id does not exist
#=> LINE 1: ..."spree_documents".* FROM "spree_documents" WHERE "spree_doc...
^
#=> : SELECT "spree_documents".* FROM "spree_documents" WHERE "spree_documents"."product_id" = $1
Seems like I'm not defining the relationship between documents and products correctly, but I'm not sure what the issue is.
It looks like you never added a product_id column to your Spree::Documents table. When you define a model belongs_to another model, it tells ActiveRecord that the first one will be a [relation]_id column in its table.
You just need to make sure to add t.references :product in your migration, so it'd look like:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :spree_documents do |t|
t.references :product
t.timestamps
end
end
end

multiple languages models

I've been asked to design a multiple language application and I need advice with which is the best approach with Rails.
Basically all the tables have some common fields that doesn't need to
be translated and some others that need translation.
thank you
For this purpose, will approach gem globalize3. Easy to use.
In your gemfile:
gem 'globalize'
Model:
class Article < ActiveRecord::Base
translates :title, :text
end
And migration:
class CreateArticles < ActiveRecord::Migration
def up
create_table :articles do |t|
t.timestamps
end
Article.create_translation_table! :title => :string, :text => :text
end
def down
drop_table :articles
Article.drop_translation_table!
end
end
And run
rake db:migrate

Scaffolding ActiveRecord: two columns of the same data type

Another basic Rails question:
I have a database table that needs to contain references to exactly two different records of a specific data type.
Hypothetical example: I'm making a video game database. I have a table for "Companies." I want to have exactly one developer and exactly one publisher for each "Videogame" entry.
I know that if I want to have one company, I can just do something like:
script/generate Videogame company:references
But I need to have both companies. I'd rather not use a join table, as there can only be exactly two of the given data type, and I need them to be distinct.
It seems like the answer should be pretty obvious, but I can't find it anywhere on the Internet.
Just to tidy things up a bit, in your migration you can now also do:
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
And since you're calling the keys developer_id and publisher_id, the model should probably be:
belongs_to :developer, :class_name => "Company"
belongs_to :publisher, :class_name => "Company"
It's not a major problem, but I find that as the number of associations with extra arguments get added, the less clear things become, so it's best to stick to the defaults whenever possible.
I have no idea how to do this with script/generate.
The underlying idea is easier to show without using script/generate anyway. You want two fields in your videogames table/model that hold the foreign keys to the companies table/model.
I'll show you what I think the code would look like, but I haven't tested it, so I could be wrong.
Your migration file has:
create_table :videogames do |t|
# all your other fields
t.int :developer_id
t.int :publisher_id
end
Then in your model:
belongs_to :developer, class_name: "Company", foreign_key: "developer_id"
belongs_to :publisher, class_name: "Company", foreign_key: "publisher_id"
You also mention wanting the two companies to be distinct, which you could handle in a validation in the model that checks that developer_id != publisher_id.
If there are any methods or validation you want specific to a certain company type, you could sub class the company model. This employs a technique called single table inheritance. For more information check out this article: http://wiki.rubyonrails.org/rails/pages/singletableinheritance
You would then have:
#db/migrate/###_create_companies
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :type # required so rails know what type of company a record is
t.timestamps
end
end
def self.down
drop_table :companies
end
end
#db/migrate/###_create_videogames
class CreateVideogames < ActiveRecord::Migration
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
def self.down
drop_table :videogames
end
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :videogames
common validations and methods
end
#app/models/developer.rb
class Developer < Company
developer specific code
end
#app/models/publisher.rb
class Publisher < Company
publisher specific code
end
#app/models/videogame.rb
class Videogame < ActiveRecord::Base
belongs_to :developer, :publisher
end
As a result, you would have Company, Developer and Publisher models to use.
Company.find(:all)
Developer.find(:all)
Publisher.find(:all)

Resources