JsonB and attr_json gem: Get the defined attributes programmatically - ruby-on-rails

I'm using a jsonb field in my Rails application and have installed the gem attr_json. Is there a way to receive the defined json_attributes programmatically? With a "normal" rails attribute, I would just do #instance.attribute_names. But with attr_json is there any way how to have the json_attributes returned?
class Vehicle < Item
include AttrJson::Record
attr_json :licence_plate, :string, container_attribute: "custom_attributes_indexed"
attr_json :brand, :string, container_attribute: "custom_attributes_indexed"
attr_json :serial_number, :string, container_attribute: "custom_attributes_indexed"
attr_json :inventory_number, :string, container_attribute: "custom_attributes_indexed"
end
For this code I would like to do something like #vehicle.json_attribute_names and have the following returned
["licence_plate", "brand", "serial_number", "inventory_number"]

You can retrieve the defined json_attributes via: Vehicle.attr_json_registry.attribute_names
But even simpler to retrieve the attributes via the rails method attribute names is to add rails_attribute: true to your attr_json definitions. This will return all the "normal" attributes and the JSON attributes as an array.
[...]
attr_json :licence_plate, :string, container_attribute: "custom_attributes_indexed", rails_attribute: true
[...]

I have a Rails app that uses a JSONB column in Postgres and without any other gems I can call the following to get the keys...
Given a model Inspections with a JSONB column called "results" I can do:
#some_inspection = Inspection.first
#some_inspection.results
#=> {"assigned_to" => "John Smith", "inspection_date" => "2020_01_02", "passed" => "true"}
#some_inspection.results.keys
#=> ["assigned_to", "inspection_date", "passed"]
I was under the impression that the great thing about having a JSONB column is that it seamlessly translates for me. I don't have any attr_json nor any other specialized code in my model. Like so much Ruby and Rails "it just works". I pass it a hash and it stores that as JSON and when I ask for it, I get a hash. I can then do any hash methods like .values or .keys on it. If I need JSON back I can do #some_inspection.results.to_json.
If you pass it actual JSON then it will just store the JSON text as a string in the column. You can then get the JSON back just by calling the column name like:
#some_inspection.results
#=> "{\"assigned_to\":\"John Smith\",\"inspection_date\":\"2020_01_02\",\"passed\":\"true\"}"
But if you want to do something like .keys you have to parse it since it is a string:
JSON.parse(#some_inspection.results).keys
#=> ["assigned_to", "inspection_date", "passed"]

Related

How to store string as array in database column using Ruby on Rails

This question is asked many times on SO. The main problem is nothing got fits into my situation.
Case is, I am not able to store typed content as array in database column.
text_field whose code is:
= text_field_tag 'product[keywords][]', #product.keywords, class: 'tab-input
product_keywords'
In controller strong parameters are:
params.require(:product).permit(:id, :name, :keywords => [])
Jquery code that is not not removing value upon deletion when typed wrong value but it add commas after each element as I want to take commas seperated value in one column.
$(document).on 'keyup', '.product_keywords', ->
keyword = #value.replace(/(\w)[\s,]+(\w?)/g, '$1, $2')
if keyword != #value
#value = keyword
return
model code:
serialize :keywords, Array
migration code:
class AddKeywordsToProducts < ActiveRecord::Migration[5.1]
def change
add_column :products, :keywords, :text
end
end
So, if someone writes, abc and hit space a comma is added in the end. after three typed words it will look like:
abc, dbx, she
now I want to store it as array in column but its not storing properly.
it stores as:
["abc, dbx, she"]
Also please can anybody tell me the best cases to handle these cases?
Plus best practices to deal with such cases using ruby so I will learn it for future?
You probably want a custom serializer as shown here. So instead of:
serialize :keywords, Array
You might do somewhat like:
serialize :keywords, KeywordSerializer
And somewhere in helpers:
class KeywordSerializer
def self.dump(what)
what.join(", ")
end
def self.load(what)
what.split(/\s*,\s*/)
end
end
Passing array elements using single form tag is not possible to pass as a array and passing array as a string, you need to process it near white-listing your params,
permitted_params = params.require(:product).permit(:id, :name, :keywords => [])
permitted_params[:keywords] = permitted_params[:keywords][0].split(/\s*,\s*/)

Custom wrap JSON to Virtus Model?

I have a JSON object that looks like the following:
{
"id":"10103",
"key":"PROD",
"name":"Product",
"projectCategory":{
"id":"10000",
"name":"design",
"description":""
}
}
and a Virtus model that looks like the following:
class Project
include Virtus.model
attribute :id, Integer
attribute :key, String
attribute :name, String
attribute :category, String #should be the value of json["projectCategory"]["name"]
end
Everything lines up fine other than trying to map Project.category to json["projectCategory"]["name"].
So in total the end Virtus object I'm look for should look like:
"id" => "10103",
"key" => "PROD",
"name" => "Product",
"category" => "design"
Right now I'm creating a model instance with Project.new(JSON.parse(response)) or basically a hash of the json response. How can I custom map Virtus some attributes to my json response?
So I ended up figuring out you can override the self.new method allowing you to get to nested values in the hash you pass your Virtus model.
I ended up doing the following which worked fine:
class Project
include Virtus.model
attribute :id, Integer
attribute :name, String
attribute :key, String
attribute :category, String
def self.new(attributes)
new_attributes = attributes.dup
# Map nested obj "projectCategory.name" to Project.category
if attributes.key?("projectCategory") and attributes["projectCategory"].key?("name")
new_attributes[:'category'] = attributes["projectCategory"]["name"]
end
super(new_attributes)
end
end

Programmatically get all database column types

I am building a Rails gem for which I might need to know the currently available column types. So say for Postgres, I am looking for something like: ActiveRecord::Base.available_column_types. I looked through the source with no success so far.
I can't find an ActiveRecord method to get what you want. But I can show you two ways you can achieve this:
With any path you need to create an initializer and Monkey Patch ActiveRecord. For example: /config/initializers/active_record_extensions.rb. Then, the options:
OPTION 1: get data types based on your models
class ActiveRecord::Base
def self.available_column_types
types = []
ActiveRecord::Base.subclasses.collect{ |type| type.name }.each do |model_name|
types += eval("#{model_name}.columns.map(&:type)")
end
types.uniq
end
end
Then you can do rails console on your terminal and write:
irb(main):001:0> User.available_column_types
=> [:integer, :string, :text, :datetime, :boolean, :date, :hstore]
irb(main):002:0> ActiveRecord::Base.available_column_types
=> [:integer, :string, :text, :datetime, :boolean, :date, :hstore]
irb(main):003:0>
OPTION 2: get all posible data types based on you db adapter
class ActiveRecord::Base
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) and
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
types = ActiveRecord::Base.connection.execute("select * from pg_type;")
return types.inject([]) { |result, record| result << record["typname"] }
# Too much info on pg_type table, you can get whatever you need.
end
if defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter) and
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
# I don't know, it's just an example. Yo can add all adapters you want
return
end
# maybe raise an Exception with NO ADAPTER! message
end
end
Once again, on your console, you can do ActiveRecord::Base.available_column_types to see the result.
Note: you need to adapt this in order to make it work with your gem.

How to properly pass an array of values to the `attr_accessible` method?

I am using Ruby on Rails v3.2.2 and I would like to handle an array of symbols so to pass its values to the attr_accessible method as well as it should be made. That is, I have:
attr_array = [:one, :two, ...]
If I use:
attr_accessible attr_array
I get the following:
self.accessible_attributes.inspect
# => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"[:one, :two, ..."]}>
However, I would like to get:
# => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"one", "two", "..."}>
as well as it should be made.
How can I make that?
Just like this :
attr_accessible *array

Mongoid: Added Hash to Model but can't write to it

I've got a model, Entity.
class Entity
include Mongoid::Document
field :x
field :y
field :z, type => Hash, :default => {} # new field
end
I added a new field to it, a hash. When I try to use it, I get an error. My code is:
e = Entity.first
if e.z["a"] # if there is a key of this in it?
e.z["a"] = e.z["a"] + 1
else
e.z["a"] = 1
end
But, this error with an undefined method get for hash. If I try to create an initializer for it, to set the values in an existing document, it errors with the same error. What am I doing wrong?
Initializer looks like:
e = Entity.first
e.write_attribute(:z, {})
Thanks
Sorted it.
It seems the answer is to set in Mongoid 1.9.5 the hash to:
field :hash_field, :type => Hash, :default => Hash.new
and it can access and initialize it. Not quite understanding why, but happy to have the answer !

Resources