Ruby Sinatra Datamapper/database confusion - ruby

I'm confused as to how database relationship works.
Say I have a Border Crossing('crossing'), which has two
Directions('north', 'south'), each of which direction has 2 types of lanes ('normal','fast'), each of which lane has 2 metrics (=data) ('delay','queue_length').
In reality there are several crossings, with more lane types and more metrics.
How the heck should I store that in a database? I've used databases before, but never did table joins or one-to-many or anything like that.
I came across Datamapper and since I'm learning how to us Sinatra I thought I'd give it a go.
In the tutorial (http://datamapper.org/getting-started.html), the "one-to-many" part just screamed "this is what you need", so I started fiddling around.
require 'data_mapper'
DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/development.db")
class Crossing
include DataMapper::Resource
property :id, Serial
property :name, String,:unique=>true
has n, :directions
end
class Direction
include DataMapper::Resource
property :id, Serial
property :direction, String,:unique=>true
belongs_to :crossing
has n, :lanes
end
class Lane
include DataMapper::Resource
property :id, Serial
property :lane, String
belongs_to :direction
has n, :datas
end
class Data
include DataMapper::Resource
property :id, Serial
property :name, String,:unique=>true
property :value, String
belongs_to :lane
end
DataMapper.finalize.auto_migrate!
I just thought this looked so elegantly put: "crossing has n directions, directions has n lanes, etc"
Then:
Crossing.create(:name => "crossing")
Direction.create(:direction => "north")
Direction.create(:direction => "south")
Lane.create(:lane => 'normal')
Lane.create(:lane => 'fast')
Data.create(:data => 'delay')
Data.create(:data => 'queue_length')
// now how do I retrieve find the data of a lane of a direction of a crossing?
Now, what I will input and retrieve all the time is the Data part. Does this whole thing make sense or I'm just not understanding what table associations are for? I know I could just have a gigantic object instead of this but I'm pretty sure that's a weird way of doing things.
#crossing = {
'crossing name' => {
:directions => {
:north => {
:normal => {
:delay => '10 min',
:queue => '100 m'
},
:fast => {
:delay => '1 min',
:queue => '10 m'
}
},
etc etc etc
}
and then access the data like #crossing[:north][:normal][:delay]....but I kinda feel like a database would be better?
Am I making any sense in any way? Anybody got some pointers for a young grasshoper?

I would rather go with this structure:
Data belongs to Crossing, Direction and Lane; it has properties for delay and queue
Direction has many Data, and has exactly two rows
Lane has many Data, and has exactly two rows
Crossing has many Data, and has many rows
The reason is, you don't want to repeat the strings "north", "south" etc in your database.
Then, first seed the database with constant tables:
Direction.create(direction: 'north')
Direction.create(direction: 'south')
Lane.create(lane: 'normal')
Lane.create(lane: 'fast')
Then you can make your crossings:
cool_crossing = Crossing.create(name: 'My Cool Crossing')
not_cool_crossing = Lane.create(name: 'My Not So Cool Crossing')
and add data points:
north = Direction.first(name: "north")
normal = Lane.first(name: "normal")
Data.create(
crossing: cool_crossing,
lane: normal,
direction: north,
delay: 10,
queue: 1
)
and retrieve data by:
all_data_for_cool_crossing = Data.all(
crossing: cool_crossing
)
or
data_for_cool_crossing_normal_north = Data.first(
crossing: cool_crossing,
lane: normal,
direction: north
)

Related

Skooma input validator

I was given a task to implement an input validator with the Skooma library https://github.com/bobfp/skooma#validators
The general concept is pretty clear, but for some inputs I have a list of "legal" words, and I have zero clue on how to implement the validation for this case.
Hence why I came here, I wanted to ask if you know any examples / projects that used this library? I googled but didn't find anything.
Of if you have any other tipps just let me know! 🙂
This is the example:
my schema:
schema = %{
:titel => :string,
:category => :string,
:high_level_category => :string,
:description => :string,
:potential_impacts => :string,
:affected_assets => :string,
:rating => :string }
The legal inputs for category:
category = %{core: 'Core network threats', access: 'Access network threats', multi: 'Multi edge computing threats',
virtualisation: 'Virtualisation threats', phyiscal: 'Physical infrastructure threats', generic: 'Generic threats'}
I tried it with a normal list as well, such as
category = ['Core network threats', 'Access network threats', 'Multi edge computing threats' .......]
But I just cant get my head around how to check if the :category is present in the category list.
You need a custom validator function, here's an example:
alias Skooma.Validators
#valid_categories [
"Access network threats",
"Core network threats",
"Generic threats",
"Multi edge computing threats",
"Physical infrastructure threats",
"Virtualisation threats"
]
def valid?(data), do: Skooma.valid?(data, schema())
defp schema,
do: %{
:category => [:string, inclusion(#valid_categories)],
... # rest of the schema
}
# copied from:
# https://github.com/bobfp/skooma/blob/master/lib/validators.ex#L38-L48
defp inclusion(values_list) when is_list(values_list) do
fn data ->
bool = data in values_list
if bool do
:ok
else
{:error, "Value is not included in the options: #{inspect(values_list)}"}
end
end
end
You can replace the inclusion function with Validators.inclusion/1. In this case, you'll need to install Skooma from Github because it has not been published as speaking today (31 Jan 2022).

How can I avoid duplication in a join query using Sequel with Postgres on Sinatra?

I want to do a simple join. I have two tables: "candidates" and "notes".
Not all candidates have notes written about them, some candidates have more than one note written about them. The linking fields are id in the candidates table and candidate_id in the notes table. The query is:
people = candidates.where(:industry => industry).where("country = ?", country).left_outer_join(:notes, :candidate_id => :id).order(Sequel.desc(:id)).map do |row|
{
:id => row[:id],
:first => row[:first],
:last => row[:last],
:designation => row[:designation],
:company => row[:company],
:email => row[:email],
:remarks => row[:remarks],
:note => row[:note]
}
end
It works kind of fine and gets all the specified candidates from the candidates table and the notes from the notes table but where there is more than one note it repeats the name of the candidate. In the resulting list, person "abc" appears twice or three times depending on the number of notes associated with that person.
I am not actually printing the notes in the HTML result just a "tick" if that person has notes and "--" if no notes.
I want the person's name to appear only once. I have tried adding distinct in every conceivable place in the query but it made no difference.
Any ideas?
In order for distinct to work, you need to make sure you are only selecting columns that you want to be distinct on. You could try adding this to the query
.select(:candidates__id, :first, :last, :designation, :company, :email, :remarks, Sequel.as({:notes=>nil}).as(:notes)).distinct
But you may be better off using a subselect instead of a join to check for the existence of notes (assuming you are using a decent database):
candidates.where(:industry => industry, :country=>country).select_append(Sequel.as({:id=>DB[:notes].select(:candidate_id)}, :note)).order(Sequel.desc(:id)).map do |row|
{ :id => row[:id], :first => row[:first], :last => row[:last], :designation => row[:designation], :company => row[:company], :email => row[:email], :remarks => row[:remarks], :note => row[:note] }
end

Trying to populate gmaps4rails with multiple json strings in one page

I hope I am asking this right, so please let me know if I'm way off.
The problem is trying to build a homepage that draws from multiple controllers, to display the nearest locations from multiple controllers, ie. food, businesses, ect.
Right now the individual listings pages have maps drawn from their respective
#json = Controller.all.to_gmaps4rails
How would I do something like :
#json = Controller1 Controller2 .all.to_gmaps4rails
I hope this isnt a noob question and I'm just having a bad day. Thanks guys!
edit 12.5.2011 #seanhill - this is one of the models, the other sections are very close to this format. First off, I wasn't even sure if my homepage requires it's own model, as it doesn't interact with the db at all, more pulling data from controllers that do the work. Thanks for the response Sean!
class Dining < ActiveRecord::Base
validates_uniqueness_of :name, :message => "already exists"
attr_accessible :name, :address, :cuisine, :latitude, :longitude, :about, :facebook, :twitter, :phone, :website
geocoded_by :address
after_validation :geocode, :if => :address_changed?
acts_as_gmappable :process_geocoding => false
def gmaps4rails_address
"#{self.address}"
end
def gmaps4rails_infowindow
"<h3>#{self.name}</h3><br /><h5>#{self.cuisine}</h5>"
end
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
else
scoped
end
end
end
Try this
holder = Controller1.all
holder << Controller2.all
#json = holder.flatten.map{|h| {lng: h.longitude, lat: h.latitude, class: h.class.to_s}}.to_json
Make sure to change longitude and latitude based on your column names and use js to manipulate the markers based upon class.
As the #Sean Hill said you shouldn't be calling .all on controllers but I think you have a slight misunderstanding of how things are working. Assuming you have a Model called Dining and another called Shop, when you call Dining.all or Shop.all inside class DiningsController < ApplicationController, you are calling .all on either the Dining Model or the Shop Model not on the DiningsController.
The information you display through a controller is only limited by the methods you call in it although it is best practice ensure the main focus of the information displayed is related to the respective controller.
So what you are really trying to do is get the records from multiple models and group them together to display them in a single map.
With that said the answer should read something like this
holder = Dining.all # Takes all Dining records returned as an array and sets them to holder variable
holder << Shop.all # Pushes the Shop records array into the holder with the dining records
holder.flatten!# Next we flatten the array so we only have a single array.
# Then we use the map method to run the given code one time for each instance
# in the holder array to extract the info we need. The results for every instance
# in holder are returned in an array which we then convert to_json.
#json = holder.map{|h| {lng: h.longitude, lat: h.latitude, class: h.class.to_s}}.to_json
#json1 = something.to_gmaps4rails
#json2 = something.to_gmaps4rails
#json = (JSON.parse(#json1) + JSON.parse(#json2)).to_json
I populated the map with my initial data of festivals, and then added the rides to it with javascript with this code,
<% content_for :scripts do %>
<script type="text/javascript">
Gmaps.map.callback = function() {
$.getJSON('/rides_gmap', function(data){
Gmaps.map.addMarkers(data);
});
}
</script>
<%end%>
In the rides controller I had this
def rides_gmap
#rides = Ride.all
#json = #rides.to_gmaps4rails do |ride, marker|
marker.infowindow render_to_string(:partial => "/rides/infowindow", :locals => { :ride => ride})
marker.picture({
'picture' => view_context.image_path("orange-dot.png"),
'width' => 20,
'height' => 20
})
marker.title "#{ride.address}"
marker.json({:ride_id => ride.id, :ride_festivaltype => ride.festival.festivaltype
end
respond_with #json
end
I hope this helps.

Chaining datamapper relationships across different repositories

class A
include DataMapper::Resource
def self.default_repository_name
:alt_db
end
property :aid, Integer, :key => true
# other stuff
belongs_to :b, :model => 'B', :child_key => [ :bid ]
end
class B
include DataMapper::Resource
# this one is in the default repo
property :bid, Integer, :key => true
# other stuff
belongs_to :c, :model => 'C', :child_key => [ :cid ]
end
class C
include DataMapper::Resource
# this one is in the default repo
property :cid, Integer, :key => true
# other stuff
end
If I just have A and B, this works fine. If I add C, however, I get an error:
dm-core/model/property.rb:73:in `new': wrong number of arguments (4 for 3) (ArgumentError)
If I want to make a chain of relationships with DataMapper, so that I can give an ID in one place and get a piece of data that's, say, four tables away through a series of references to subsequent tables' primary key ID field, how can I do this?
EDIT: Digging into the DM source from the stack trace:
DataMapper.repository(other_repository_name) do
properties << klass.new(self, name, options, type)
end
That's where the error is raised. Indeed, in this case klass is a DataMapper Integer property, and it's initialize method only accepts three options (model, name, and an options hash).
This whole block is only executed because I'm using more than one repository, though B and C are in the same one so I don't know if that sheds any light on why it's erroring on the cid property.
EDIT2:
I have tried all permutations, and it appears that when you're chaining, once you cross a database-boundary, that must be the end of the chain. For example, since A is :alt_db and B is :default, B is as deep as I can go, regardless of whether C is :default, :alt_db, or a third option.
If instead both A and B were :default, or both were :alt_db, and then C were the opposite one, C would be as deep as I could go.
I don't understand this behavior really, though.
You found a bug actually. It's been fixed in master. You can try grabbing sources from git and see if it works.
Your code works fine for me.
irb(main):001:0> A.first.b.c
DEBUG - "(0.001168) SELECT "aid", "bid" FROM "as" ORDER BY "aid" LIMIT 1"
DEBUG - "(0.000337) SELECT "bid", "cid" FROM "bs" WHERE "bid" = 2 LIMIT 1"
DEBUG - "(0.000046) SELECT "cid" FROM "cs" WHERE "cid" = 3 LIMIT 1"
=> #<C #cid=3>
My gem is dm-core-1.1.0, you should check your version.
It turns out this was a small issue with DataMapper chaining across repositories. Submitted to them and it's allegedly been fixed already!
http://datamapper.lighthouseapp.com/projects/20609/tickets/1506-can-only-chain-up-to-first-time-changing-default-repository#ticket-1506-1

Fetching values via foreign keys with DataMapper

I have two tables, nodes and terms.
The relevant fields in nodes are: nid (primary key) and value
In terms, they are: value, tid, and nid, where value and tid together are the primary key and nid is a foreign key referencing nodes.nid.
I want to add records to terms. I have the tid and nid, and the value I want to pull from the corresponding node - e.g. look up the value for a given nid in node and then put that as the value in terms.
A way to do this in SQL might be:
INSERT INTO terms(tid, nid, value)
values(mytid, mynid, (
select value from nodes where nid=mynid
));
Could somebody help me do this with DataMapper?
class Node
include DataMapper::Resource
property :nid, Serial, :key => true
property :value, Integer
end
class Term
include DataMapper::Resource
property :tid, Integer, :key => true
# how do I define nid and value?
end
# and then what do I give to Term.new or Term.create and how?
If anyone could point me to a good tutorial of DataMapper as well, I'd appreciate it. I've been using their online docs, but I've found the situations I find myself in are rarely covered there.
From your description the models you're looking for should be setup like that:
class Node
include DataMapper::Resource
property :nid, Serial
property :value, Integer
end
class Term
include DataMapper::Resource
property :tid, Integer, :key => true
property :value, Integer, :key => true
belongs_to :node, :child_key => :nid
end
You can work with these models like that:
# create a node
node = Node.create(:value => 123)
# create a term and associate it with the node
term = Term.create(:tid => 321, :node => node, :value => node.value)

Resources