I have a model
class Product < ApplicationRecord
translates :title
end
When I create a record I then use globalize3 to create translations. My question is: how do I get the original untranslated record value of :title ?
Because when I do Product.last.title.translations I get all translation values, and when I do Product.last.title I only get the translation of the current I18n value.
I'm assuming you're using globalize and not globalize3 which has not been updated in almost 10 years. If not you should switch.
Also the whole idea of "original untranslated value" is kind of nonsense when dealing with Globalize. All the values are stored on the translations table.
You can switch locales by using Globalize.with_locale:
class Product < ApplicationRecord
translates :title
def original_title
Globalize.with_locale(I18n.default_locale) do
title
end
end
end
This assumes that records are originally created in the default locale. If that is not the case you could query the translations table:
class Product < ApplicationRecord
translates :title
def original_title
translations.first.title
end
end
Related
I am trying to add has_one_attached and has_many_attached
dynamically to a model on each request. I am able to do it, but probably there is a bug in the implementation, and I am looking for a better/correct way.
Here is my current setup. I have a products table that belongs to a shop model. A shop defines the attributes that a product can have using the custom_fields model.
Here is some code.
CustomField Model
# This model has mainly two attributes field_name and field_type. The records in this table are dynamic i.e., user-generated
# Here are some examples of the kind of records that can be stored:
# *) An icon field_name with image field_type
# *) manual field_name with file field_type
# *) assets field_name with multi_file field_type
# *) photos field_name with multi_image field_type
# *) product_name field_name with text type
# *) price field_name with number type
class CustomField < ApplicationRecord
end
Shop Model
class Shop < ApplicationRecord
has_many :custom_fields
has_many :products
end
Product Model
# This model has a JSON column (field_data) to store non active_storage fields.
# For example fields like product_name and price information will be stored in the JSON column
class Product < ApplicationRecord
belongs_to :shop
end
ProductDecorator class. Instantiated on each request in a controller
class ProductDecorator < SimpleDelegator
def initialize(object)
super
#custom_fields = shop.custom_fields
init_fields
end
def init_fields
# Save the non active_storage attributes to the JSON column.
# Here I am adding the store_accessor to the singleton_class. On each request we have a different singleton_class
__getobj__.singleton_class.class_eval do
store_accessor :field_data, *simple_fields
end
# We can't use has_one_attached on a singleton_class class or at least I couldn't figure out a way.
# Here I am monkey patching the class to make it work on each request which needs to be avoided
has_one_attached_fields.each do |field|
__getobj__.class.class_eval do
has_one_attached field
end
end
# Same like above here are monkey patching the class on each request.
has_many_attached_fields.each do |field|
__getobj__.class.class_eval do
has_many_attached field
end
end
end
def simple_fields
non_scalar = %w[file image multi_file multi_image]
#custom_fields.reject { |f| non_scalar.include? f.field_type }.pluck(:field_name)
end
def has_one_attached_fields
#custom_fields.select { |f| %w[file image].include? f.field_type }.pluck(:field_name)
end
def has_many_attached_fields
#custom_fields.select { |f| %w[multi_file multi_image].include? f.field_type }.pluck(:field_name)
end
end
The product model is decorated with the ProductDecorator class to modify the Product behavior in a rails controller on each request.
Based on the custom_fields defined in the shop model, the decorator class adds the active_storage functionality to a model.
The current code will work but, the dynamically added functionality will live forever in the class and will not be cleaned up after the request as the methods are added to the class instead of a singleton class.
I looked into ruby refinements but couldn't come up with a solution yet. Another option that I have is to sidestep active_storage completely and build custom rails models but would like to use active_storage if possible.
Is there any better way? Any help in this will be greatly appreciated
Prior to Rails 4.2.0, ActiveRecord would automatically convert a String to the type specified by serialize.
class Post < ActiveRecord::Base
serialize :options, Array # Column type: 'text', DB: PostgreSQL
end
class PostTest < ActiveSupport::TestCase
test "assign options" do
post = Post.new
post.options = "[1,2,3]" # => "[1,2,3]"
end
end
In Rails 4.2.1
class Post < ActiveRecord::Base
serialize :options, Array
end
class PostTest < ActiveSupport::TestCase
test "assign options" do
post = Post.new
post.options = "[1,2,3]" # ActiveRecord::SerializationTypeMismatch: Attribute was supposed to be a Array, but was a String.
end
end
I can't find this in the documentation, changelogs. Was this type of String to Array conversion removed or is it a bug? In my use case, I have such a String from params assigned to a model. It works in Rails 4.1.10 but in Rails 4.2.1 it raises ActiveRecord::SerializationTypeMismatch.
I'm not sure which commit between 4.1.10 and 4.2.1 introduced this behaviour but it's not a bug as it is well documented here. In particular,
If class_name is specified, the serialized object must be of that class on assignment and retrieval. Otherwise SerializationTypeMismatch will be raised.
That's strange because YAMLColumn has been behaving like this for the years. Take a look at this commit.
Simply trying to work out how to copy attributes from one Active Model to another without having to do it one by one.
I have two models one is RFM (ruby to filemaker) one is mongoid both mixin active model.
gem "ginjo-rfm"
the model
require 'rfm'
class StudentAdmin < Rfm::Base
config :layout => 'STUDENT_ADMIN_LAYOUT'
attr_accessor :name_first,:name_last
end
Mongoid model
class Student
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
end
Is there a quicky copy I can do? I found a sample between active record objects e.g.
student_admin = ... #load StudentAdmin
Student.new(student_admin.attributes.slice(Student.attribute_names))
but RFM doesn't provide a attributes method.
EDIT
Sorry what I am trying to achive is a better way than this
student_admins = #get student admins from external service
students = []
student_admins.each() do |sa|
students.push(Student.create!(first_name: sa.name_first, last_name: sa.name_last))
end
This example only shows 2 attributes, but in practice there is over 50 and was wondering if there is a way to do it without having to specify every attribute e.g. if the attribute names are the same on two objects copy them automatically.
Try this:
students = student_admins.map do |sa|
attrs = sa.methods.inject({}) do |hash, m|
next unless Student.column_names.include? m.to_s
hash[m] = sa.send m
end
Student.create(attrs)
end
Student would have to be a class that inherits from ActiveRecord::Base:
class Student < ActiveRecord::Base
...
end
Is there a way to have a custom serialization for fields in rails, a method that runs when a field is saved and loaded to convert from/to a string which is what ultimately is saved on the database.
Specifically what I want to do is have a field of type symbol like gender, with possible values :male and :female storing "male" and "female" on the database. There are some workarounds, like:
def gender
read_attribute(:gender).try(:to_sym)
end
but that leaves obj.attributes unchanged, so it's a leaky abstraction.
You can do it in Rails 3.1. The object you want to serialize has to reply to load and dump methods.
Here is an example of serializing a string in Base64.
class User < ActiveRecord::Base
class Base64
def load(text)
return unless text
text.unpack('m').first
end
def dump(text)
[text].pack 'm'
end
end
serialize :bank_account_number, Base64.new
end
For more details see: http://archives.edgerails.info/articles/what-s-new-in-edge-rails/2011/03/09/custom-activerecord-attribute-serialization/index.html
def whachamacallit
read_attribute("whachamacallit").to_sym
end
def whachamacallit=(name)
write_attribute("whachamacallit", name.to_s)
end
store them as stings in the database, but extract them as symbols when you pull them out then convert back before you save.
would work with any number or combination of strings / symbols.
to limit it to only a select few
validates_inclusion_of :whachamacallit, :in => [ :male, :female, :unknown, :hidden ]
From http://blog.quov.is/2012/05/01/custom-activerecord-attribute-serializers/
class Recipe < ActiveRecord::Base
serialize :ingredients, IngredientsList
end
class IngredientsList < Array
def self.dump(ingredients)
ingredients ? ingredients.join("\n") : nil
end
def self.load(ingredients)
ingredients ? new(ingredients.split("\n")) : nil
end
end
you can define the models to_xml for a model and it will do that
http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html
its possible to define Marshall.dump and put in that way i think, but its something to look into
You could use serialize method inside the model. Please reference to this link:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
(ps. search keyword "serialize" in that page ;D)
In short, you could do this:
class YourModel < ActiveRecord::Base
serialize :db_field
end
Rails would automatically serialize the field before saving to database, and deserialize it after fetched from the database.
well for just male/female you could just do a Boolean column like male and if it was false assume that meant female, add wrapper methods for it
def female?
return !self.male?
end
We just released a gem (AttributeHelpers) that does exactly this. Disclaimer: I am a maintainer for the gem.
It allows you to call attr_symbol :gender in your class definition and the serialization happens automagically.
In a Ruby on Rails application I am trying to use information from fields that are not associated with the model in validation.
Here is part of the model as an example (the whole model has gotten kinda big):
class Scorecard < ActiveRecord::Base
belongs_to :course
belongs_to :user
validate :attributes_consistency
def attributes_consistency
# Executed for all scorecards. Checks if the user completed the hole attributes correctly
if ( params[:no_fairways] and any_fairways? and !only_nine? ) or ( params[:no_fairways] and !any_h1_to_h9_score_blank and any_h1_to_h9_fairway? and only_nine? ) or ( params[:no_fairways] and !any_h10_to_h18_score_blank and any_h10_to_h18_fairway? and only_nine? )
errors.add_to_base("You inidicated that you missed all the fairways, but you also marked one or more fairways in the scorecard. Either uncheck the fairways mistakenly marked or uncheck the 'No fairways' checkbox.")
end
if ( params[:no_girs] and any_girs? and !only_nine? ) or ( params[:no_girs] and !any_h1_to_h9_score_blank and any_h1_to_h9_gir? and only_nine? ) or ( params[:no_girs] and !any_h10_to_h18_score_blank and any_h10_to_h18_gir? and only_nine? )
errors.add_to_base("You inidicated that you missed all the greens, but you also marked one or more greens in the scorecard. Either uncheck the marked greens on the scorecard or uncheck the 'No GIRs' checkbox.")
end
end # attributes_consistency
def any_h1_to_h9_score_blank?
h1_score.blank? or h2_score.blank? or h3_score.blank? or h4_score.blank? or h5_score.blank? or h6_score.blank? or h7_score.blank? or h8_score.blank? or h9_score.blank?
end
def any_h10_to_h18_score_blank?
h10_score.blank? or h11_score.blank? or h12_score.blank? or h13_score.blank? or h14_score.blank? or h15_score.blank? or h16_score.blank? or h17_score.blank? or h18_score.blank?
end
def any_h1_to_h9_fairway?
h1_fairway? or h2_fairway? or h3_fairway? or h4_fairway? or h5_fairway? or h6_fairway? or h7_fairway? or h8_fairway? or h9_fairway?
end
def any_h10_to_h18_fairway?
h10_fairway? or h11_fairway? or h12_fairway? or h13_fairway? or h14_fairway? or h15_fairway? or h16_fairway? or h17_fairway? or h18_fairway?
end
def any_h1_to_h9_gir?
h1_gir? or h2_gir? or h3_gir? or h4_gir? or h5_gir? or h6_gir? or h7_gir? or h8_gir? or h9_gir?
end
def any_h10_to_h18_gir?
h10_gir? or h11_gir? or h12_gir? or h13_gir? or h14_gir? or h15_gir? or h16_gir? or h17_gir? or h18_gir?
end
So how can I access params from the model?
Don't let params sneak up to the model. There's no point of having a controller in that case. Instead, checkout this episode from Railscasts that talks about virtual attributes that do not go into the database but can still be used for validations.
You don't need a corresponding model attribute for the virtual attributes. Define attributes local to the class such as #no_fairways that hold the state.
class ScoreCard < ActiveRecord::Base
# define attributes and accessors for both fields
attr_accessor :no_fairways, :no_girs
..
end
Now inside you form, you could just write:
<% form_for #scorecard %>
<%= f.check_box :no_fairways %>
<% end %>
Found the solution, thanks for the lingo though, "virtual attribute" helped with the google searchin.
The cleanliest way to accomplish this is to create attributes that are not part of the database but still part of the model. In my case I put this into the model:
attr_accessor :no_fairways
attr_accessor :no_girs
That easy! Now #scorecard.no_fairways and #scorecard.no_girs act just like any other attribute but aren't part of the database.