require'uri/generic'require'active_support/core_ext/module/aliasing'require'active_support/core_ext/object/blank'require'active_support/core_ext/hash/indifferent_access'moduleURIclassGID<Generic# URI::GID encodes an app unique reference to a specific model as an URI.# It has the components: app name, model class name, model id and params.# All components except params are required.## The URI format looks like "gid://app/model_name/model_id".## Simple metadata can be stored in params. Useful if your app has multiple databases,# for instance, and you need to find out which one to look up the model in.## Params will be encoded as query parameters like so# "gid://app/model_name/model_id?key=value&another_key=another_value".## Params won't be typecast, they're always strings.# For convenience params can be accessed using both strings and symbol keys.## Multi value params aren't supported. Any params encoding multiple values under# the same key will return only the last value. For example, when decoding# params like "key=first_value&key=last_value" key will only be last_value.## Read the documentation for +parse+, +create+ and +build+ for more.alias:app:hostattr_reader:model_name,:model_id,:params# Raised when creating a Global ID for a model without an idclassMissingModelIdError<URI::InvalidComponentError;endclass<<self# Validates +app+'s as URI hostnames containing only alphanumeric characters# and hyphens. An ArgumentError is raised if +app+ is invalid.## URI::GID.validate_app('bcx') # => 'bcx'# URI::GID.validate_app('foo-bar') # => 'foo-bar'## URI::GID.validate_app(nil) # => ArgumentError# URI::GID.validate_app('foo/bar') # => ArgumentErrordefvalidate_app(app)parse("gid://#{app}/Model/1").apprescueURI::ErrorraiseArgumentError,'Invalid app name. '\'App names must be valid URI hostnames: alphanumeric and hyphen characters only.'end# Create a new URI::GID by parsing a gid string with argument check.## URI::GID.parse 'gid://bcx/Person/1?key=value'## This differs from URI() and URI.parse which do not check arguments.## URI('gid://bcx') # => URI::GID instance# URI.parse('gid://bcx') # => URI::GID instance# URI::GID.parse('gid://bcx/') # => raises URI::InvalidComponentErrordefparse(uri)generic_components=URI.split(uri)<<nil<<true# nil parser, true arg_checknew(*generic_components)end# Shorthand to build a URI::GID from an app, a model and optional params.## URI::GID.create('bcx', Person.find(5), database: 'superhumans')defcreate(app,model,params=nil)buildapp: app,model_name: model.class.name,model_id: model.id,params: paramsend# Create a new URI::GID from components with argument check.## The allowed components are app, model_name, model_id and params, which can be# either a hash or an array.## Using a hash:## URI::GID.build(app: 'bcx', model_name: 'Person', model_id: '1', params: { key: 'value' })## Using an array, the arguments must be in order [app, model_name, model_id, params]:## URI::GID.build(['bcx', 'Person', '1', key: 'value'])defbuild(args)parts=Util.make_components_hash(self,args)parts[:host]=parts[:app]parts[:path]="/#{parts[:model_name]}/#{CGI.escape(parts[:model_id].to_s)}"ifparts[:params]&&!parts[:params].empty?parts[:query]=URI.encode_www_form(parts[:params])endsuperpartsendenddefto_s# Implement #to_s to avoid no implicit conversion of nil into string when path is nil"gid://#{app}#{path}#{'?'+queryifquery}"endprotecteddefset_path(path)set_model_components(path)unlessdefined?(@model_name)&&@model_idsuperend# Ruby 2.2 uses #query= instead of #set_querydefquery=(query)set_paramsparse_query_params(query)superend# Ruby 2.1 or less uses #set_query to assign the querydefset_query(query)set_paramsparse_query_params(query)superenddefset_params(params)@params=paramsendprivateCOMPONENT=[:scheme,:app,:model_name,:model_id,:params].freeze# Extracts model_name and model_id from the URI path.PATH_REGEXP=%r(\A/([^/]+)/?([^/]+)?\z)defcheck_host(host)validate_component(host)superenddefcheck_path(path)validate_component(path)set_model_components(path,true)enddefcheck_scheme(scheme)ifscheme=='gid'superelseraiseURI::BadURIError,"Not a gid:// URI scheme: #{inspect}"endenddefset_model_components(path,validate=false)_,model_name,model_id=path.match(PATH_REGEXP).to_amodel_id=CGI.unescape(model_id)ifmodel_idvalidate_component(model_name)&&validate_model_id(model_id,model_name)ifvalidate@model_name=model_name@model_id=model_idenddefvalidate_component(component)returncomponentunlesscomponent.blank?raiseURI::InvalidComponentError,"Expected a URI like gid://app/Person/1234: #{inspect}"enddefvalidate_model_id(model_id,model_name)returnmodel_idunlessmodel_id.blank?raiseMissingModelIdError,"Unable to create a Global ID for "\"#{model_name} without a model id."enddefparse_query_params(query)Hash[URI.decode_www_form(query)].with_indifferent_accessifqueryendendifrespond_to?(:register_scheme)register_scheme('GID',GID)else@@schemes['GID']=GIDendend