I need to run a rake task to migrate data in my model
class Translation < ApplicationRecord
store :body, accessors: [:object_body], coder: YAML
belongs_to :team
end
and need to access body as text.
I don't want to remove the coder part since a lot of logic relies on that.
Can I access body without decoding to a ruby object?
Just add a new method on the model.
class Translation < ApplicationRecord
store :body, accessors: [:object_body], coder: YAML
belongs_to :team
def raw_body
read_attribute_before_type_cast('body')
end
end
read_attribute_before_type_cast should work. It will return the value before typecasting or deserialization.
raw_body = obj.read_attribute_before_type_cast(:body)
Related
I'm using ActiveModel::Serializer in a rails application to format my model data as a json response, but I would like to change the formatting so that the associations of my main model are not nested. I tried setting root: false and that doesn't work
Expected behavior vs actual behavior
I have a model Account with an association belongs_to :account_status
and I was able to add this association in the AccountSerializer to get that associated data just fine. But do to my api contract requirements, I need the json to be formatted without the association nesting.
So I'm getting this:
{
"account_id": 1
<other account info>
...
"account_status": {
"status_code": 1
"desc": "status description"
....
}
}
But I want this:
{
"account_id": 1
<other account info>
...
"account_status_status_code": 1
"account_status_desc": "status description"
....
}
Model + Serializer code
How can I achieve the expected behavior without writing each account_status field as an individual attribute in the AccountSerializer ??
Controller
class AccountsController < ActionController::API
def show
account = Account.find(params[:account_id])
render json: account
end
end
Model
class Account < ActiveRecord::Base
self.primary_key = :account_id
belongs_to :account_status, foreign_key: :account_status_code, inverse_of: :accounts
validates :account_status_code, presence: true
end
Serializer
class AccountSerializer < ActiveModel::Serializer
attributes(*Account.attribute_names.map(&:to_sym))
belongs_to :account_status,
foreign_key: :account_status_code,
inverse_of: :accounts
end
Environment
OS Type & Version: macOS Catalina v 10.15.7
Rails 6.1.4:
ActiveModelSerializers Version 0.10.0:
Output of ruby -e "puts RUBY_DESCRIPTION":
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19]
you could replace belongs_to :account_status,... by below code
class AccountSerializer < ActiveModel::Serializer
attributes(*Account.attribute_names.map(&:to_sym))
AccountStatus.attribute_names.each do |attr_name|
key = "account_status_#{attr_name}"
define_method(key) do
# i checked and see that `object.account_status` just call one time
object.account_status.send(attr_name)
end
attribute key.to_sym
end
end
Instead of using an assocation in your serializer you can setup delegation in your model:
class Account < ApplicationRecord
delegate :desc, ...,
to: :account_status,
prefix: true
end
This will create a account_status_desc method which you can simply call from your serializer:
class AccountSerializer < ActiveModel::Serializer
attributes(
:foo,
:bar,
:baz,
:account_status_code, # this is already a attribute of Account
:account_status_desc
# ...
)
end
Another way of doing this is by simply adding methods to your serializer:
class AccountSerializer < ActiveModel::Serializer
attributes :foo
def foo
object.bar.do_something
end
end
This is a good alternative when the result of serialization does not align with the internal representation in the model.
I have something like this:
module Api
module V1
class Order < ActiveRecord::Base
has_many :order_lines
accepts_nested_attributes_for :order_lines
end
end
module Api
module V1
class OrderLine < ActiveRecord::Base
belongs_to :order
end
end
In my orders controller, I permit the order_lines_attributes param:
params.permit(:name, :order_lines_attributes => [
:quantity, :price, :notes, :priority, :product_id, :option_id
])
I am then making a post call to the appropriate route which will create an order and all nested order_lines. That method creates an order successfully, but some rails magic is trying to create the nested order_lines as well. I get this error:
Uninitialized Constant OrderLine.
I need my accepts_nested_attributes_for call to realize that OrderLine is namespaced to Api::V1::OrderLine. Instead, rails behind the scenes is looking for just OrderLine without the namespace. How can I resolve this issue?
I am pretty sure that the solution here is just to let Rails know the complete nested/namespaced class name.
From docs:
:class_name
Specify the class name of the association. Use it only
if that name can't be inferred from the association name. So
belongs_to :author will by default be linked to the Author class, but
if the real class name is Person, you'll have to specify it with this
option.
I usually see, that class_name option takes the string (class name) as a argument, but I prefer to use constant, not string:
module Api
module V1
class Order < ActiveRecord::Base
has_many :order_lines,
class_name: Api::V1::OrderLine
end
end
end
module Api
module V1
class OrderLine < ActiveRecord::Base
belongs_to :order,
class_name: Api::V1::Order
end
end
end
I've been trying to serialize a column before I put it in the database. I wanted to do the following in my model:
class SearchResult < ActiveRecord::Base
serialize :data
end
...but it wouldn't serialize when I save. So I resorted to this:
class SearchResult < ActiveRecord::Base
before_save :serialize_data
private
def serialize_data
self.data = YAML.dump self.data
end
end
This works. But, is there any reason why the top code doesn't work (it's much neater), or do I need to declare the serialize method? It doesn't throw any errors, but doesn't do what I hoped it would do either.
Have you tried:
class SearchResult < ActiveRecord::Base
serialize :data, Hash
end
What about
class SearchResult < ActiveRecord::Base
store :data, accessors: [:data_help]
end
I have a field in a model that is serialized and when I attempted to validates the uniqueness of it, it doesn't work. (Still on Rails 2.3 on this app)
app/models/foo.rb
class foo < ActiveRecord::Base
serialize :rules
validates_uniqueness_of :rules
end
I have attempted to store the content in a hash field instead and validate the content hash's uniqueness. Then I ran to another problem of the order of the callbacks.
require 'digest/md5'
class foo < ActiveRecord::Base
before_save :update_content_hash
validates_uniqueness_of :content_hash
def update_content_hash
self.content_hash = OpenSSL::Digest::SHA1.hexdigest(self.rules.flatten)
end
end
However, having looked at the Active Record callbacks order, before_save is executed after validation, so it will always be valid because the default value is nil and after that it's updated to the new content hash.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Maybe I'm not thinking straight, any solution to this?
Many thanks in advance.
Try this:
before_validation :update_content_hash
I have a models like Routine and RoutineContent for localization
in Routine.rb
Class Routine < ActiveRecord::Base
has_many :routine_contents, dependent: :destroy
accepts_nested_attributes_for :routine_contents, reject_if: proc {|attributes| attributes['title'].empty?}
end
and in RoutinesContent
class RoutineContent < ActiveRecord::Base
belongs_to :routine
validates_presence_of :title
end
In the new Routine action I puts on RoutineConten fields for languages. If title in one object is emty then this object will rejected.
When I go to edit action, I do this
def set_routine_contents
contents = #routine.routine_contents.group_by {|content| content.lang}
if contents['ru'].nil?
#routine.routine_contents << RoutineContent.new(lang: 'ru')
end
if contents['en'].nil?
#routine.routine_contents << RoutineContent.new(lang: 'en')
end
end
end after this Rails INSERT INTO emty object in table, why? How I can disable it?
Thanks
Solution
def set_routine_contents
contents = #routine.routine_contents.group_by {|content| content.lang}
if contents['ru'].nil?
#routine.routine_contents.build(lang: 'ru')
end
if contents['en'].nil?
#routine.routine_contents.build(lang: 'en')
end
end
Use the build method. Add to Array via << it was bad idea
has_many association implemented with foreign key in routine_id in routine_contents table.
So adding new RoutineContent to your Routine requires determined primary key in Routine to write to routine_id, and causes Routine to save if not saved yet.