aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhellekin <hellekin@cepheide.org>2020-10-09 10:24:06 +0200
committerhellekin <hellekin@cepheide.org>2020-10-09 10:24:06 +0200
commitf48ce2f4c934fde3862cdad593eececc7a567d61 (patch)
treed2c874f8ebf4686d07052fa333032c04ef49e861
parent31850c6ca118b7828dbaa3ad1a87dab4287718f5 (diff)
downloadincommon-map-f48ce2f4c934fde3862cdad593eececc7a567d61.tar.gz
Add Classifications and Resource validations
- Turn `has_and_belongs_to_many` into `has_many :through`: now, resources and sections are related through Classifications. - Refactor usage of jsonb column to use ActiveRecord validations - Attention! store_accessor: NOTE: If you are using structured database data types (eg. PostgreSQL hstore/json, or MySQL 5.7+ json) there is no need for the serialization provided by .store. Simply use .store_accessor instead to generate the accessor methods. Be aware that these columns use a string keyed hash and do not allow access using a symbol. NOTE: The default validations with the exception of uniqueness will work. For example, if you want to check for uniqueness with hstore you will need to use a custom validation to handle it. https://api.rubyonrails.org/classes/ActiveRecord/Store.html
-rw-r--r--Gemfile9
-rw-r--r--Gemfile.lock10
-rw-r--r--app/models/agent.rb11
-rw-r--r--app/models/classification.rb4
-rw-r--r--app/models/resource.rb68
-rw-r--r--app/models/schemas/resource_feature_properties.json12
-rw-r--r--app/models/section.rb5
-rw-r--r--app/serializers/hash_serializer.rb9
-rw-r--r--db/migrate/20201009025353_add_default_to_resource_feature.rb30
-rw-r--r--db/migrate/20201009061548_rename_resources_sections_to_classifications.rb5
-rw-r--r--db/schema.rb15
11 files changed, 147 insertions, 31 deletions
diff --git a/Gemfile b/Gemfile
index e11ac05..1897dd4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -32,11 +32,14 @@ gem 'bitfields'
gem 'discourse_api'
# User pagination
gem 'kaminari'
+# Use of Leaflet maps
+gem 'leaflet-rails'
+# Validate phone numbers
+gem 'phony_rails'
# Enforce stable UUIDs for models
gem 'uuid_parameter', '~> 0.2.5'
-
-#Enable use of leaflet
-gem 'leaflet-rails'
+# Validate URLs in models
+gem 'validate_url'
# Reduces boot times through caching; required in config/boot.rb
diff --git a/Gemfile.lock b/Gemfile.lock
index 96d66e2..aa16165 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -120,11 +120,16 @@ GEM
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
pg (1.2.3)
+ phony (2.18.15)
+ phony_rails (0.14.13)
+ activesupport (>= 3.0)
+ phony (> 2.15)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
+ public_suffix (4.0.6)
puma (4.3.6)
nio4r (~> 2.0)
puma-plugin-systemd (0.1.5)
@@ -196,6 +201,9 @@ GEM
thread_safe (~> 0.1)
uuid_parameter (0.2.6)
rails (>= 5.2.1)
+ validate_url (1.0.13)
+ activemodel (>= 3.0.0)
+ public_suffix
web-console (4.0.4)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
@@ -224,6 +232,7 @@ DEPENDENCIES
leaflet-rails
listen (~> 3.2)
pg (>= 0.18, < 2.0)
+ phony_rails
pry
pry-rails
puma (~> 4.1)
@@ -235,6 +244,7 @@ DEPENDENCIES
turbolinks (~> 5)
tzinfo-data
uuid_parameter (~> 0.2.5)
+ validate_url
web-console (>= 3.3.0)
webpacker (~> 4.0)
diff --git a/app/models/agent.rb b/app/models/agent.rb
index 07e0b8e..6264ba3 100644
--- a/app/models/agent.rb
+++ b/app/models/agent.rb
@@ -2,4 +2,15 @@ class Agent < ApplicationRecord
has_many :agencies
has_many :members, through: :agencies, source: :user
has_many :resources
+ has_many :taxonomies
+ has_many :categories, through: :taxonomies
+ has_many :sections, through: :categories
+
+ def to_param
+ uuid
+ end
+
+ def to_s
+ name
+ end
end
diff --git a/app/models/classification.rb b/app/models/classification.rb
new file mode 100644
index 0000000..c384aa7
--- /dev/null
+++ b/app/models/classification.rb
@@ -0,0 +1,4 @@
+class Classification < ApplicationRecord
+ belongs_to :resource
+ belongs_to :section
+end
diff --git a/app/models/resource.rb b/app/models/resource.rb
index cd43bf9..906933d 100644
--- a/app/models/resource.rb
+++ b/app/models/resource.rb
@@ -1,26 +1,56 @@
+require_dependency 'phony_rails'
+
class Resource < ApplicationRecord
# Universally Unique Identifier :uuid
include UUIDParameter
belongs_to :agent
- has_and_belongs_to_many :sections
-
- # Figure out the requested property name
- def method_missing(name, *args, &block)
- Rails.logger.info("method_missing: #{name} // #{feature['properties'][name.to_s]}")
- if feature['properties'].key?(name.to_s)
- feature['properties'][name.to_s]
- else
- case name.to_s
- when 'lon', 'longitude'
- feature['geometry']['coordinates'].first
- when 'lat', 'latitude'
- feature['geometry']['coordinates'].last
- when 'geo_type'
- feature['geometry']['type']
- else
- super
- end
- end
+ has_many :classifications
+ has_many :sections, through: :classifications
+
+ serialize :feature, HashSerializer
+ store_accessor :feature, :name, :summary, :description, :email, :source, :address, :postal_code, :city, :phone_number, :website
+
+ validates_associated :agent
+
+ validates :name,
+ presence: true,
+ length: { in: 3..64 }
+
+ validates :email,
+ with: { format: URI::MailTo::EMAIL_REGEXP },
+ allow_blank: true
+
+ validates :source,
+ inclusion: { in: Agent.pluck(:name) }
+
+ # TODO: Address,Postal Code,City validation
+
+ phony_normalize :phone_number, default_country_code: 'BE', normalize_when_valid: true
+ validates :phone_number,
+ phony_plausible: { ignore_record_country_code: true, ignore_record_country_number: true }
+
+ # Depends on validate_url Gem
+ validates :website,
+ url: { allow_blank: true }
+
+ # Accessors for feature['geometry']
+ def geo_type
+ self.feature['geometry']['type']
+ end
+
+ # You can use, e.g.: res.longitude = 0.123
+ def longitude
+ feature['geometry']['coordinates'][0]
+ end
+ def longitude=(value)
+ feature['geometry']['coordinates'][0] = value
+ end
+ # You can use, e.g.: res.latitude = 0.123
+ def latitude
+ feature['geometry']['coordinates'][1]
+ end
+ def latitude=(value)
+ feature['geometry']['coordinates'][1] = value
end
end
diff --git a/app/models/schemas/resource_feature_properties.json b/app/models/schemas/resource_feature_properties.json
new file mode 100644
index 0000000..84cd6ae
--- /dev/null
+++ b/app/models/schemas/resource_feature_properties.json
@@ -0,0 +1,12 @@
+// JSON Schema for Resource#feature
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://api.incommon.cc/schema/resource_feature.json",
+ "title":
+ "type": "object",
+ "required": [],
+ "geometry": {
+ },
+ "properties": {
+ }
+}
diff --git a/app/models/section.rb b/app/models/section.rb
index 6cfeb38..7d26882 100644
--- a/app/models/section.rb
+++ b/app/models/section.rb
@@ -1,11 +1,12 @@
class Section < ApplicationRecord
belongs_to :category
has_one :taxonomy, through: :category
- has_and_belongs_to_many :resources
+ has_many :classifications
+ has_many :resources, through: :classifications
acts_as_list column: :rank, scope: :category
validates :name,
uniqueness: { scope: :category_id },
- length: 3..64
+ length: { in: 3..64 }
end
diff --git a/app/serializers/hash_serializer.rb b/app/serializers/hash_serializer.rb
new file mode 100644
index 0000000..5db639f
--- /dev/null
+++ b/app/serializers/hash_serializer.rb
@@ -0,0 +1,9 @@
+class HashSerializer
+ def self.dump(hash)
+ hash.to_json
+ end
+
+ def self.load(hash)
+ (hash || {}).with_indifferent_access
+ end
+end
diff --git a/db/migrate/20201009025353_add_default_to_resource_feature.rb b/db/migrate/20201009025353_add_default_to_resource_feature.rb
new file mode 100644
index 0000000..06cad6c
--- /dev/null
+++ b/db/migrate/20201009025353_add_default_to_resource_feature.rb
@@ -0,0 +1,30 @@
+class AddDefaultToResourceFeature < ActiveRecord::Migration[6.0]
+ def up
+ change_column_default :resources, :feature,
+ {
+ "geometry": {
+ "type": "Point",
+ "coordinates": [0,0]
+ },
+ "properties": {
+ "name":"",
+ "description":"",
+ "address":"",
+ "postal_code":"",
+ "city":"",
+ "email":"",
+ "phone_number":"",
+ "website":"",
+ "categories":[],
+ "source":"incommon",
+ "entry_number":nil,
+ "srid":4326
+ }
+ }
+ add_index :resources, :feature, using: :gin
+ end
+ def down
+ change_column_default :resources, :feature, nil
+ remove_index :resources, :feature
+ end
+end
diff --git a/db/migrate/20201009061548_rename_resources_sections_to_classifications.rb b/db/migrate/20201009061548_rename_resources_sections_to_classifications.rb
new file mode 100644
index 0000000..4f7972b
--- /dev/null
+++ b/db/migrate/20201009061548_rename_resources_sections_to_classifications.rb
@@ -0,0 +1,5 @@
+class RenameResourcesSectionsToClassifications < ActiveRecord::Migration[6.0]
+ def change
+ rename_table :resources_sections, :classifications
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 16ba65c..0660ef9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_10_08_190558) do
+ActiveRecord::Schema.define(version: 2020_10_09_061548) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -50,21 +50,22 @@ ActiveRecord::Schema.define(version: 2020_10_08_190558) do
t.index ["taxonomy_id"], name: "index_categories_on_taxonomy_id"
end
+ create_table "classifications", id: false, force: :cascade do |t|
+ t.bigint "resource_id", null: false
+ t.bigint "section_id", null: false
+ end
+
create_table "resources", force: :cascade do |t|
t.uuid "uuid"
- t.jsonb "feature"
+ t.jsonb "feature", default: {"geometry"=>{"type"=>"Point", "coordinates"=>[0, 0]}, "properties"=>{"city"=>"", "name"=>"", "srid"=>4326, "email"=>"", "source"=>"incommon", "address"=>"", "website"=>"", "categories"=>[], "description"=>"", "postal_code"=>"", "entry_number"=>nil, "phone_number"=>""}}
t.bigint "agent_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["agent_id"], name: "index_resources_on_agent_id"
+ t.index ["feature"], name: "index_resources_on_feature", using: :gin
t.index ["uuid"], name: "index_resources_on_uuid", unique: true
end
- create_table "resources_sections", id: false, force: :cascade do |t|
- t.bigint "resource_id", null: false
- t.bigint "section_id", null: false
- end
-
create_table "sections", force: :cascade do |t|
t.string "name", limit: 64
t.string "summary", limit: 136