Parse nested JSON and save it to the database with rails - ruby-on-rails

Using rails (4), I would like to parse some data from an external API and store it into my database. For instance:
{ "name" : "Mercedes-Benz",
"commonName: "Mercedes",
"vehicles" : [ {
"name" : "Mercedes c5",
"components" : ["wheels", "doors"]
}]
}
I am aware that with JSON.parse I could create a new instance and store it if the json would match, but there are a few problems that prevent me to do it:
commonName uses cammelcase instead of rails typical underscore. Is there a way i can magically assign a key to an attribute?
Will vehicles be saved to the table vehicles ? Can I accomplish that automatically? Or should I go through every vehicle and save each one?
Components is just an array of strings. What would be a good way to represent that? Just another table with an attribute name?

Yes, if you want to be able to look up the components then having it as a separate table makes sense. It would need a vehicle_id as well as name.
For accepting the attributes for the vehicles and components use accepts_nested_attributes_for.
So your models should looked something like this:
class VehicleType < ActiveRecord::Base
has_many :vehicles
accepts_nested_attributes_for :vehicles
end
class Vehicle < ActiveRecord::Base
belongs_to :vehicle_type
has_many :components
accepts_nested_attributes_for :components
end
class Component < ActiveRecord::Base
belongs_to :vehicle
end
For converting commonName (camelCase) to common_name (snake case), there's a great Stack Overflow answer on that here: Converting nested hash keys from CamelCase to snake_case in Ruby. So assuming you have defined the function described there (convert_hash_keys) and the models above, your code should look roughly like this:
converted_hash = convert_hash_keys(JSON.parse(input_json_string))
vehicle_type = VehicleType.create(converted_hash)
For updating it would be VehicleType.update(converted_hash)

Related

Replacing a legacy model with a new one of the same name

Re-structuring a legacy app I wanted to re-use one of the model names because it is too perfect not to.
I want to make it totally invisible to the outside, JSON API, preferably even the controller layer, that these models do not have the default table name. But I cannot figure out the final puzzle piece. aliasing the foreign keys without making a chain and breaking it all.
So I have two tables. The old table perfections which I will be migrating away from, but not in one go unfortunately. Then the new table imperfections which will be taking over.
I can easily mask the table names:
class Perfection < ApplicationRecord
self.table_name = "imperfections"
end
class LegacyPerfection < ApplicationRecord
self.table_name = "perfections"
end
Models holding a foreign key to these though. That is my problem. In fact I have one problem model. It belongs to both these models. I can alias only one of the foreign keys to look like what I want from the outside but aliasing both I get an alias chain because of the shared name.
class OtherThing < ApplicationRecord
belongs_to :perfection, foreign_key: :imperfection_id
belongs_to :legacy_perfection, foreign_key: :perfection_id, optional: true
alias_attribute :legacy_perfection_id, 'perfection_id'
alias_attribute :perfection_id, 'imperfection_id'
end
I totally see why this makes an alias chain. The aliases are effectively doing the equivalent to this (and more for the getters snd stuff)
class OtherThing < ApplicationRecord
belongs_to :perfection, foreign_key: :imperfection_id
belongs_to :legacy_perfection, foreign_key: :perfection_id, optional: true
def legacy_perfection_id=(value)
self.perfection_id = value
end
def perfection_id=(value)
self.imperfection_id = value
end
end
If I call other_thing.legacy_perfection_id = 1 this number will be saved into imperfection_id.
I wonder if I can do something else... like actually change the attribute names somehow, and end up with a model where the parameters you pass to create or update are not revealing the internals.
For now I will do the transformation in the controller but I would like to have it be even cleaner.
The input parameters you pass to your model are not really linked to its attributes or columns - they are linked to setter methods. assign_attributes basically just does:
attributes.each do |k, v|
send("#{k}=", v)
end
So if you want the model to accept new_attribute as input but still assign the old attribute you can do it with:
def new_attribute=(value)
self.old_attribute = value
end
In some cases though it can be useful to have adapters. Like if API v1 accepts old_attribute and API v2 accepts new_attribute. To keep the difference from leaking into the model layer you add an adapter that transforms the params before you assign them. This is actually a controller concern in MVC as the controller is responsible for accepting user input and passing it to models.

Defining attributes at runtime based on data from related object

I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!

Rails - Manipulating the returned objects in an ActiveRecord query

So I have the following simplified models and associations:
class Barber < User
has_many :barber_styles, inverse_of: :barber
end
class Style < ActiveRecord::Base
end
class BarberStyle < ActiveRecord::Base
belongs_to :barber, inverse_of: :barber_styles
belongs_to :style
delegate :name, to: :style
end
If I wanted to make a query based on all BarberStyle's that belong to a specific Barber, is there a way I can include the name of the associated Style?
I want to use a line something like:
BarberStyle.joins(:barber).where(barber: 109).include(:style)
So I'd be able to access an associated style.name. But I don't think a line like this exists.
I know I could just use map to build an array of my own objects, but I have to use a similar query with many different models, and don't want to do a bunch of extra work if it's not necessary.
In one of my previous projects, I was able to render json with the following lines:
#colleges = College.includes(:sports).where(sports: { gender: "male" })
render json: #colleges, include: :sports
But I don't want to render json in this case.
Edit:
I don't really have any unincluded associations to show, but I'll try to elaborate further.
All I'm trying to do is have extra associated models/fields aggregated to each BarberStyle result within my ActiveRecord Relation query.
In an attempt to make this less confusing, here is an example of the type of data I want to pass into my JS view:
[
#<BarberStyle
id: 1,
barber_id: 116,
style_id: 91,
style: { name: "Cool Hairstyle" }
>,
#<BarberStyle
id: 2,
barber_id: 97,
style_id: 92,
style: { name: "Cooler Hairstyle" }
>,
etc...
]
The reason I want the data formatted this way is because I can't make any queries on associated models in my JS view (without an AJAX call to the server).
I had very similarly formatted data when I did render json: #colleges, include: :sports in the past. This gave me a collection of Colleges with associated Sport models aggregated to each College result. I don't want to build json in this fashion for my current situation, as it will complicate a few things. But I suppose that's a last resort.
I'm not sure if there's a way to structure an ActiveRecord query where it adds additional fields to the results, but I feel like it should, so here I am looking for it. Haven't found anything in the docs, but then again there is sooooo much left out of those.
Doug as far as I understand your code, BarberStyle must be a joining model between Barber and Style. You mentioned in your comment that you removed the has_many :styles, through: :barber_styles from your Barber model because you thought that it wasn't relevant. That's not true, it's exactly the point that would help you to achieve your goal. Add this relation again then you can do something like this:
barber = Barber.includes(:styles).where(barber: {id: 109})
barber.styles.each { |style| puts style.name }
Since barber.styles is a collection, I added a loop between all the possible styles you can have. But, from that, you can use your data as you feel like, looping through it or any other way you want.
Hope to have helped!
First off, in your case inverse_of does not accomplish anything, since you are setting the default values. Remove that.
Secondly, it seems you need to better understand the concept of HABTM relationships.
Using has many through is generally a good idea since you can add data and logic to the model in the middle.
It ideally suits your case, so you should set it up like this:
class Barber < User
has_many :barber_styles
has_many :styles, through: :barber_styles
end
class Style < ActiveRecord::Base
has_many :barber_styles
has_many :barbers, through: :barber_styles
end
class BarberStyle < ActiveRecord::Base
belongs_to :barber
belongs_to :style
end
Now it is an easy task to get the styles of a given barber. Just do this:
barber = Barber.find(1)
barber.styles
# => AR::Collection[<#Style name:'Cool Hairstyle'>,<#Style name:'Cooler Hairstyle'>]
Rails automatically uses the BarberStyle model in between to find the styles of a certain barber.
I assume this covers your need, if you have extra information stored only in BarberStyle, let me know.

Adding a Computed Attribute to ActiveResource Resource

What I'm trying to do is add an attribute to an activeresource resource that computer based on other attributes created from the servers response.
Further complicating things is that the attributes the computation depends on are part of a has_many association say has_many: items.
What I'd like to happen is when u = User.find(123) is called the items are retrieved and an attribute added to User based on some computation, for example u.blue_item_count.
The new attribute also need to appear when to object is serialized into XML or JSON. For instance u would serialize into {"id":1, "name":"bob", "blue_item_count":21 }.
Thanks
Just define a method on your User object that uses the items association. For example:
class User < ActiveRecord::Base
has_many :items
def blue_item_count
items.each do |item|
...
end
end
end

Using Retrieval Multiple Objects for another Retrieval in Active Records/Ruby on Rails

Kind of new to Ruby/Rails, coming from c/c++, so I'm doing my baby steps.
I'm trying to find the most elegant solution to the following problem.
Table A, among others has a foreign key to table B (let's call it b_id), and table B contains a name field and a primary (id).
I wish to get a list of object from A, based on some criteria, use this list's b_id to access Table B, and retrieve the names (name field).
I've been trying many things which fail. I guess I'm missing something fundamental here.
I tried:
curr_users = A.Where(condition)
curr_names = B.where(id: curr_users.b_id) # fails
Also tried:
curr_names = B.where(id: curr_users.all().b_id) # fails, doesn't recognize b_id
The following works, but it only handles a single user...
curr_names = B.where(id: curr_users.first().b_id) # ok
I can iterate the curr_users and build an array of foreign keys and use them to access B, but it seems there must be more elegant way to do this.
What do I miss here?
Cheers.
Assuming you have following models:
class Employee
belongs_to :department
end
class Department
has_many :employees
end
Now you can departments based on some employee filter
# departments with employees from California
Department.include(:employees).where(:employees => {:state => "CA"}).pluck(:name)
For simplicity, let's take an example of Article and Comments, instead of A and B.
A Comment has a foreign key article_id pointing at Article, so we can setup a has_many relationship from Article to Comment and a belongs_to relationship from Comment to Article like so:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Once you have that, you will be able do <article>.comments and Rails will spit out an array of all comments that have that article's foreign key. No need to use conditionals unless you are trying to set up a more complicated query (like all comments that were created before a certain date, for example).
To get all the comment titles (names in your example), you can do <article>.comments.map(&:title).

Resources