Commit 09a87726fac518a161779a7348882cd28a89d689

Authored by AntonioTerceiro
1 parent d7aa1364

ActionItem8: initial effort on VirtualCommunity, Profile and Domain



git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@25 3f533792-8f58-4932-b0fe-aaf55b0a4547
app/controllers/application.rb
1 1 # Filters added to this controller will be run for all controllers in the application.
2 2 # Likewise, all the methods added will be available for all controllers.
3 3 class ApplicationController < ActionController::Base
4   -end
5 4 \ No newline at end of file
  5 +
  6 + before_filter :detect_stuff_by_domain
  7 +
  8 + protected
  9 +
  10 + def detect_stuff_by_domain
  11 + @domain = Domain.find_by_name(request.host)
  12 + @virtual_community = @domain.virtual_community
  13 + @profile = @domain.profile
  14 + end
  15 +
  16 +end
... ...
app/controllers/home_controller.rb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +class HomeController < ApplicationController
  2 +end
... ...
app/helpers/home_helper.rb 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +module HomeHelper
  2 +end
... ...
app/models/domain.rb
1 1 class Domain < ActiveRecord::Base
2 2  
  3 + # relationships
  4 + ###############
  5 +
3 6 belongs_to :owner, :polymorphic => true
4 7  
5   - validates_format_of :name, :with => /^(\w+\.)+\w+$/
  8 + # validations
  9 + #############
  10 +
  11 + # <tt>name</tt> must be a sequence of word characters (a to z, plus 0 to 9,
  12 + # plus '_'). Letters must be lowercase
  13 + validates_format_of :name, :with => /^([a-z0-9_]+\.)+[a-z0-9_]+$/, :message => _('%{fn} must be composed only of lowercase latters (a to z), numbers (0 to 9) and "_"')
  14 +
  15 + # checks validations that could not be expressed using Rails' predefined
  16 + # validations. In particular:
  17 + # * <tt>name</tt> must not start with 'www.'
  18 + def validate
  19 + if self.name =~ /^www\./
  20 + self.errors.add(:name, _('%{fn} must not start with www.'))
  21 + end
  22 + end
  23 +
  24 + # we cannot have two domains with the same name
  25 + validates_uniqueness_of :name
  26 +
  27 + # businessl logic
  28 + #################
  29 +
  30 + # finds a domain by its name. The argument <tt>name</tt> can start with
  31 + # "www.", but it will be removed before searching. So searching for
  32 + # 'www.example.net' is exactly the same as searching for just 'example.net'
  33 + def self.find_by_name(name)
  34 + self.find(:first, :conditions => [ 'name = ?', self.extract_domain_name(name) ])
  35 + end
  36 +
  37 + # turns the argument (expected to be a String) into a domain name that is
  38 + # accepted, by removing any leading 'www.' and turning the downcasing it.
  39 + def self.extract_domain_name(name)
  40 + name.downcase.sub(/^www\./, '')
  41 + end
  42 +
  43 + # detects the VirtualCommunity to which this domain belongs, either if it's
  44 + # directly owned by one, or through a profile who owns the domain but belongs
  45 + # to a VirtualCommunity.
  46 + def virtual_community
  47 + case owner
  48 + when VirtualCommunity
  49 + owner
  50 + when Profile
  51 + owner.virtual_community
  52 + end
  53 + end
  54 +
  55 + # returns the profile associated to this domain. Returns nil if the domain is
  56 + # not directly associated with a profile.
  57 + def profile
  58 + case owner
  59 + when VirtualCommunity
  60 + nil
  61 + when Profile
  62 + owner
  63 + end
  64 + end
6 65  
7 66 end
... ...
app/models/profile.rb
  1 +# A Profile is the representation and web-presence of an individual or an
  2 +# organization. Every Profile is attached to its VirtualCommunity of origin,
  3 +# which by default is the one returned by VirtualCommunity:default.
1 4 class Profile < ActiveRecord::Base
  5 +
  6 + has_many :domains, :as => :owner
  7 + belongs_to :virtual_community
  8 +
2 9 validates_presence_of :identifier
3 10 validates_format_of :identifier, :with => /^[a-z][a-z0-9_]+[a-z0-9]$/
  11 +
  12 + # creates a new Profile. By default, it is attached to the default
  13 + # VirtualCommunity (see VirtualCommunity#default)
  14 + def initialize(*args)
  15 + super(*args)
  16 + self.virtual_community ||= VirtualCommunity.default
  17 + end
  18 +
4 19 end
... ...
app/models/virtual_community.rb
1 1 class VirtualCommunity < ActiveRecord::Base
2 2  
  3 + # One VirtualCommunity can be reached by many domains
3 4 has_many :domains, :as => :owner
4 5  
  6 + # <tt>name</tt> is mandatory
  7 + validates_presence_of :name
  8 +
  9 + # only one virtual community can be the default one
  10 + validates_uniqueness_of :is_default, :if => (lambda do |virtual_community| virtual_community.is_default? end), :message => _('Only one Virtual Community can be the default one')
  11 +
  12 + # VirtualCommunity configuration
5 13 serialize :configuration, Hash
  14 +
  15 + # a Hash with configuration parameters for this community. The configuration
  16 + # contains general parameters of the VirtualCommunity as well as
  17 + # enabling/disabling optional features.
6 18 def configuration
7 19 self[:configuration] ||= Hash.new
8 20 end
9 21  
  22 + # the default VirtualCommunity.
  23 + def self.default
  24 + self.find(:first, :conditions => [ 'is_default = ?', true ] )
  25 + end
  26 +
10 27 end
... ...
app/views/home/index.rhtml 0 → 100644
... ... @@ -0,0 +1 @@
  1 +Start page of Virtual Community <%# @virtual_community.name %>
... ...
config/routes.rb
... ... @@ -11,7 +11,7 @@ ActionController::Routing::Routes.draw do |map|
11 11  
12 12 # You can have the root of your site routed by hooking up ''
13 13 # -- just remember to delete public/index.html.
14   - # map.connect '', :controller => "welcome"
  14 + map.connect '', :controller => "home"
15 15  
16 16 # Allow downloading Web Service WSDL as a file with an extension
17 17 # instead of a file named 'wsdl'
... ...
db/migrate/001_create_virtual_communities.rb
... ... @@ -2,8 +2,8 @@ class CreateVirtualCommunities &lt; ActiveRecord::Migration
2 2 def self.up
3 3 create_table :virtual_communities do |t|
4 4 t.column :name, :string
5   - t.column :domain, :string
6 5 t.column :configuration, :text
  6 + t.column :is_default, :boolean
7 7 end
8 8 end
9 9  
... ...
db/migrate/002_create_profiles.rb
... ... @@ -3,6 +3,7 @@ class CreateProfiles &lt; ActiveRecord::Migration
3 3 create_table :profiles do |t|
4 4 t.column :name, :string
5 5 t.column :identifier, :string
  6 + t.column :virtual_community_id, :integer
6 7 end
7 8 end
8 9  
... ...
db/migrate/003_create_domains.rb
... ... @@ -2,7 +2,7 @@ class CreateDomains &lt; ActiveRecord::Migration
2 2 def self.up
3 3 create_table :domains do |t|
4 4 t.column :name, :string
5   - t.column :ownwer_type, :string
  5 + t.column :owner_type, :string
6 6 t.column :owner_id, :integer
7 7 end
8 8 end
... ...
public/index.html
... ... @@ -1,277 +0,0 @@
1   -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2   - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3   -<html>
4   - <head>
5   - <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
6   - <title>Ruby on Rails: Welcome aboard</title>
7   - <style type="text/css" media="screen">
8   - body {
9   - margin: 0;
10   - margin-bottom: 25px;
11   - padding: 0;
12   - background-color: #f0f0f0;
13   - font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
14   - font-size: 13px;
15   - color: #333;
16   - }
17   -
18   - h1 {
19   - font-size: 28px;
20   - color: #000;
21   - }
22   -
23   - a {color: #03c}
24   - a:hover {
25   - background-color: #03c;
26   - color: white;
27   - text-decoration: none;
28   - }
29   -
30   -
31   - #page {
32   - background-color: #f0f0f0;
33   - width: 750px;
34   - margin: 0;
35   - margin-left: auto;
36   - margin-right: auto;
37   - }
38   -
39   - #content {
40   - float: left;
41   - background-color: white;
42   - border: 3px solid #aaa;
43   - border-top: none;
44   - padding: 25px;
45   - width: 500px;
46   - }
47   -
48   - #sidebar {
49   - float: right;
50   - width: 175px;
51   - }
52   -
53   - #footer {
54   - clear: both;
55   - }
56   -
57   -
58   - #header, #about, #getting-started {
59   - padding-left: 75px;
60   - padding-right: 30px;
61   - }
62   -
63   -
64   - #header {
65   - background-image: url("images/rails.png");
66   - background-repeat: no-repeat;
67   - background-position: top left;
68   - height: 64px;
69   - }
70   - #header h1, #header h2 {margin: 0}
71   - #header h2 {
72   - color: #888;
73   - font-weight: normal;
74   - font-size: 16px;
75   - }
76   -
77   -
78   - #about h3 {
79   - margin: 0;
80   - margin-bottom: 10px;
81   - font-size: 14px;
82   - }
83   -
84   - #about-content {
85   - background-color: #ffd;
86   - border: 1px solid #fc0;
87   - margin-left: -11px;
88   - }
89   - #about-content table {
90   - margin-top: 10px;
91   - margin-bottom: 10px;
92   - font-size: 11px;
93   - border-collapse: collapse;
94   - }
95   - #about-content td {
96   - padding: 10px;
97   - padding-top: 3px;
98   - padding-bottom: 3px;
99   - }
100   - #about-content td.name {color: #555}
101   - #about-content td.value {color: #000}
102   -
103   - #about-content.failure {
104   - background-color: #fcc;
105   - border: 1px solid #f00;
106   - }
107   - #about-content.failure p {
108   - margin: 0;
109   - padding: 10px;
110   - }
111   -
112   -
113   - #getting-started {
114   - border-top: 1px solid #ccc;
115   - margin-top: 25px;
116   - padding-top: 15px;
117   - }
118   - #getting-started h1 {
119   - margin: 0;
120   - font-size: 20px;
121   - }
122   - #getting-started h2 {
123   - margin: 0;
124   - font-size: 14px;
125   - font-weight: normal;
126   - color: #333;
127   - margin-bottom: 25px;
128   - }
129   - #getting-started ol {
130   - margin-left: 0;
131   - padding-left: 0;
132   - }
133   - #getting-started li {
134   - font-size: 18px;
135   - color: #888;
136   - margin-bottom: 25px;
137   - }
138   - #getting-started li h2 {
139   - margin: 0;
140   - font-weight: normal;
141   - font-size: 18px;
142   - color: #333;
143   - }
144   - #getting-started li p {
145   - color: #555;
146   - font-size: 13px;
147   - }
148   -
149   -
150   - #search {
151   - margin: 0;
152   - padding-top: 10px;
153   - padding-bottom: 10px;
154   - font-size: 11px;
155   - }
156   - #search input {
157   - font-size: 11px;
158   - margin: 2px;
159   - }
160   - #search-text {width: 170px}
161   -
162   -
163   - #sidebar ul {
164   - margin-left: 0;
165   - padding-left: 0;
166   - }
167   - #sidebar ul h3 {
168   - margin-top: 25px;
169   - font-size: 16px;
170   - padding-bottom: 10px;
171   - border-bottom: 1px solid #ccc;
172   - }
173   - #sidebar li {
174   - list-style-type: none;
175   - }
176   - #sidebar ul.links li {
177   - margin-bottom: 5px;
178   - }
179   -
180   - </style>
181   - <script type="text/javascript" src="javascripts/prototype.js"></script>
182   - <script type="text/javascript" src="javascripts/effects.js"></script>
183   - <script type="text/javascript">
184   - function about() {
185   - if (Element.empty('about-content')) {
186   - new Ajax.Updater('about-content', 'rails/info/properties', {
187   - method: 'get',
188   - onFailure: function() {Element.classNames('about-content').add('failure')},
189   - onComplete: function() {new Effect.BlindDown('about-content', {duration: 0.25})}
190   - });
191   - } else {
192   - new Effect[Element.visible('about-content') ?
193   - 'BlindUp' : 'BlindDown']('about-content', {duration: 0.25});
194   - }
195   - }
196   -
197   - window.onload = function() {
198   - $('search-text').value = '';
199   - $('search').onsubmit = function() {
200   - $('search-text').value = 'site:rubyonrails.org ' + $F('search-text');
201   - }
202   - }
203   - </script>
204   - </head>
205   - <body>
206   - <div id="page">
207   - <div id="sidebar">
208   - <ul id="sidebar-items">
209   - <li>
210   - <form id="search" action="http://www.google.com/search" method="get">
211   - <input type="hidden" name="hl" value="en" />
212   - <input type="text" id="search-text" name="q" value="site:rubyonrails.org " />
213   - <input type="submit" value="Search" /> the Rails site
214   - </form>
215   - </li>
216   -
217   - <li>
218   - <h3>Join the community</h3>
219   - <ul class="links">
220   - <li><a href="http://www.rubyonrails.org/">Ruby on Rails</a></li>
221   - <li><a href="http://weblog.rubyonrails.org/">Official weblog</a></li>
222   - <li><a href="http://lists.rubyonrails.org/">Mailing lists</a></li>
223   - <li><a href="http://wiki.rubyonrails.org/rails/pages/IRC">IRC channel</a></li>
224   - <li><a href="http://wiki.rubyonrails.org/">Wiki</a></li>
225   - <li><a href="http://dev.rubyonrails.org/">Bug tracker</a></li>
226   - </ul>
227   - </li>
228   -
229   - <li>
230   - <h3>Browse the documentation</h3>
231   - <ul class="links">
232   - <li><a href="http://api.rubyonrails.org/">Rails API</a></li>
233   - <li><a href="http://stdlib.rubyonrails.org/">Ruby standard library</a></li>
234   - <li><a href="http://corelib.rubyonrails.org/">Ruby core</a></li>
235   - </ul>
236   - </li>
237   - </ul>
238   - </div>
239   -
240   - <div id="content">
241   - <div id="header">
242   - <h1>Welcome aboard</h1>
243   - <h2>You&rsquo;re riding the Rails!</h2>
244   - </div>
245   -
246   - <div id="about">
247   - <h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
248   - <div id="about-content" style="display: none"></div>
249   - </div>
250   -
251   - <div id="getting-started">
252   - <h1>Getting started</h1>
253   - <h2>Here&rsquo;s how to get rolling:</h2>
254   -
255   - <ol>
256   - <li>
257   - <h2>Create your databases and edit <tt>config/database.yml</tt></h2>
258   - <p>Rails needs to know your login and password.</p>
259   - </li>
260   -
261   - <li>
262   - <h2>Use <tt>script/generate</tt> to create your models and controllers</h2>
263   - <p>To see all available options, run it without parameters.</p>
264   - </li>
265   -
266   - <li>
267   - <h2>Set up a default route and remove or rename this file</h2>
268   - <p>Routes are setup in config/routes.rb.</p>
269   - </li>
270   - </ol>
271   - </div>
272   - </div>
273   -
274   - <div id="footer">&nbsp;</div>
275   - </div>
276   - </body>
277   -</html>
278 0 \ No newline at end of file
test/fixtures/domains.yml
1 1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 2 one:
3 3 id: 1
  4 + name: colivre.net
  5 + owner_type: VirtualCommunity
  6 + owner_id: 1
4 7 two:
5 8 id: 2
  9 + name: anhetegua.net
  10 + owner_type: VirtualCommunity
  11 + owner_id: 2
  12 +three:
  13 + id: 3
  14 + name: johndoe.net
  15 + owner_type: Profile
  16 + owner_id: 1
  17 +four:
  18 + id: 4
  19 + name: jrh.net
  20 + owner_type: Profile
  21 + owner_id: 2
... ...
test/fixtures/profiles.yml
1 1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 2 first:
3 3 id: 1
  4 + name: 'John Doe'
  5 + identifier: johndoe
  6 + virtual_community_id: 1
4 7 another:
5 8 id: 2
  9 + name: 'Joe Random Hacker'
  10 + identifier: joerandomhacker
  11 + virtual_community_id: 1
... ...
test/fixtures/virtual_communities.yml
1 1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2 2 first:
3 3 id: 1
4   - name: 'Anheteguá'
5   - domain: 'anhetegua.net'
  4 + name: 'Colivre.net'
  5 + is_default: true
6 6 another:
7 7 id: 2
8   - name: 'Colivre.net'
9   - domain: 'colivre.net'
  8 + name: 'Anheteguá'
  9 + is_default: false
... ...
test/functional/home_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +require 'home_controller'
  3 +
  4 +# Re-raise errors caught by the controller.
  5 +class HomeController; def rescue_action(e) raise e end; end
  6 +
  7 +class HomeControllerTest < Test::Unit::TestCase
  8 +
  9 + fixtures :profiles, :virtual_communities, :domains
  10 +
  11 + def setup
  12 + @controller = HomeController.new
  13 + @request = ActionController::TestRequest.new
  14 + @response = ActionController::TestResponse.new
  15 + end
  16 +
  17 + def test_detection_of_virtual_community_by_host
  18 + uses_host 'www.colivre.net'
  19 + get :index
  20 + assert_template 'index'
  21 +
  22 + assert_kind_of VirtualCommunity, assigns(:virtual_community)
  23 +
  24 + assert_kind_of Domain, assigns(:domain)
  25 + assert_equal 'colivre.net', assigns(:domain).name
  26 +
  27 + assert_nil assigns(:profile)
  28 + end
  29 +
  30 + def test_detect_profile_by_host
  31 + uses_host 'www.jrh.net'
  32 + get :index
  33 + assert_template 'index'
  34 +
  35 + assert_kind_of VirtualCommunity, assigns(:virtual_community)
  36 +
  37 + assert_kind_of Domain, assigns(:domain)
  38 + assert_equal 'jrh.net', assigns(:domain).name
  39 +
  40 + assert_kind_of Profile, assigns(:profile)
  41 + end
  42 +end
... ...
test/test_helper.rb
... ... @@ -25,4 +25,11 @@ class Test::Unit::TestCase
25 25 self.use_instantiated_fixtures = false
26 26  
27 27 # Add more helper methods to be used by all tests here...
  28 +
  29 + private
  30 +
  31 + def uses_host(name)
  32 + @request.instance_variable_set('@host', name)
  33 + end
  34 +
28 35 end
... ...
test/unit/domain_test.rb
1 1 require File.dirname(__FILE__) + '/../test_helper'
2 2  
3 3 class DomainTest < Test::Unit::TestCase
4   - fixtures :domains
  4 + fixtures :domains, :virtual_communities, :profiles
5 5  
6 6 # Replace this with your real tests.
7 7 def test_domain_name_format
8 8 c = Domain.new
9   - c.valid?
  9 + assert !c.valid?
10 10 assert c.errors.invalid?(:name)
11 11  
12 12 c.name = 'bliblibli'
13   - c.valid?
  13 + assert !c.valid?
  14 + assert c.errors.invalid?(:name)
  15 +
  16 + c.name = 'EXAMPLE.NET'
  17 + assert !c.valid?
14 18 assert c.errors.invalid?(:name)
15 19  
16 20 c.name = 'test.net'
17 21 c.valid?
18   - assert ! c.errors.invalid?(:name)
  22 + assert !c.errors.invalid?(:name)
19 23 end
20 24  
21 25 def test_owner
... ... @@ -25,4 +29,58 @@ class DomainTest &lt; Test::Unit::TestCase
25 29 assert_kind_of VirtualCommunity, d.owner
26 30 end
27 31  
  32 + def test_get_domain_name
  33 + assert_equal 'example.net', Domain.extract_domain_name('www.example.net')
  34 + assert_equal 'example.net', Domain.extract_domain_name('WWW.EXAMPLE.NET')
  35 + end
  36 +
  37 + def test_name_cannot_have_www
  38 + d = Domain.new
  39 + d.name = 'www.example.net'
  40 + d.valid?
  41 + assert d.errors.invalid?(:name)
  42 +
  43 + d.name = 'example.net'
  44 + d.valid?
  45 + assert !d.errors.invalid?(:name)
  46 + end
  47 +
  48 + def test_find_by_name
  49 + Domain.delete_all
  50 + Domain.create(:name => 'example.net')
  51 + d1 = Domain.find_by_name('example.net')
  52 + d2 = Domain.find_by_name('www.example.net')
  53 + assert !d1.nil?
  54 + assert !d2.nil?
  55 + assert d1 == d2
  56 + end
  57 +
  58 + def test_unique_name
  59 + Domain.delete_all
  60 + assert Domain.create(:name => 'example.net')
  61 +
  62 + d = Domain.new(:name => 'example.net')
  63 + assert !d.valid?
  64 + assert d.errors.invalid?(:name)
  65 + end
  66 +
  67 + def test_virtual_community
  68 + # domain directly linked to VirtualCommunity
  69 + domain = Domain.find_by_name('colivre.net')
  70 + assert_kind_of VirtualCommunity, domain.owner
  71 + assert_kind_of VirtualCommunity, domain.virtual_community
  72 +
  73 + # domain linked to Profile
  74 + domain = Domain.find_by_name('johndoe.net')
  75 + assert_kind_of Profile, domain.owner
  76 + assert_kind_of VirtualCommunity, domain.virtual_community
  77 + end
  78 +
  79 + def test_profile
  80 + # domain linked to profile
  81 + assert_not_nil Domain.find_by_name('johndoe.net').profile
  82 + # domain linked to VirtualCommunity
  83 + assert_nil Domain.find_by_name('colivre.net').profile
  84 + end
  85 +
28 86 end
... ...
test/unit/profile_test.rb
1 1 require File.dirname(__FILE__) + '/../test_helper'
2 2  
3 3 class ProfileTest < Test::Unit::TestCase
4   - fixtures :profiles
  4 + fixtures :profiles, :virtual_communities
5 5  
6 6 def test_identifier_validation
7 7 p = Profile.new
... ... @@ -29,4 +29,14 @@ class ProfileTest &lt; Test::Unit::TestCase
29 29 assert ! p.errors.invalid?(:identifier)
30 30 end
31 31  
  32 + def test_has_domains
  33 + p = Profile.new
  34 + assert_kind_of Array, p.domains
  35 + end
  36 +
  37 + def test_belongs_to_virtual_community_and_has_default
  38 + p = Profile.new
  39 + assert_kind_of VirtualCommunity, p.virtual_community
  40 + end
  41 +
32 42 end
... ...
test/unit/virtual_community_test.rb
... ... @@ -4,8 +4,22 @@ class VirtualCommunityTest &lt; Test::Unit::TestCase
4 4 fixtures :virtual_communities
5 5  
6 6 def test_configuration
7   - c = VirtualCommunity.new
8   - assert_kind_of Hash, c.configuration
  7 + vc = VirtualCommunity.new
  8 + assert_kind_of Hash, vc.configuration
  9 + end
  10 +
  11 + def test_exists_default_and_it_is_unique
  12 + VirtualCommunity.delete_all
  13 + vc = VirtualCommunity.new(:name => 'Test Community')
  14 + vc.is_default = true
  15 + assert vc.save
  16 +
  17 + vc2 = VirtualCommunity.new(:name => 'Another Test Community')
  18 + vc2.is_default = true
  19 + assert !vc2.valid?
  20 + assert vc2.errors.invalid?(:is_default)
  21 +
  22 + assert_equal vc, VirtualCommunity.default
9 23 end
10 24  
11 25 end
... ...