diff options
author | IN COMMON Collective <collective@incommon.cc> | 2021-04-08 16:34:15 +0200 |
---|---|---|
committer | IN COMMON Collective <collective@incommon.cc> | 2021-04-08 16:34:15 +0200 |
commit | d14700c51d692335f001a93c2f6b13b135783206 (patch) | |
tree | 1204bc1ae744098eba6604a961765187984a90d8 /app/models | |
parent | c738e96b2b99bfd92b70d4cec26d6874a7f609e4 (diff) | |
download | incommon-map-d14700c51d692335f001a93c2f6b13b135783206.tar.gz |
[FIX] Use form model to create/edit resources (fixes #4, fixes #5, refs #3)v0.1.10
Since we must associate other models (e.g., classifications) to a Resource,
we use a composite model to save all changes inside a database transaction.
This approach makes it simpler to handle resources and their associations.
Work remains to fix the geolocation and reverse geolocation to ensure
these are in sync.
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/classification.rb | 2 | ||||
-rw-r--r-- | app/models/resource.rb | 11 | ||||
-rw-r--r-- | app/models/resource_form.rb | 138 |
3 files changed, 146 insertions, 5 deletions
diff --git a/app/models/classification.rb b/app/models/classification.rb index 0754054..e2b180f 100644 --- a/app/models/classification.rb +++ b/app/models/classification.rb @@ -5,4 +5,6 @@ class Classification < ApplicationRecord belongs_to :resource belongs_to :section + + self.primary_key = :resource_id end diff --git a/app/models/resource.rb b/app/models/resource.rb index c603978..b803afb 100644 --- a/app/models/resource.rb +++ b/app/models/resource.rb @@ -21,7 +21,7 @@ class Resource < ApplicationRecord length: { in: 3..64 } validates :email, - with: { format: URI::MailTo::EMAIL_REGEXP }, + format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true validates :source, @@ -31,7 +31,8 @@ class Resource < ApplicationRecord 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 } + phony_plausible: { ignore_record_country_code: true, ignore_record_country_number: true }, + allow_blank: true # Depends on validate_url Gem validates :website, @@ -47,19 +48,19 @@ class Resource < ApplicationRecord format('%<lon>3.7f', lon: feature['geometry']['coordinates'][0]).to_f end def longitude=(value) - feature['geometry']['coordinates'][0] = format('%<lon>3.7f', lon: value).to_f + feature['geometry']['coordinates'][0] = format('%<lon>3.7f', lon: value.to_f).to_f end # You can use, e.g.: res.latitude = 0.123 def latitude format('%<lat>2.7f', lat: feature['geometry']['coordinates'][1]).to_f end def latitude=(value) - feature['geometry']['coordinates'][1] = format('%<lat>2.7f', lat: value).to_f + feature['geometry']['coordinates'][1] = format('%<lat>2.7f', lat: value.to_f).to_f end # Properties - [:name, :summary, :description, :email, :source, :address, :postal_code, :city, :phone_number, :website].each do |prop| + [:name, :summary, :description, :email, :source, :address, :postal_code, :city, :phone_number, :website, :entry_number].each do |prop| # Define a reader define_method prop do properties[prop.to_s] diff --git a/app/models/resource_form.rb b/app/models/resource_form.rb new file mode 100644 index 0000000..14275a4 --- /dev/null +++ b/app/models/resource_form.rb @@ -0,0 +1,138 @@ +class ResourceForm + include ActiveModel::Model + + attr_accessor :agent, :resource + attr_accessor :agent_id, :uuid, :name, :summary, :description, :email, + :website, :phone_number, :address, :postal_code, :city, + :entry_number, :latitude, :longitude, :section_ids, :source + + with_options presence: true do + validates :name + validates :email, allow_blank: true +# validates :source + validates :phone_number, allow_blank: true + validates :website, allow_blank: true + end + + validate :validate_models + + def initialize(agent, attributes = nil) + @agent = agent + @attributes = attributes || default_attributes + + raise ArgumentError "Attributes must be permitted" unless @attributes.permitted? + + # We use an empty hidden field to ensure this is always set, even if no + # section is selected, so let's remove the empty value + @section_ids = @attributes.extract!(:section_ids)[:section_ids].filter_map { |x| x.presence } rescue [] + # We may have an existing and correct source, but anyway we must have one + @resource = @agent.resources.build({ source: @agent.name }.merge(@attributes)) + end + + def new_record? + resource&.new_record? || true + end + + # New forms can provide an existing classification, + # and updates can prefer passed arguments to ensure correct changes + def section_ids + resource.present? && @section_ids.empty? ? resource.section_ids : @section_ids + end + + # Ensure field values stick around + # OMG this is ugly + # TODO: use delegator or metaprog + def name + @name ||= resource&.name + end + def summary + @summary ||= resource&.summary + end + def description + @description ||= resource&.description + end + def email + @email ||= resource&.email + end + def website + @website ||= resource&.website + end + def phone_number + @phone_number ||= resource&.phone_number + end + def address + @address ||= resource&.address + end + def postal_code + @postal_code ||= resource&.postal_code + end + def city + @city ||= resource&.city + end + def longitude + @longitude ||= resource&.longitude + end + def latitude + @latitude ||= resource&.latitude + end + + + def save + Rails.logger.info "--- Calling SAVE ---" + return false if invalid? + + ActiveRecord::Base.transaction do + resource.save! && \ + @section_ids.each do |sec_id| + Rails.logger.info("section " + sec_id) + resource.categories << resource.classifications.find_or_create_by!(section_id: sec_id) + end + + true + rescue ActiveRecord::StatementInvalid => e + errors.add(:base, e.message) + false + end + + rescue Exception => e + Rails.logger.info "Unhandled Exception #{e.class}: #{e.message}" + false + end + + private + + def default_attributes + ActionController::Parameters.new( + { + agent_id: @agent.id, + uuid: '', + name: '', + summary: '', + description: '', + email: '', + website: '', + phone_number: '', + address: '', + postal_code: '', + city: '', + entry_number: nil, + latitude: 0.0, + longitude: 0.0, + source: @agent.name, + section_ids: [] + }).permit! + end + + # Validate underlying models + def validate_models + Rails.logger.info('--- validate_models ---') + resource.valid? || promote_errors(resource.errors) + end + + # Promote an error from the Model to the Form + def promote_errors(model_errors) + model_errors.each do |attribute, error| + errors.add(attribute, error.full_message) + end + end +end |