Storing List item details in a database? - ruby-on-rails

So, this may be more of a "Software Engineering" question. But im thinking of a good way at how to store details for a Widget in active record.
Pretend Widget A has a show page, and in that show page we have some accordian style "FAQS" or something to that effect. Within the accordian is a list, with bullet points highlighting different things of how Widget A works, or how to use Widget A.
Since obviously we wouldn't want to make a separate page for each widget, these items would need to be stored somewhere. But we also wouldn't want to make...10, 20 or 30 separate fields in the database for each one of these. So whats the solutions for this?
My first thought is some sort of hash or array, but does rails allow this? Especially if they are long strings per item. Is there a better way?
Or is the proper way to do this is just claim this as a model (like.."faq_item") or something, and then have a reference ID for the Widget it needs to go to? (that way the "faq_item" model/schema would only need a few fields, and can just assigned the reference ID to the Widget it would belong to.

If each widget has only a few "FAQ items" (or "details", as I'll refer to them) and each detail is nothing more than a text string, you could store a widget's details in a serialized array as such:
# models/widget.rb
class Widget < ApplicationRecord
# serialize the `details` attribute as JSON into
# the `details` column on the widgets table
serialize :details, JSON
end
# db/schema.rb
# ...
create_table "widgets", force: :cascade do |t|
t.string "name"
t.text "details"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
# rails console
wid = Widget.create!(
:name =>
'Wideband, Voltage-Feedback Operational Amplifier With Disable',
:details => [
'Flexible supply range: 5-V to 12-V Single Supply, +/- 2.5-V to 5-V Dual Supply',
'Unity-Gain Stable: 500 MHz (G = 1)',
'High Output Current: 190 mA',
'High Slew Rate: 1800 V/us',
'Wideband 5-V Operation: 220 MHz (G = 2)'
])
# => #<Widget ...>
wid.details.first
# => "Flexible supply range: 5-V to 12-V Single Supply, +/- 2.5-V to 5-V Dual Supply"
You can look at the Rails 5 serialization API for more information on serialize.
If, however, you need to store more information for each detail (for instance, created_at/updated_at fields) or each widget has more than a few details, then it may be prudent to create a new table for widget details as you suggested:
# models/widget.rb
class Widget < ApplicationRecord
has_many :details, :dependent => :destroy
end
# models/widget/detail.rb
class Widget::Detail < ApplicationRecord
belongs_to :widget
end
# db/schema.rb
# ...
create_table "widget_details", force: :cascade do |t|
t.integer "widget_id"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
wid = Widget.create!(
:name =>
'CMOS, 125 MHz Complete DDS Synthesizer',
:details => [
Widget::Detail.create!(:content => '125 MHz Clock Rate'),
Widget::Detail.create!(:content => 'On-Chip High Performance DAC'),
Widget::Detail.create!(:content => '32-Bit Frequency Tuning Word')
])
# => #<Widget ...>
wid.details.first
# => #<Widget::Detail ... content: "125 MHz Clock Rate" ...>

If you are using Postgres you could use a JSONB type field in your database. With a JSONB data type you will be able to have unstructured data while being able to query the field with Postgres and ActiveRecord without the need for a new table.
Like this:
rails g migration add_fields_to_widgets details:jsonb
rails db:migrate
Test your widget creation inside the rails console.
Widget.create(name: "Widget Foo", details: { "how to use": "Instructions on how to use", "height": "12cm", "width": "100cm" })
If you'd want to find all the widgets with 12cm height, you would just have to make a query like this:
Widget.where("details->>'height' = ?", "12cm")
which would return your original Widget Foo object, and then you would be able to manipulate it with pure JavaScript on your front-end.

Related

Can you have an association based on a JSONB column in Rails?

I have a Rails app (rails v6.0.3, ruby 2.7.1) that is using the Noticed gem to send notifications. I have the following model configuration:
class Vendor < ApplicationRecord
has_noticed_notifications
end
The has_noticed_notifications is, as described in their README, a "Helper for associating and destroying Notification records where(params: {param_name.to_sym => self})"
So when I create a Notification like so...
VendorAddedNotification.with(
vendor: vendor,
data_source: "user",
).deliver(some_user) # => Notification inserted!
I expect to be able to find the Notifications that reference the vendor, using the Noticed method, like so:
vendor = Vendor.find ...
vendor.notifications_as_vendor # => Expected: [ Notification#123 ]
However, the input is always an empty array (Actual => [])
I looked at their source code and it looks like notifications_as_vendor is the following query:
Notification.where(params: { :vendor => self }) # where self = an instance of the Vendor model
However, that doesn't seem to work, and I'm not sure if it's supposed to or not. I tried running a simpler query to see if it worked ...
Notification.where(params: { :data_source => "user" })
But that did not work either. However, when I ran the same query with a different signature, it did:
Notification.where("params->>'data_source' = ?", "user")
So my question is-- is this Notified's mistake, or am I missing something in my configuration? I'm using PSQL for this, here is the relevant schema:
...
create_table "notifications", force: :cascade do |t|
t.string "recipient_type", null: false
t.bigint "recipient_id", null: false
t.string "type", null: false
t.jsonb "params"
t.datetime "read_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["read_at"], name: "index_notifications_on_read_at"
t.index ["recipient_type", "recipient_id"], name: "index_notifications_on_recipient_type_and_recipient_id"
end
...
And here are the related models:
class VendorAddedNotification < Noticed::Base
deliver_by :database
param :vendor
param :data_source
end
class Notification < ApplicationRecord
include Noticed::Model
belongs_to :recipient, polymorphic: true
end
Thank you in advance!
I've found why it's not working, it seems to be an issue with Notified.
In plain SQL I ran:
# PLAIN SQL
select "params" from "notifications" limit 1
Which returns the notification's params (returned notifcation's id=77)
# PLAIN SQL Result
"{""added_by"": {""_aj_globalid"": ""gid://stack-shine/WorkspaceMember/269""}, ""data_source"": ""user"", ""_aj_symbol_keys"": [""workspace_vendor"", ""data_source"", ""added_by""], ""workspace_vendor"": {""_aj_globalid"": ""gid://stack-shine/WorkspaceVendor/296""}}"
Now in Rails when I do
vendor = Notification.find(77).params[:vendor]
vendor.notifications_as_vendor.to_sql
The result is ...
"SELECT \"notifications\".* FROM \"notifications\" WHERE \"notifications\".\"params\" = '{\"vendor\":{\"_aj_globalid\":\"gid://stack-shine/Vendor/296\"},\"_aj_symbol_keys\":[\"vendor\"]}'"
... the extracted params from that query are:
'{\"vendor\":{\"_aj_globalid\":\"gid://stack-shine/Vendor/296\"},\"_aj_symbol_keys\":[\"vendor\"]}'
So ... In the database, the serialized params are A, but Rails is search for B:
# A: `params` In the database
"{""added_by"": {""_aj_globalid"": ""gid://stack-shine/WorkspaceMember/269""}, ""data_source"": ""user"", ""_aj_symbol_keys"": [""vendor"", ""data_source"", ""added_by""], ""vendor"": {""_aj_globalid"": ""gid://stack-shine/Vendor/296""}}"
# B: `params` Searched with by Rails
"{\"vendor\":{\"_aj_globalid\":\"gid://stack-shine/Vendor/296\"},\"_aj_symbol_keys\":[\"vendor\"]}"
Clearly this query could not work because the params in the database are not the params being search by Rails.
The notification, in the database, has extra parameters on top of "vendor" ("data_source" and "added_by") that are not being search up by the Vendor. Is this why it returns nothing?
For now, I'll simply the look up the notifications myself by storing the vendor_id in params and doing something like Notification.where("params >> vendor_id = ?", 123)

Rails 4/postgresql - Send data to another randomly chosen rows of another table

I have a Ruby on Rails 4 app and postgresql9.4 as database.
I have a Deal, DealPrize and Prize models.
Prizes have a quantity and a name. For example prize1 has name dvd and quantity 67.
DealPrizes have a column deal_id and prize_id
class Deal
has_many :prizes, dependent: :destroy
has_many :deal_prizes, dependent: :delete_all
end
class Prize
belongs_to :deal, :foreign_key => 'deal_id'
has_many :deal_prizes, dependent: :destroy
end
class DealPrize
belongs_to :deal, :foreign_key => 'deal_id'
belongs_to :prize, :foreign_key => 'prize_id'
end
Database structure:
create_table "deal_prizes", id: :bigserial, force: :cascade do |t|
t.string "name",
t.integer "deal_id"
t.integer "prize_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "prizes", force: :cascade do |t|
t.string "prize_name", limit: 255
t.integer "quantity"
t.integer "deal_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "prizes", ["deal_id"], name: "index_prizes_on_deal_id", using: :btree
create_table "deals", force: :cascade do |t|
t.string "name", limit: 255
t.string "description", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I'm gonna take an example to explain what I want and so far fail to achieve:
I need to select all the prizes associated with Deal id= 5.
For example I will get as result
prize id=5 => dvd (whose prize.quantity =67)
prize id= 9 => Cd (whose prize.quantity = 32)
then send these prizes (that is to say the 67 DVDs and the 7 CDs) in randomly chosen rows inside the DealPrizes rows where Deal_id = 5 AND prize_id=empty (needed so that if it has already been attributed a prize, it should not be again put another prize inside the same row)
Note: I don't want to find them and write on rows that are consecutive: i need random rows.
then WRITE into these Dealprizes randomly chosen rows. so I'll write on 67 randomly chosen rows prize_id = 5 and on 9 randomly chosen rows prize_id = 9
Note: the number of rows in DealPrize is superior to 2 millions so need for performance is required.
I would rather use raw sql than active record and will try to not use Rand and random functions directly on the whole table like this guy advice not to http://www.adamwaselnuk.com/projects/2015-03-28-here-be-taverns.html
"The problem is these can lead to performance issues on larger tables
because when you say "Hey database order the table randomly and then
give me the first row" the table says "Okay, I am going to go through
every single row and assign it a random number and then limit my
selection to one". Asking for one thing results in a computation on
every single row in the table!"
I'm totally new to Rails so I could only explain/map the different steps needed on Models/deal.rb
# 1. find the first prize where deal_id= seld.id
# and take each of the prizes quantity and id ofr the next steps
def find_prizes_for_this_deal
Prize.find_by deal_id: self.id # here I could only find one, don't know how to find many prizes at once and keep both id and quantity
end
# 2. choose Deal prize's table randomly chosen rows (created for the same
# deal attached to prizes) where deal_id=self.id and prize_id = empty
def DealPrize.random
# trick found here http://www.adamwaselnuk.com/projects/2015-03-28-here-be-taverns.html, in order not to use the poor-performance rand() methodon the whole table
offset(rand(count)).first.where(["deal_id = ?", self_id] && prize_id= nil)
end
#3. send the prizes selected in step 1. into the randomly chosen rows of
# DealPrizes in step 2
def NO IDEA here how to do this at
end
How to make this work ?
There is a lot that needs to be done for this problem. I can help you out with most and point you in the right direction.
The first part has been answered
<pre><code>deal = Deal.find(5)
prizes = deal.prizes
</code></pre>
You can loop through prizes
prizes.each do |prz|
d_id = prz.deal_id
p_id = prz.id
quantity_count = prz.quantity
end
To get the quantity of the first prize
quantity_count = prizes[0].quantity
Or get one prize by id
prz = prizes.where(id: 5)
I suggest creating two new arrays that will store random numbers to use in your queries. Get the last record id from deal_prizes to be your max number.
max = DealPrize.order("id").last.id
Based on the quantity count, loop to populate array with random numbers
first_prize = []
(1..quantity_count).each do |n|
first_prize << rand(max)
end
You can use that array the find or update records
DealPrize.where(id: first_prize).update_all(deal_id: d_id, prize_id: p_id)
Or if your going to hardcode it
"UPDATE deal_prizes SET deal_id = #{d_id}, prize_id = #{p_id} WHERE id IN ( #{first_prize.join(',')} )"
Be mindful of updating records with prize_id not null or zero (however you indicate it). You can run queries till you find exactly 67 records that have prize_id is null.
good_rows = DealPrize.where(id: first_prize).where("prize_id IS NULL")
bad_rows = first_prize - good_rows
and then replace ids in bad_rows by randomly generating new ids and query again. Continue doing that till you find all good rows then update by adding your new set of good rows with the existing one perfect_first_prize = good_rows + new_good_rows
Your question is very complex and confuses me a lot. Let's go step by step.
I need to select all the prizes associated with Deal id= 5.
That's easy.
deal = Deal.find(5)
prizes = deal.prizes

Permutating an existing array to seed a Rails database

I would like to seed my Rails app database with the permutation of an existing array of objects, and am unsure about the best way to go about this.
I currently have a Country model, with the following attributes:
create_table :countries do |t|
t.string :name
t.float :latitude_dec
t.float :longitude_dec
t.timestamps null: false
end
I have seeded this model from a .yaml file (as these attributes are static), and now would like to use these records to seed a CountryPair model (where the attributes are also static). This model will have the following attributes:
create_table :country_pairs do |t|
t.string :country_a
t.string :country_b
t.string :pair_name
t.float :country_a_latitude_dec
t.float :country_b_latitude_dec
t.float :country_a_longitude_dec
t.float :country_b_longitude_dec
t.float :distance
t.timestamps null: false
end
The aim is to permutate the array of Country objects, and create a CountryPair object from each permutation (and seed the database with the output). I understand the Ruby array#permutation method, but am unsure about how to pull out the appropriate values into the new array of CountryPair objects. The order of countries in the pair is important here, so I'd like to use permutations rather than combinations.
Ultimately, I'd also like to calculate the distance between the country pairs, but I'm hoping to start figuring that out once I have the CountryPair model filled!!
This is my first foray back into Rails after a five year absence, so apologies if I've got some of the terminology/methodology wrong - please do ask for clarification if any further information is required! Thanks in advance!
You can add this snippet to your seeds.rb after the Countries are seeded.
Country.all.permutation(2) do |p|
CountryPair.create(
country_a: p[0].name,
country_b: p[1].name,
pair_name: p[0]name + p[1].name,
country_a_latitude_dec: p[0].latitude.dec,
country_b_latitude_dec: p[1].latitude.dec,
country_a_longitude_dec: p[0].longitude.dec,
country_b_longitude_dec: p[1].longitude.dec,
distance: # An algorithm to calculate distance
)
end
Then run it with: rake db:setup

Sort by date span

Let's say we have the following model.
create_table :meetings do |t|
t.datetime :started_at
t.datetime: ended_at
end
class Meeting < ActiveRecord::base
end
How would I order a meetings_result, so that the longest meeting is the first meeting in the collection and the shortest meeting the last.
Something like
Meeting.order(longest(started_at..ended_at))
Obviously that doesn't work.
How would I achieve this, preferably without using raw SQL?
I don't think you can do it without using raw SQL.
Using Raw SQL:
Meeting.order('(ended_at - start_at) DESC')
(works with PostGreSQL)
No SQL? Two options come to mind. Create an array of hashes and sort it there, or add another column in the db and sort on that.
# How many records in the meetings table? This array of hashes could get huge.
meetings_array = []
Meeting.all.each do |meeting|
meetings_array << {id: meeting.id, started_at: meeting.started_at, ended_at: meeting.ended_at , duration: meeting.ended_at - meeting.started_at }
end
meetings_array.sort_by { |hsh| hsh[:duration] }
Or, create another column:
# Is it worth adding another column?
create_table :meetings do |t|
t.datetime :started_at
t.datetime :ended_at
t.datetime :duration
end
Update this column whenever you have both started_at and ended_at. Then you can:
Meeting.order("duration")

How do I get only unique results from two dissimilar arrays?

This might seem like a duplicate question, but I can't find any information on this. I want to show the results from a remotely acquired json array excluding certain results by comparing them to a local table. I have a gallery model with:
t.integer :smugmug_id
t.string :smugmug_key
t.integer :category_id
t.string :category_name
t.string :description
t.integer :highlight_id
t.string :highlight_key
t.string :highlight_type
t.string :keywords
t.string :nicename
t.integer :subcategory_id
t.string :subcategory_name
t.string :title
t.string :url
The data for this model gets populated by a rake task that connects to the smugmug api (json) and stores the data locally. I'm trying to create a view that shows all the smugmug galleries that are not stored locally.
Here's what I've tried so far, but it's not excluding the locally stored galleries like I thought it would.
def self.not_stored
smugmug_list = Smug::Client.new.albums(heavy = true)
gallery_list = Gallery.select(:smugmug_id)
smugmug_list.each do |smugmug|
smugmug unless gallery_list.include? smugmug.id
end
end
Hopefully this makes sense. I'm getting a json array of galleries, and I want to display that array excluding results where the album id matches the smugmug_id of any of my locally stored records.
Quick edit: I'm using an adaptation of this gem to connect to the smugmug api.
Just use the difference operator.
General Example:
ruby-1.9.2-p136 :001 > [3,2,1] - [2,1]
=> [3]
So you would have:
smugmug_list.collect{|e| e.id} - gallery_list
Enumerable#collect will turn the smugmug_list into a list of id's. From there, you can do the difference operator, which will return all the id's of all the smugmug galleries that are not stored locally.
Another option to maintain the list of galleries:
smugmug_list.select{|e|!gallery_list.include?(e.id)}

Resources