Class: AstroboaCLI::Command::Model

Inherits:
Base
  • Object
show all
Defined in:
lib/astroboa-cli/command/model.rb

Overview

Manage your domain models

Astroboa facilitates a DOMAIN DRIVEN design of applications. The core of your application(s) is the DOMAIN MODEL, a graph of ENTITIES that describe the type of information your application(s) will create, consume and search.

In order to store your application(s) data you follow three simple steps: 1) you create an astroboa repository (astroboa-cli repository:create) 2) you create your domain model by:

- using the Astroboa Entity Definition DSL which is very close to ActiveRecord::Schema definitions of ruby on rails
- directy writing an XML Schema for each entity (or a single schema to include all entity definitions)

3) you “associate” your domain model with the repository you just created (astroboa-cli model:associate)

One of the best features of astroboa is its programming-language agnostic and dynamic domain model that can be shared between applications. You do not need to create model classes for your applications. Astroboa uses the domain model that you define once and dynamically creates the appropriate objects for your app. Additionally you can “model-as-you-go”, that is you may define new entities or update existing ones at any time during development or production. Astroboa will automatically update the APIs and the generated object instances to the updated domain model.

Instance Attribute Summary

Attributes inherited from Base

#args, #log, #log_file, #options

Instance Method Summary collapse

Methods inherited from Base

#initialize, namespace

Methods included from Util

#ask, #astroboa_running?, #check_if_running_with_sudo, #create_postgresql_db, #create_postgresql_db_with_jdbc, #delete_file_content_between_regex, #delete_file_lines, #dir_writable?, #display, #drop_postgresql_db, #drop_postgresql_db_with_jdbc, #error, #extract_archive_command, #fail, #format_with_bang, #gem_available?, #get_postgresql_config, #get_server_conf_file, #get_server_configuration, #has_executable, #has_executable_with_version, #has_version_in_grep, #jruby_ok?, #jruby_version_ok?, #linux?, #longest, #mac_os_x?, #output_with_bang, #process_os_command, #render_template_to_file, #repository?, #repository_in_repos_config?, #repository_in_server_config?, #ruby_ok?, #ruby_version_ok?, #running_with_sudo?, #runs_with_jruby?, #save_server_configuration, #shell, #strip_text_nodes, #unzip_file, #windows?, #write_xml

Constructor Details

This class inherits a constructor from AstroboaCLI::Command::Base

Instance Method Details

#add_propertyObject

model:add_property PROPERTY_NAME OBJECT_TYPE

Adds a new property with name ‘PROPERTY_NAME’ to ‘OBJECT_TYPE’. If ‘–update’ is specified it updates an existing property

If you specify the ‘MODELS_DIR’ (i.e. where your models are stored) then the XML Schema that contains the object type definition is expected to be found in ‘MODELS_DIR/xsd/OBJECT_TYPE.xsd’ If you do not specify the ‘MODELS_DIR’ then the XML Schema that contains the object type definition is expected to be found inside the current directory in ‘models/xsd/OBJECT_TYPE.xsd’

-d, –models_dir MODELS_DIR # The directory (absolute path) where your models are stored. # Default is ‘./models’ # The XML Schema file that contains the object type definition is expected to be found in ‘MODELS_DIR/xsd/OBJECT_TYPE.xsd’ -t, –type TYPE # The property type. # Accepted types are: string,integer,double,boolean,date,dateTime,binary,topic_ref,object_ref,complex,. Default type is ‘string’. -x, –max_values MAX_NUM_OF_VALUES # The maximum number of values that are permitted for this property. # Specify ‘1’ if you want to store only one value. # Specify ‘unbounded’ if you want to store a list of values without an upper limit in list size. # Default is ‘1’. -n, –min_values MIN_NUM_VALUES # The minimum number of values that are permitted for this property. # Specify ‘0’ to designate a non mandatory field. # Specify ‘1’ to designate a mandatory field. # Default is ‘0’. -p, –parent PROPERTY_NAME # The name of an EXISTING property that will contain this property. # The parent property should always be of type ‘complex’. # Properties of type ‘complex’ act as a grouping container for their child properties. # Imagine them as named fieldsets of a form # E.g. The ‘complex’ property ‘address’ is the parent (container) of the properties ‘street’, ‘city’, ‘zipcode’, ‘country’. # You may arbitrarily nest ‘complex’ properties inside other ‘complex’ properties to create property trees. -l, –localized_labels PROPERTY_NAME_LABELS # Provide friendly names for the property in different languages # By default the ‘PROPERTY_NAME’ will be used as the english label # Example: -l en:First Name,fr:Prénom,es:Primer Nombre -u, –update # Use this option to specify whether you want to update an EXISTING property. # If the property with ‘PROPERTY_NAME’ exists and you do not use this option then NO UPDATE will be performed. # The default values for property attributes are not used during a property update. # ONLY the property attributes that are expicitly specified with the options will be updated. # E.g. if you give ‘astroboa-cli model:add_property age person -t integer –update’ # and the property ‘age’ exists then only its ‘type’ will be changed. # The ‘minValues’, ‘maxValues’, ‘localized_labels’ will keep their existing values.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/astroboa-cli/command/model.rb', line 227

def add_property
  if property_name = args.shift
    property_name = property_name.strip
  else
    error "Please specify a name for the property. Usage: model:add_property PROPERTY_NAME OBJECT_TYPE"
  end
  
  if object_type = args.shift
    object_type = object_type.strip
  else
    error "Please specify the object type for which you want to add / update a property. Usage: model:add_property PROPERTY_NAME OBJECT_TYPE"
  end
  
  models_dir = options[:models_dir] ||= File.join(Dir.getwd, 'models')
  xsd_dir = File.join models_dir, 'xsd'
  schema_file = File.join xsd_dir, "#{object_type}.xsd"
  
  error <<-MSG unless File.exists? schema_file
  XML Schema file #{schema_file} does not exist.
  The XML Schema that contains the object type definition 
  is expected to be found in 'MODELS_DIR/xsd/OBJECT_TYPE.xsd'
  If you do not specify the MODELS_DIR with '--models_dir MODELS_DIR' 
  the default is './models'
  MSG
  
  localized_labels = options[:localized_labels] ||= "en:#{property_name}"
  localized_labels_map = {}
  localized_labels.split(',').each {|loc_lab| loc_lab_array = loc_lab.split(':'); localized_labels_map[loc_lab_array[0]] = loc_lab_array[1]}
  
  type_specified = options.has_key? :type
  max_values_specified = options.has_key? :max_values
  min_values_specified = options.has_key? :min_values
  parent_specified = options.has_key? :parent
  type = options[:type] ||= 'string'
  max_values = options[:max_values] ||= '1'
  min_values = options[:min_values] ||= '0'
  parent = options[:parent]

  schema = nil
  File.open(schema_file, 'r') do |f|
    schema = Nokogiri::XML(f) do |config|
      config.noblanks
    end
  end
  
  
  # find if specified property is already defined
  property_node_set = schema.xpath "//xs:element[@name='#{property_name}']"
  if property_node_set.length == 0  # property is not defined
    # if a parent has been specified check if it exists
    error <<-MSG if parent && schema.xpath("//xs:element[@name='#{parent}']").length == 0
    The parent property '#{parent}' you specified does not exist. 
    Please check the XML Schema at '#{schema_file}' and run the command again with a
    existing parent property.
    MSG
    display "property '#{property_name}' is not yet defined. Lets create it..."
    property = create_property schema, object_type: object_type, name: property_name, 
      type: type, min_values: min_values, max_values: max_values, i18n: localized_labels_map
      
    write_xml schema, schema_file
    display <<-MSG
    Create new property '#{property_name}' for object type '#{object_type}': OK
    The new property is now defined in file: #{schema_file}
    The xml schema definition for the new property is: 
    #{property.to_xml indent: 1, indent_text: "\t", encoding: 'UTF-8'}
    MSG
  else
    display "property '#{property_name}' is already defined. Lets update it..."
    error "property update is not yet supported"
  end
end

#associateObject

model:associate REPOSITORY [MODELS_DIR]

This command allows you to associate a repository with a domain model. After the association is done your repository can store entities that follow the domain model.

It is recommended to use this command either to bootstrap new repositories with a domain model or use it with CAUTION to update the domain model of an existing repository. It is SAFE to use it for domain model updates ONLY WHEN YOU ADD new object types or add new properties to existing types. This command does not cope with model updates that change existing object type names or change existing property names, property types and property value cardinality. I you do such updates and use this command, it may render your data inaccessible. In any case it will not delete any existing data so you can instantly recover your data visibility if you put back your old model.

The command can be used both when the server is stoppped as well as when the server is up. So you can do LIVE UPDATES to your schema but be warned that a schema change will cause a few seconds performace decrease on a live system.

If in some case you need to change existing object type names or change existing property names, property types and property value cardinality in your domain model then use ‘astroboa-cli model:propagate_updates’ in order to propagate the changes to a repository. This might require to alter data in the repository as opposed to ‘model:associate’ that never touches the stored data and so it should be used ONLY if you ADD new features to your model.

If you specify the ‘MODELS_DIR’ (i.e. where your models are stored) then your DSL model definition is expected to be in ‘MODELS_DIR/dsl’ and your XML Schemas to be in ‘MODELS_DIR/xsd’ If you do not specify the ‘MODELS_DIR’ then the domain model is expected to be found inside the current directory in ‘models/dsl’ and ‘models/xsd’



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/astroboa-cli/command/model.rb', line 55

def associate
  
  if repository = args.shift
    repository = repository.strip
  else
    error "Please specify the repository name. Usage: model:associate REPOSITORY MODEL_DIR"
  end
  
  server_configuration = get_server_configuration
  
  error "Repository '#{repository}' does not exist or it is not properly configured (use astroboa-cli repository:list to see available repositories)" unless repository?(server_configuration, repository)
  
  if models_dir = args.shift
    models_dir = models_dir.strip
  else
    models_dir = File.join(Dir.getwd, 'models')
  end
  
  error <<-MSG unless Dir.exists? models_dir
  Directory #{models_dir} does not exist. 
  If you specify the 'MODELS_DIR' then your DSL model definition is expected to be 
  in 'MODELS_DIR/dsl' and your XML Schemas to be in 'MODELS_DIR/xsd' 
  If you do not specify the 'MODELS_DIR' then domain model is expected to be found inside current directory in 'models/dsl' and 'models/xsd'
  MSG
  
  astroboa_dir = server_configuration['install_dir']
  
  display "Looking for XML Schemas..."
  xsd_dir = File.join models_dir, 'xsd'
  models_contain_xsds = Dir.exists?(xsd_dir) && Dir.entries(xsd_dir) != [".", ".."]
  
  if models_contain_xsds
    display "Found XML Schemas in '#{xsd_dir}'"
    display "Validating XML Schemas..."
    
    tmp_dir = File.join(astroboa_dir, 'tmp')
    
    FileUtils.rm_r tmp_dir if Dir.exists? tmp_dir
    
    FileUtils.mkdir_p tmp_dir
    FileUtils.cp_r File.join(astroboa_dir, 'schemas'), tmp_dir
    
    tmp_schema_dir = File.join(tmp_dir, 'schemas')
    FileUtils.cp_r Dir.glob(File.join(xsd_dir, '*.xsd')), tmp_schema_dir
    
    # Validate schemas in domain model
    Dir[File.join(xsd_dir, '*.xsd')].each  do |schema_path|
      schema_file = schema_path.split('/').last
      
      display
      display '-------------------------------------------------'
      display "Validating XML Schema: #{schema_file}"
      error "Please correct the schema file '#{schema_file}' and run the command again" unless domain_model_valid? schema_file, tmp_schema_dir
    end
    
    # copy schemas to repository schemas directory
    repository_schema_dir = File.join(server_configuration['repos_dir'], repository, 'astroboa_schemata')
    FileUtils.cp_r Dir.glob(File.join(xsd_dir, '*.xsd')), repository_schema_dir
    
    display
    display '-------------------------------------------------'
    display '-------------------------------------------------'
    display "Repository '#{repository}' associated to XML Schemas in '#{xsd_dir}': OK"
  else
    display "No XML Schemas Found"
  end
  
  
end

#create_object_typeObject

model:create_object_type NAME

Creates a new object type. The name of the new object type (the class name in programming terms) will be NAME

It will create a new XML Schema and will add the required definitions (namespaces, imports, xml tags) for the new object type. The generated file will be named ‘NAME.xsd’ If you do not specify a namespace for your new object type (using –namespace) then the namespace ‘astroboa/schema/NAME’ will be used by default

If you specify the ‘MODELS_DIR’ (i.e. where your models are stored) then the XML Schema file will be written inside ‘MODELS_DIR/xsd/’ (‘MODELS_DIR/xsd’ will be created if it does not exist) If you do not specify the ‘MODELS_DIR’ then the XML Schema file will be written in ‘models/xsd/’ inside the current directory (‘./models/xsd’ will be created if it does not exist).

The created object type will not have any properties. Use the ‘model:add_property’ command to add properties to your new type.

NOTE: If you are creating XML Schema files manually besides using this command please follow the same convension that this command follows, that is to keep each object type in a separate file and name your file after the name of the type.

-d, –models_dir MODELS_DIR # The directory (absolute path) where your models are stored. # The generated XML Schema file will be written inside ‘MODELS_DIR/xsd/’ # If directory MODELS_DIR/xsd does not exist, it will be created. -n, –namespace NAMESPACE # The namespace to be used for your new object type # (i.e. the corresponding entity tag will be namespaced with the provided namespace) # If not specified, the default namespace ‘astroboa/schema/NAME’ will be used (NAME is the name of your object type). -l, –localized_labels OBJECT_TYPE_LABELS # Provide friendly object type names for different languages # By default the ‘NAME’ of the object type will be used as the english label # Example: -l en:Movie,fr:Film,es:Filme



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/astroboa-cli/command/model.rb', line 166

def create_object_type
  if object_type = args.shift
    object_type = object_type.strip
  else
    error "Please specify a name for your new object type. Usage: model:create_object_type NAME"
  end
   
  namespace = options[:namespace] ||= "http://astroboa/schema/#{object_type}"
  models_dir = options[:models_dir] ||= File.join(Dir.getwd, 'models')
  localized_labels = options[:localized_labels] ||= "en:#{object_type}"
  localized_labels_map = {}
  localized_labels.split(',').each {|loc_lab| loc_lab_array = loc_lab.split(':'); localized_labels_map[loc_lab_array[0]] = loc_lab_array[1]}
  
  xsd_dir = File.join models_dir, 'xsd'
  
  unless Dir.exists? xsd_dir
    FileUtils.mkdir_p xsd_dir
  end 
  
  schema_file = File.join xsd_dir, "#{object_type}.xsd"
  
  error <<-MSG if File.exists? schema_file
  XML Schema file '#{schema_file}' exists.
  This means that you have already defined a type named '#{object_type}' (the command creates each object type 
  in a different schema file named after the object type name).
  There is also the possibility that you have manually created the file '#{schema_file}'. 
  If you are creating XML Schema files manually please follow the convension 
  to keep each object type in a separate file and name your file after the name of the type.
  MSG
  
  # TODO: Extra check all other schemas to verify that user has not manually add this type
  
  server_configuration = get_server_configuration
  astroboa_dir = server_configuration['install_dir']
  
  object_type_template = File.join(astroboa_dir, 'astroboa-setup-templates', 'object_type_template.xsd')
  context = {namespace: namespace, object_type: object_type, localized_labels_map: localized_labels_map}
  render_template_to_file(object_type_template, context, schema_file)
  
  display "Generate schema file '#{schema_file}' for new object type '#{object_type}': OK"
  display "You can now use 'model:add_property' to add properties to your new object type."
  display "When you finish adding properties to your type use 'model:associate' to associate a repository with your new object type and start creating object instances of this type" 
end

#graphObject

model:graph DOMAIN_MODEL

Draws a graphic representation of the domain model



128
129
130
# File 'lib/astroboa-cli/command/model.rb', line 128

def graph
  
end

#listObject

model:list DOMAIN_MODEL

Displays information about the domain models and their entities



142
143
144
# File 'lib/astroboa-cli/command/model.rb', line 142

def list
  
end

#viewObject

model:view PATH

Displays the model of an entity or entity property



135
136
137
# File 'lib/astroboa-cli/command/model.rb', line 135

def view
  
end