From c1854b8b518bde82b80429636f73496a537c3ad5 Mon Sep 17 00:00:00 2001 From: hukl Date: Sun, 1 Feb 2009 21:32:44 +0100 Subject: added first careful steps for aggregation --- app/helpers/content_helper.rb | 23 +++++++++++++++++++++- app/models/page.rb | 35 ++++++++++++++++++++++++++++++++++ app/views/content/_article.html.erb | 6 ++++++ app/views/content/render_page.html.erb | 2 +- 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 app/views/content/_article.html.erb diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb index 0e1df66..046a2db 100644 --- a/app/helpers/content_helper.rb +++ b/app/helpers/content_helper.rb @@ -1,8 +1,29 @@ module ContentHelper - # def date_for_page page page.published_at.to_s(:db) rescue page.created_at.to_s(:db) end + def aggregate? content + options = {} + + if content =~ /]*)>/ + tag = $~.to_s + matched_data = $1.scan(/\w+\=\"[a-zA-Z\s\/_\d]*\"/) + + matched_data.each do |data| + splitted_data = data.split("=") + options[splitted_data[0].to_sym] = splitted_data[1].gsub(/\"/, "") + end + end + + content.sub(tag, render_collection(options)) + end + + def render_collection options + render( + :partial => 'content/article', + :collection => Page.aggregate(options) + ) + end end diff --git a/app/models/page.rb b/app/models/page.rb index 0fcb4a8..3d02e9f 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -4,4 +4,39 @@ class Page < ActiveRecord::Base acts_as_list :column => :revision, :scope => :node_id + + # + def self.aggregate options + + defaults = { + :flags => "", + :path => "", + :limit => 20, + :order_by => "id", + :order_direction => "ASC" + } + + options = defaults.merge options + + pages = Page.all( + :limit => options[:limit], + :order => "#{options[:order_by]} #{options[:order_direction]}" + ) + end end + + +named_scope :flagged_as, lambda { |flags| + conditions = {} + flags.each do |flag| + conditions[flag] = true + end + + { :conditions => conditions } +} \ No newline at end of file diff --git a/app/views/content/_article.html.erb b/app/views/content/_article.html.erb new file mode 100644 index 0000000..1e06787 --- /dev/null +++ b/app/views/content/_article.html.erb @@ -0,0 +1,6 @@ +
+
+
+

<%= article.title %>

+

<%= date_for_page article %>, gregoa

+

<%= article.abstract %>

\ No newline at end of file diff --git a/app/views/content/render_page.html.erb b/app/views/content/render_page.html.erb index 311d61d..b70f90e 100644 --- a/app/views/content/render_page.html.erb +++ b/app/views/content/render_page.html.erb @@ -13,5 +13,5 @@

<%= date_for_page @page %>, gregoa


<%= @page.abstract %>

- <%= @page.body %> + <%= aggregate?(@page.body) %> \ No newline at end of file -- cgit v1.3 From 7a5555b1ac542e5fd5680cefaba3574677ecaa46 Mon Sep 17 00:00:00 2001 From: hukl Date: Mon, 2 Feb 2009 00:22:54 +0100 Subject: aggregation spike with flags associated to pages Page has now a named_scope :with_flags which accepts an array of flag names and returns corresponding pages. Can be chained with order and limit --- app/models/flag.rb | 3 +++ app/models/page.rb | 22 +++++++++++----------- db/migrate/20090201211159_create_flags.rb | 13 +++++++++++++ ...0090201211523_add_join_table_for_flags_pages.rb | 15 +++++++++++++++ test/fixtures/flags.yml | 7 +++++++ test/unit/flag_test.rb | 8 ++++++++ 6 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 app/models/flag.rb create mode 100644 db/migrate/20090201211159_create_flags.rb create mode 100644 db/migrate/20090201211523_add_join_table_for_flags_pages.rb create mode 100644 test/fixtures/flags.yml create mode 100644 test/unit/flag_test.rb diff --git a/app/models/flag.rb b/app/models/flag.rb new file mode 100644 index 0000000..6d67377 --- /dev/null +++ b/app/models/flag.rb @@ -0,0 +1,3 @@ +class Flag < ActiveRecord::Base + has_and_belongs_to_many :pages +end diff --git a/app/models/page.rb b/app/models/page.rb index 3d02e9f..d5d888f 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -1,9 +1,19 @@ class Page < ActiveRecord::Base belongs_to :node + has_and_belongs_to_many :flags acts_as_list :column => :revision, :scope => :node_id + named_scope :with_flags, lambda {|flag_names| + if (flags = Flag.find_all_by_name(flag_names)).empty? + {} + else + {:include => :flags, :conditions => ['flags_pages.flag_id IN (?)', flags.map(&:id)] } + end + } + + # "#{options[:order_by]} #{options[:order_direction]}" ) end -end - - -named_scope :flagged_as, lambda { |flags| - conditions = {} - flags.each do |flag| - conditions[flag] = true - end - - { :conditions => conditions } -} \ No newline at end of file +end \ No newline at end of file diff --git a/db/migrate/20090201211159_create_flags.rb b/db/migrate/20090201211159_create_flags.rb new file mode 100644 index 0000000..3b9da43 --- /dev/null +++ b/db/migrate/20090201211159_create_flags.rb @@ -0,0 +1,13 @@ +class CreateFlags < ActiveRecord::Migration + def self.up + create_table :flags do |t| + t.string :name + + t.timestamps + end + end + + def self.down + drop_table :flags + end +end diff --git a/db/migrate/20090201211523_add_join_table_for_flags_pages.rb b/db/migrate/20090201211523_add_join_table_for_flags_pages.rb new file mode 100644 index 0000000..99eb9c9 --- /dev/null +++ b/db/migrate/20090201211523_add_join_table_for_flags_pages.rb @@ -0,0 +1,15 @@ +class AddJoinTableForFlagsPages < ActiveRecord::Migration + def self.up + create_table :flags_pages, :id => false do |t| + t.integer :flag_id + t.integer :page_id + end + add_index :flags_pages, [:flag_id] + add_index :flags_pages, [:page_id] + end + + def self.down + remove_table :flags_pages + end + +end diff --git a/test/fixtures/flags.yml b/test/fixtures/flags.yml new file mode 100644 index 0000000..157d747 --- /dev/null +++ b/test/fixtures/flags.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +one: + name: MyString + +two: + name: MyString diff --git a/test/unit/flag_test.rb b/test/unit/flag_test.rb new file mode 100644 index 0000000..49b0d96 --- /dev/null +++ b/test/unit/flag_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class FlagTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end -- cgit v1.3 From 5a8c9154d504c5376e807716c0cfcb76ef766d88 Mon Sep 17 00:00:00 2001 From: hukl Date: Mon, 2 Feb 2009 00:37:42 +0100 Subject: made the whole aggregate helper exception safe by always falling back to the original content if something went wrong. only executing substitution if tag is found --- app/helpers/content_helper.rb | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb index 046a2db..3cf3488 100644 --- a/app/helpers/content_helper.rb +++ b/app/helpers/content_helper.rb @@ -7,17 +7,22 @@ module ContentHelper def aggregate? content options = {} - if content =~ /]*)>/ - tag = $~.to_s - matched_data = $1.scan(/\w+\=\"[a-zA-Z\s\/_\d]*\"/) - - matched_data.each do |data| - splitted_data = data.split("=") - options[splitted_data[0].to_sym] = splitted_data[1].gsub(/\"/, "") + begin + if content =~ /]*)>/ + tag = $~.to_s + matched_data = $1.scan(/\w+\=\"[a-zA-Z\s\/_\d]*\"/) + + matched_data.each do |data| + splitted_data = data.split("=") + options[splitted_data[0].to_sym] = splitted_data[1].gsub(/\"/, "") + end + + content.sub(tag, render_collection(options)) end + + rescue + content end - - content.sub(tag, render_collection(options)) end def render_collection options -- cgit v1.3 From 9c793dd8b3adc8adb8d541b88a1f547e18937d14 Mon Sep 17 00:00:00 2001 From: hukl Date: Mon, 2 Feb 2009 20:53:40 +0100 Subject: more concept text --- doc/README_FOR_APP | 52 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP index 9b76357..9f7133a 100644 --- a/doc/README_FOR_APP +++ b/doc/README_FOR_APP @@ -1,14 +1,52 @@ CCCMS +==General -The basic structure of the cccms is built from Nodes. Nodes live within a -nested set. When a arbitrary url is entered it is dispatched to the -ContentController action render_page. Based on the url parameters the -render_page action retrieves the corresponding Node from the database. Rather -than walking through the tree of the nested set to find the node, the Node is -retrieved directly by its unique_name which matches the url parameters. +===Nodes -So much for now yeah +The structure of the cccms is built from Node objects which live within a nested +set. Therefor a given node has parents, children, descendants etc. + +The position of a node within the nested set corresponds directly to the URL +under which that node is accessible: + +root + \__updates + \__2009 + \___ultra_important_news + +=> http://domain/de/updates/2009/ultra_important_news + +Note that the first parameter after the domain is the locale. Everything after +the locale identifier is the unique path of a given node. The unique path itself +is generated from the slugs of the ancestors of a node. The last part of the +unique path is taken from the slug of the node. + +Once a node is added to the nested set or moved within, the unique path of that +node is generated from all its ancestors up to the root node. The computed path +is then saved on the node object itself, allowing the system to retrieve a +node simply by looking for the right url in the unique_path column. This is a +lot faster then walking down the tree. + +===Pages + +As the nodes only built the structure, another object is necessary to actually +hold all the contents. This object is called a page and is associated to a node +via a one-to-many association. A node can have multiple pages associated to it. +The node is actually a proxy for the pages behind it, and the pages act as a +versioned page. By default, if you retrieve a node from the database by its +unique path and ask this node for a page, the node would return the most recent +one. It is also possible to get a page from a node, supplying a revision number. +The node object would then retrieve the associated page with the corresponding +revision number. For convenience purposes, the most recent page revision, in +the scope of a node, is flagged as the head of this collection. This is +primarily for making certain queries a lot easier where you only want to select +upon the current pages in the db rather than on all. + +It is important to know that all the associations of a page, such as tags, +authors etc, must be copied one a new revision of a page is created. The Page +class is providing a deep_copy method to make sure everything important is +copied. git clone ssh://git@svn.medienhaus.udk-berlin.de/usr/local/git/cccms -- cgit v1.3 From fa31c7b73d57183f5379d81bf17aaf45bea59daa Mon Sep 17 00:00:00 2001 From: hukl Date: Mon, 2 Feb 2009 21:01:30 +0100 Subject: just a few more lines in the concept --- doc/README_FOR_APP | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP index 9f7133a..c1bc254 100644 --- a/doc/README_FOR_APP +++ b/doc/README_FOR_APP @@ -48,6 +48,24 @@ authors etc, must be copied one a new revision of a page is created. The Page class is providing a deep_copy method to make sure everything important is copied. +===Keywords + +Pages of course come with meta data attatched to them. Keywords are one kind of +meta data. They can be understood and used as tags, categories or any similar +concept. + +===Aggregation + +Keywords and other meta data can be used to aggregate any ammount of pages +into the body of another page. + + + git clone ssh://git@svn.medienhaus.udk-berlin.de/usr/local/git/cccms -- cgit v1.3 From d0c3cd743e8500f643ff54b93e4a599c7e16b9d1 Mon Sep 17 00:00:00 2001 From: hukl Date: Mon, 2 Feb 2009 21:02:18 +0100 Subject: more comments with alternate queries --- app/models/page.rb | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/app/models/page.rb b/app/models/page.rb index d5d888f..fee20a8 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -5,6 +5,24 @@ class Page < ActiveRecord::Base acts_as_list :column => :revision, :scope => :node_id + # Alternativ Queries with one or two inner joins. Loading the Flags themselves + # would be another query. Could be faster on larger data sets. + # + # Single Join: + # + # Page.find( + # :all, + # :joins => 'JOIN flags_pages on pages.id = flags_pages.page_id', + # :include => :flags, + # :conditions => ['flags_pages.flag_id IN (?)', [1,2]] + # ) + # Two inner joins: + # + # Page.find( + # :all, + # :joins => :flags_pages, + # :conditions => ['flags_pages.flag_id IN (?)', [1,2]] + # ) named_scope :with_flags, lambda {|flag_names| if (flags = Flag.find_all_by_name(flag_names)).empty? {} @@ -12,12 +30,9 @@ class Page < ActiveRecord::Base {:include => :flags, :conditions => ['flags_pages.flag_id IN (?)', flags.map(&:id)] } end } - - - + # "", - :path => "", :limit => 20, :order_by => "id", :order_direction => "ASC" @@ -34,7 +48,7 @@ class Page < ActiveRecord::Base options = defaults.merge options - pages = Page.all( + pages = Page.with_flags(options[:flags].split(/\s/)).all( :limit => options[:limit], :order => "#{options[:order_by]} #{options[:order_direction]}" ) -- cgit v1.3