wontomedia 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +9 -5
- data/VERSION.yml +2 -2
- data/app/controllers/admin_controller.rb +34 -11
- data/app/controllers/connections_controller.rb +8 -2
- data/app/controllers/items_controller.rb +213 -68
- data/app/helpers/format_helper.rb +22 -1
- data/app/helpers/items_helper.rb +43 -0
- data/app/models/connection.rb +128 -100
- data/app/models/item.rb +96 -0
- data/app/views/connections/_spo_select_controls.html.erb +65 -31
- data/app/views/connections/edit.html.erb +7 -2
- data/app/views/connections/index.html.erb +11 -4
- data/app/views/connections/show.html.erb +13 -5
- data/app/views/items/_class_select.html.erb +41 -0
- data/app/views/items/_content_examples.html.erb +21 -18
- data/app/views/items/_core_tasks.html.erb +40 -5
- data/app/views/items/_form_fields.html.erb +4 -4
- data/app/views/items/_inline_item_add_form.html.erb +48 -0
- data/app/views/items/_inline_scalar_add_form.html.erb +36 -0
- data/app/views/items/_most_populous_classes.html.erb +42 -0
- data/app/views/items/_type_select.html.erb +28 -26
- data/app/views/items/edit.html.erb +8 -2
- data/app/views/items/index.html.erb +5 -1
- data/app/views/items/new.html.erb +69 -54
- data/app/views/items/newpop.html.erb +18 -3
- data/app/views/items/show.html.erb +110 -7
- data/app/views/layouts/application.html.erb +2 -2
- data/app/views/layouts/base.html.erb +4 -2
- data/app/views/layouts/home.html.erb +2 -2
- data/assets/wontomedia-sample.rb +2 -0
- data/config/asset_packages.yml +1 -0
- data/config/cucumber.yml +11 -13
- data/db/fixtures/connections.yml +85 -4
- data/db/fixtures/items.yml +140 -8
- data/db/migrate/20100315135952_provide_scalar_objects.rb +32 -0
- data/db/migrate/20100321042343_add_timestamp_columns.rb +33 -0
- data/db/schema.rb +17 -11
- data/default-custom/app/views/items/home.html.erb +1 -1
- data/default-custom/public/stylesheets/wm.css +21 -4
- data/lib/helpers/connection_helper.rb +84 -1
- data/lib/helpers/item_helper.rb +16 -3
- data/lib/tasks/cucumber.rake +0 -2
- data/public/images/transparent_ltblue_background.png +0 -0
- data/public/images/{alert_background.png → transparent_white_background.png} +0 -0
- data/public/javascripts/forConnectionsForms.js +182 -41
- data/public/javascripts/forItemsForms.js +40 -5
- data/public/javascripts/forItemsShow.js +27 -0
- data/public/javascripts/itemCreatePopup.js +10 -3
- metadata +13 -5
@@ -153,12 +153,17 @@ module FormatHelper
|
|
153
153
|
end
|
154
154
|
|
155
155
|
def logo_image
|
156
|
+
result = ''
|
157
|
+
if WontoMedia.site_logo_title
|
158
|
+
result += "<div width='99%'>#{WontoMedia.site_logo_title}</div>\n"
|
159
|
+
end
|
156
160
|
logo_name = (File.exists?(
|
157
161
|
Rails.root.join( 'public', 'images', 'logo.jpg'))) ?
|
158
162
|
'/images/logo.jpg' :
|
159
163
|
'/images/logo.png'
|
160
|
-
image_tag( logo_name, :alt=>'Logo', :width=>'99%',
|
164
|
+
result += image_tag( logo_name, :alt=>'Logo', :width=>'99%',
|
161
165
|
:style=>'margin-bottom: 0.7ex;' )
|
166
|
+
result
|
162
167
|
end
|
163
168
|
|
164
169
|
|
@@ -178,4 +183,20 @@ module FormatHelper
|
|
178
183
|
page_name +
|
179
184
|
WontoMedia.site_content_url_postfix
|
180
185
|
end
|
186
|
+
|
187
|
+
def ruby_hash_to_javascript( js_name, ruby_hash, prefix = 'id' )
|
188
|
+
has_started = false
|
189
|
+
js = "<script type='text/javascript'>\n"
|
190
|
+
js += " #{js_name} = {\n"
|
191
|
+
|
192
|
+
ruby_hash.keys.each do |class_item|
|
193
|
+
js += ",\n" if has_started
|
194
|
+
has_started = true
|
195
|
+
js += " #{prefix}#{class_item.id}: '#{ruby_hash[class_item]}'"
|
196
|
+
end
|
197
|
+
|
198
|
+
js += "\n"
|
199
|
+
js += " };\n"
|
200
|
+
js + "</script>"
|
201
|
+
end
|
181
202
|
end
|
data/app/helpers/items_helper.rb
CHANGED
@@ -102,4 +102,47 @@ module ItemsHelper
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
105
|
+
|
106
|
+
# produce an array of all the system's class items suitable for the
|
107
|
+
# "options" argument to select_tag, sorted as follows:
|
108
|
+
# * first: no-selection option
|
109
|
+
# * 2nd: +@item+'s currently-selected class, if any
|
110
|
+
# * non-built-in classes, sorted alphabetically
|
111
|
+
# * built-in classes
|
112
|
+
def class_options_list_for(item)
|
113
|
+
option_array = [ [ "- class of item -", "" ] ]
|
114
|
+
|
115
|
+
this_class_item = item.class_item
|
116
|
+
unless this_class_item.nil?
|
117
|
+
option_array.concat [[ h("#{this_class_item.name}"), this_class_item.id ]]
|
118
|
+
end
|
119
|
+
|
120
|
+
groups = @class_list.group_by do |item|
|
121
|
+
if this_class_item && item.id == this_class_item.id
|
122
|
+
:discard
|
123
|
+
elsif (item.flags & Item::DATA_IS_UNALTERABLE) == 0
|
124
|
+
:contributor
|
125
|
+
else
|
126
|
+
:builtin
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if groups[:contributor]
|
131
|
+
option_array.concat(
|
132
|
+
groups[:contributor].sort{ |a,b|
|
133
|
+
a.name.upcase <=> b.name.upcase }.map{ |item|
|
134
|
+
[ h("#{item.name}"), item.id ] } )
|
135
|
+
option_array.concat [ [ " ----", "invalid" ] ]
|
136
|
+
end
|
137
|
+
|
138
|
+
if groups[:builtin]
|
139
|
+
option_array.concat(
|
140
|
+
groups[:builtin].sort{ |a,b|
|
141
|
+
a.name <=> b.name }.map{ |item|
|
142
|
+
[ h("#{item.name}"), item.id ] } )
|
143
|
+
end
|
144
|
+
|
145
|
+
options_for_select( option_array,
|
146
|
+
this_class_item.nil? ? "" : this_class_item.id )
|
147
|
+
end
|
105
148
|
end
|
data/app/models/connection.rb
CHANGED
@@ -36,16 +36,26 @@ class Connection < ActiveRecord::Base
|
|
36
36
|
# This constant is a bit mask for Item.flags. A non-zero value
|
37
37
|
# indicates that the Item instance should not be user-modifiable.
|
38
38
|
DATA_IS_UNALTERABLE = 1
|
39
|
+
OBJECT_KIND_ITEM = 'item'
|
40
|
+
OBJECT_KIND_SCALAR = 'scalar'
|
41
|
+
|
42
|
+
|
43
|
+
# convenience attribute allows item representing a connection's object's
|
44
|
+
# type (if specified by a relationship somewhere in the database) to
|
45
|
+
# be attached to the connection once it has been looked up once
|
46
|
+
attr_accessor :type_item
|
39
47
|
|
40
48
|
|
41
49
|
before_validation :complex_validations
|
42
|
-
# validates_presence_of :subject, :predicate
|
50
|
+
# validates_presence_of :subject, :predicate
|
43
51
|
#explicitly do the equivalent of the above in complex_validations because
|
44
52
|
#c_v has to be a "before" callback (so it's return value is checked), which
|
45
53
|
#means other validations aren't run yet, so can't count on these IDs to
|
46
54
|
#be present/valid. Ugh.
|
55
|
+
|
56
|
+
# validates_inclusion_of :kind_of_obj, :in => %w(item scalar)
|
47
57
|
# validates_uniqueness_of :subject_id, :scope => [:predicate_id, :obj_id]
|
48
|
-
#
|
58
|
+
#these work, but are subsumed by checks in c_v, so eliminated duplication
|
49
59
|
|
50
60
|
belongs_to :subject, :class_name => "Item"
|
51
61
|
belongs_to :predicate, :class_name => "Item"
|
@@ -74,134 +84,152 @@ private
|
|
74
84
|
# checks fail, this method returns +false+ and places the
|
75
85
|
# appropriate message(s) in the +errors+ flash.
|
76
86
|
def complex_validations() #:doc:
|
77
|
-
|
78
|
-
[
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
87
|
+
if kind_of_obj == OBJECT_KIND_ITEM
|
88
|
+
[ [subject_id, :subject], [predicate_id, :predicate],
|
89
|
+
[obj_id, :obj] ].each do |tocheck|
|
90
|
+
field, symbol = *tocheck
|
91
|
+
if field.nil?
|
92
|
+
errors.add symbol, "can't be blank."
|
93
|
+
return false
|
94
|
+
end
|
83
95
|
end
|
84
|
-
end
|
85
96
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
# is there an existing connection with a predicate that is a
|
98
|
+
# sub-property or super-property of the current predicate?
|
99
|
+
# check for duplicate connection and all super-properties
|
100
|
+
TrippleNavigation.relation_and_all_superproperties(
|
101
|
+
predicate_id) do |super_prop|
|
102
|
+
unless (Connection.all( :conditions => [
|
103
|
+
"subject_id = ? AND predicate_id = ? AND obj_id = ?",
|
104
|
+
subject_id, super_prop, obj_id ] ).empty? )
|
105
|
+
errors.add :subject, 'relationship (or equivalent) already exists.'
|
106
|
+
return false
|
107
|
+
end
|
96
108
|
end
|
97
|
-
end
|
98
109
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
110
|
+
# check for sub-properties
|
111
|
+
TrippleNavigation.relation_and_all_subproperties(
|
112
|
+
predicate_id) do |sub_prop|
|
113
|
+
unless (Connection.all( :conditions => [
|
114
|
+
"subject_id = ? AND predicate_id = ? AND obj_id = ?",
|
115
|
+
subject_id, sub_prop, obj_id ] ).empty? )
|
116
|
+
errors.add :subject, 'equivalent relationship already exists.'
|
117
|
+
return false
|
118
|
+
end
|
107
119
|
end
|
108
|
-
end
|
109
120
|
|
110
121
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
122
|
+
# is there an existing implied opposing connection?
|
123
|
+
# find all connections back-connecting our subject and object, determine
|
124
|
+
# if any of their predicates has an (inherited)
|
125
|
+
# inverse_relationship to current predicate
|
126
|
+
if connections = Connection.all( :conditions => [
|
127
|
+
"subject_id = ? AND obj_id = ?", obj_id, subject_id ] )
|
117
128
|
|
118
|
-
|
129
|
+
inverse_id = Item.find_by_name("inverse_relationship").id
|
119
130
|
|
120
|
-
|
121
|
-
TrippleNavigation.relation_and_all_superproperties(
|
122
|
-
predicate_id) do |proposed_rel|
|
131
|
+
connections.each do |e|
|
123
132
|
TrippleNavigation.relation_and_all_superproperties(
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
133
|
+
predicate_id) do |proposed_rel|
|
134
|
+
TrippleNavigation.relation_and_all_superproperties(
|
135
|
+
e.predicate_id) do |existing_rel|
|
136
|
+
if Connection.all( :conditions => [
|
137
|
+
"subject_id = ? AND predicate_id = ? AND obj_id = ?",
|
138
|
+
proposed_rel, inverse_id, existing_rel ] ).length > 0
|
139
|
+
errors.add :subject, 'already has this relationship implied.'
|
140
|
+
return false
|
141
|
+
end
|
130
142
|
end
|
131
143
|
end
|
132
144
|
end
|
133
145
|
end
|
134
|
-
end
|
135
146
|
|
136
147
|
|
137
|
-
|
138
|
-
|
148
|
+
#used in many subsequent tests
|
149
|
+
spo_id = Item.find_by_name("sub_property_of").id
|
139
150
|
|
140
151
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
152
|
+
# would this create an "individual parent_of category" relationship?
|
153
|
+
subItem = Item.find(subject_id)
|
154
|
+
objItem = Item.find(obj_id)
|
155
|
+
# check for "individual parent_of category"
|
156
|
+
if subItem.sti_type == ItemHelper::ITEM_INDIVIDUAL_CLASS_NAME &&
|
157
|
+
objItem.sti_type == ItemHelper::ITEM_CATEGORY_CLASS_NAME
|
158
|
+
if TrippleNavigation.check_properties(
|
159
|
+
:does => predicate_id,
|
160
|
+
:inherit_from => Item.find_by_name("parent_of").id,
|
161
|
+
:via => spo_id)
|
162
|
+
errors.add :predicate,
|
163
|
+
'an individual cannot be the parent of a category.'
|
164
|
+
return false
|
165
|
+
end
|
154
166
|
end
|
155
|
-
end
|
156
167
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
168
|
+
# check for "category child_of individual"
|
169
|
+
if subItem.sti_type == ItemHelper::ITEM_CATEGORY_CLASS_NAME &&
|
170
|
+
objItem.sti_type == ItemHelper::ITEM_INDIVIDUAL_CLASS_NAME
|
171
|
+
if TrippleNavigation.check_properties(
|
172
|
+
:does => predicate_id,
|
173
|
+
:inherit_from => Item.find_by_name("child_of").id,
|
174
|
+
:via => spo_id)
|
175
|
+
errors.add :predicate,
|
176
|
+
'a category cannot be the child of an individual.'
|
177
|
+
return false
|
178
|
+
end
|
167
179
|
end
|
168
|
-
end
|
169
180
|
|
170
181
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
if TrippleNavigation.check_properties(
|
182
|
-
:does => predicate_id, :inherit_from => prop_id, :via => spo_id)
|
182
|
+
# would this create a loop (including connection-to-self) of
|
183
|
+
# hierarchical or ordered relationships?
|
184
|
+
# if this is the kind of property we have to worry about?
|
185
|
+
[
|
186
|
+
Item.find_by_name("parent_of").id,
|
187
|
+
Item.find_by_name("child_of").id,
|
188
|
+
Item.find_by_name("sub_property_of").id,
|
189
|
+
Item.find_by_name("predecessor_of").id,
|
190
|
+
Item.find_by_name("successor_of").id
|
191
|
+
].each do |prop_id|
|
183
192
|
if TrippleNavigation.check_properties(
|
184
|
-
:does =>
|
185
|
-
|
186
|
-
|
187
|
-
|
193
|
+
:does => predicate_id, :inherit_from => prop_id, :via => spo_id)
|
194
|
+
if TrippleNavigation.check_properties(
|
195
|
+
:does => obj_id, :link_to => subject_id,
|
196
|
+
:through_children_of => prop_id )
|
197
|
+
errors.add :subject, 'this relationship would create a loop.'
|
198
|
+
return false
|
199
|
+
end
|
188
200
|
end
|
189
201
|
end
|
190
|
-
end
|
191
202
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
'cannot create an ordered/hierarchical relationship from an item to itself'
|
203
|
+
# is this a "bad" connection-to-self?
|
204
|
+
if subject_id == obj_id
|
205
|
+
[ "inverse_relationship", "hierarchical_relationship",
|
206
|
+
"ordered_relationship" ].each do |relation_name|
|
207
|
+
if TrippleNavigation.check_properties(
|
208
|
+
:does => predicate_id,
|
209
|
+
:inherit_from => Item.find_by_name(relation_name).id,
|
210
|
+
:via => spo_id )
|
211
|
+
errors.add :subject,
|
212
|
+
'cannot create an ordered/hierarchical relationship from an item to itself'
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
elsif kind_of_obj == OBJECT_KIND_SCALAR
|
220
|
+
[ [subject_id, :subject], [predicate_id, :predicate],
|
221
|
+
[scalar_obj, :scalar_obj] ].each do |tocheck|
|
222
|
+
field, symbol = *tocheck
|
223
|
+
if field.nil?
|
224
|
+
errors.add symbol, "can't be blank."
|
202
225
|
return false
|
203
226
|
end
|
204
227
|
end
|
228
|
+
|
229
|
+
|
230
|
+
else
|
231
|
+
errors.add :kind_of_obj, "isn't valid (#{kind_of_obj})"
|
232
|
+
return false
|
205
233
|
end
|
206
234
|
|
207
235
|
|
data/app/models/item.rb
CHANGED
@@ -43,8 +43,10 @@ class Item < ActiveRecord::Base
|
|
43
43
|
DATA_IS_UNALTERABLE = 1
|
44
44
|
|
45
45
|
|
46
|
+
# support sub-classes for Individual, Category, Property, Qualified
|
46
47
|
self.inheritance_column = "sti_type"
|
47
48
|
|
49
|
+
|
48
50
|
# name
|
49
51
|
validates_presence_of :name, :message => "Item's name cannot be blank."
|
50
52
|
validates_length_of :name, :maximum => 80,
|
@@ -83,4 +85,98 @@ class Item < ActiveRecord::Base
|
|
83
85
|
# column default specified in the database schema
|
84
86
|
self[:flags] or 0
|
85
87
|
end
|
88
|
+
|
89
|
+
|
90
|
+
@class_item = nil
|
91
|
+
@class_has_been_set = false
|
92
|
+
after_save :after_save_callback # update class-defining connection after
|
93
|
+
# main object saves; rollback still works
|
94
|
+
def class_item
|
95
|
+
if @class_has_been_set
|
96
|
+
@class_item # ignore db content if this has been set
|
97
|
+
# explicitly. This will eventually be
|
98
|
+
# saved or discarded
|
99
|
+
else
|
100
|
+
iio_item = Item.find_by_name('is_instance_of')
|
101
|
+
class_assignment = Connection.first( :conditions => [
|
102
|
+
"subject_id = ? AND predicate_id = ?", id, iio_item.id ] )
|
103
|
+
if class_assignment.nil? or
|
104
|
+
class_assignment.kind_of_obj != Connection::OBJECT_KIND_ITEM
|
105
|
+
return nil
|
106
|
+
else
|
107
|
+
return class_assignment.obj
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
def class_item_id
|
112
|
+
item = class_item
|
113
|
+
return item.nil? ? nil : item.id
|
114
|
+
end
|
115
|
+
def class_item=( new_class_item )
|
116
|
+
@class_item = new_class_item
|
117
|
+
@class_has_been_set = true
|
118
|
+
end
|
119
|
+
def class_item_id=( new_class_id )
|
120
|
+
@class_item = new_class_id.nil? ? nil : Item.find_by_id( new_class_id )
|
121
|
+
@class_has_been_set = true
|
122
|
+
end
|
123
|
+
|
124
|
+
def is_class?
|
125
|
+
return( !( Connection.first( :conditions => [
|
126
|
+
"predicate_id = ? AND obj_id = ?",
|
127
|
+
Item.find_by_name('is_instance_of').id,
|
128
|
+
id ] ).nil? ) or
|
129
|
+
!( Connection.first( :conditions => [
|
130
|
+
"predicate_id = ? AND (subject_id = ? OR obj_id = ?)",
|
131
|
+
Item.find_by_name('sub_class_of').id,
|
132
|
+
id, id ] ).nil? )
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def superclass_of
|
137
|
+
defining_connection = Connection.first( :conditions => [
|
138
|
+
"subject_id = ? AND predicate_id = ?",
|
139
|
+
id, Item.find_by_name('sub_class_of').id ] )
|
140
|
+
return defining_connection.obj unless defining_connection.nil?
|
141
|
+
return nil
|
142
|
+
end
|
143
|
+
|
144
|
+
def instance_of
|
145
|
+
connection = Connection.first( :conditions => [
|
146
|
+
"subject_id = ? AND predicate_id = ?",
|
147
|
+
id, Item.find_by_name('is_instance_of').id ] )
|
148
|
+
return nil if connection == nil
|
149
|
+
return connection.obj
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def after_save_callback
|
155
|
+
if @class_has_been_set
|
156
|
+
iio_item = Item.find_by_name('is_instance_of')
|
157
|
+
class_assignment = Connection.first( :conditions => [
|
158
|
+
"subject_id = ? AND predicate_id = ?", id, iio_item.id ] )
|
159
|
+
if class_assignment && @class_item == class_assignment.obj
|
160
|
+
return true # class-defining connection already in db -> done
|
161
|
+
end
|
162
|
+
|
163
|
+
if class_assignment # if we're here, current db is different
|
164
|
+
class_assignment.destroy # so get rid of it
|
165
|
+
end
|
166
|
+
|
167
|
+
begin
|
168
|
+
class_assignment = Connection.new( {
|
169
|
+
:subject_id => id,
|
170
|
+
:predicate_id => iio_item.id,
|
171
|
+
:obj_id => @class_item.id,
|
172
|
+
:kind_of_obj => Connection::OBJECT_KIND_ITEM } )
|
173
|
+
@class_has_been_set = false
|
174
|
+
return class_assignment.save
|
175
|
+
rescue
|
176
|
+
return false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
return true
|
181
|
+
end
|
86
182
|
end
|