This commit is contained in:
dinedine 2010-08-02 16:16:08 +02:00
commit c76cd6527c
44 changed files with 754 additions and 173 deletions

22
Gemfile
View File

@ -4,13 +4,16 @@ source 'http://rubygems.org'
gem 'rails', '3.0.0.rc' gem 'rails', '3.0.0.rc'
gem 'liquid', '2.0.0' gem 'liquid', '2.0.0'
# i think we'll need to fork our templating language
# gem 'locomotive-liquid'
gem 'bson_ext', '>= 1.0.1' gem 'bson_ext', '>= 1.0.1'
gem 'mongoid', '2.0.0.beta.15' gem 'mongoid', :git => "git://github.com/durran/mongoid.git", :ref => "e387a0d1dc74da057472"
gem 'mongoid_acts_as_tree', '0.1.5' gem 'mongoid_acts_as_tree', '0.1.5'
gem 'mongo_session_store', '2.0.0.pre' gem 'mongo_session_store', '2.0.0.pre'
gem 'warden' gem 'warden'
gem 'devise', '1.1.rc2' gem 'devise', :git => "git://github.com/plataformatec/devise.git"
gem 'haml', '3.0.13' gem 'haml', '3.0.15'
gem 'rmagick', '2.12.2' gem 'rmagick', '2.12.2'
gem 'aws' gem 'aws'
gem 'mimetype-fu', :require => 'mimetype_fu' gem 'mimetype-fu', :require => 'mimetype_fu'
@ -29,7 +32,6 @@ group :development do
gem 'mongrel' gem 'mongrel'
gem 'cgi_multipart_eof_fix' gem 'cgi_multipart_eof_fix'
gem 'fastthread' gem 'fastthread'
# gem 'mongrel_experimental'
end end
group :test, :development do group :test, :development do
@ -37,11 +39,17 @@ group :test, :development do
end end
group :test do group :test do
gem 'autotest' gem "autotest"
gem 'rspec-rails', '2.0.0.beta.18' gem "growl-glue"
gem 'rspec-rails', '2.0.0.beta.19'
gem 'factory_girl_rails' gem 'factory_girl_rails'
gem "pickle", :git => "http://github.com/codegram/pickle.git"
gem "pickle-mongoid"
gem 'capybara' gem 'capybara'
gem 'capybara-envjs', '>= 0.1.5'
# would be nice..
# gem "capybara-envjs"
gem 'database_cleaner' gem 'database_cleaner'
gem 'cucumber' gem 'cucumber'
gem 'cucumber-rails' gem 'cucumber-rails'

View File

@ -1,3 +1,9 @@
GIT
remote: git://github.com/durran/mongoid.git
revision: e387a0d
ref: e387a0d1dc74da057472
specs:
GIT GIT
remote: git://github.com/floehopper/mocha.git remote: git://github.com/floehopper/mocha.git
revision: d1715ff revision: d1715ff
@ -5,6 +11,20 @@ GIT
mocha (0.9.8.20090918115329) mocha (0.9.8.20090918115329)
rake rake
GIT
remote: git://github.com/plataformatec/devise.git
revision: 01c272c
specs:
devise (1.2.0)
bcrypt-ruby (~> 2.1.2)
warden (~> 0.10.7)
GIT
remote: http://github.com/codegram/pickle.git
revision: 2834204
specs:
pickle (0.3.0)
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
@ -55,9 +75,6 @@ GEM
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
selenium-webdriver (>= 0.0.3) selenium-webdriver (>= 0.0.3)
capybara-envjs (0.1.6)
capybara (>= 0.3.9)
envjs (>= 0.3.7)
carrierwave (0.5.0.beta2) carrierwave (0.5.0.beta2)
activesupport (>= 3.0.0.beta4) activesupport (>= 3.0.0.beta4)
cgi_multipart_eof_fix (2.5.0) cgi_multipart_eof_fix (2.5.0)
@ -75,12 +92,7 @@ GEM
culerity (0.2.10) culerity (0.2.10)
daemons (1.1.0) daemons (1.1.0)
database_cleaner (0.5.2) database_cleaner (0.5.2)
devise (1.1.rc2)
bcrypt-ruby (~> 2.1.2)
warden (~> 0.10.7)
diff-lcs (1.1.2) diff-lcs (1.1.2)
envjs (0.3.7)
johnson (>= 2.0.0.pre3)
erubis (2.6.6) erubis (2.6.6)
abstract (>= 1.0.0) abstract (>= 1.0.0)
factory_girl (1.3.1) factory_girl (1.3.1)
@ -98,7 +110,8 @@ GEM
gherkin (2.1.5) gherkin (2.1.5)
trollop (~> 1.16.2) trollop (~> 1.16.2)
git (1.2.5) git (1.2.5)
haml (3.0.13) growl-glue (1.0.7)
haml (3.0.15)
has_scope (0.5.0) has_scope (0.5.0)
heroku (1.9.13) heroku (1.9.13)
json_pure (>= 1.2.0, < 1.5.0) json_pure (>= 1.2.0, < 1.5.0)
@ -115,8 +128,6 @@ GEM
gemcutter (>= 0.1.0) gemcutter (>= 0.1.0)
git (>= 1.2.5) git (>= 1.2.5)
rubyforge (>= 2.0.0) rubyforge (>= 2.0.0)
johnson (2.0.0.pre3)
stackdeck (~> 0.2)
json_pure (1.4.3) json_pure (1.4.3)
launchy (0.3.7) launchy (0.3.7)
configuration (>= 0.0.5) configuration (>= 0.0.5)
@ -148,6 +159,9 @@ GEM
fastthread (>= 1.0.1) fastthread (>= 1.0.1)
gem_plugin (>= 0.2.3) gem_plugin (>= 0.2.3)
nokogiri (1.4.3.1) nokogiri (1.4.3.1)
pickle-mongoid (0.1.6)
mongoid (>= 2.0.0.beta.7)
pickle (>= 0.3.0)
polyglot (0.3.1) polyglot (0.3.1)
rack (1.2.1) rack (1.2.1)
rack-mount (0.6.9) rack-mount (0.6.9)
@ -180,9 +194,9 @@ GEM
rspec-expectations (2.0.0.beta.19) rspec-expectations (2.0.0.beta.19)
diff-lcs (>= 1.1.2) diff-lcs (>= 1.1.2)
rspec-mocks (2.0.0.beta.19) rspec-mocks (2.0.0.beta.19)
rspec-rails (2.0.0.beta.18) rspec-rails (2.0.0.beta.19)
rspec (>= 2.0.0.beta.14) rspec (= 2.0.0.beta.19)
webrat (>= 0.7.0) webrat (>= 0.7.2.beta.1)
ruby-debug (0.10.3) ruby-debug (0.10.3)
columnize (>= 0.1) columnize (>= 0.1)
ruby-debug-base (~> 0.10.3.0) ruby-debug-base (~> 0.10.3.0)
@ -196,7 +210,6 @@ GEM
json_pure json_pure
rubyzip rubyzip
spork (0.8.4) spork (0.8.4)
stackdeck (0.2.0)
term-ansicolor (1.0.5) term-ansicolor (1.0.5)
thor (0.14.0) thor (0.14.0)
treetop (1.4.8) treetop (1.4.8)
@ -206,7 +219,7 @@ GEM
uuidtools (2.1.1) uuidtools (2.1.1)
warden (0.10.7) warden (0.10.7)
rack (>= 1.0.0) rack (>= 1.0.0)
webrat (0.7.1) webrat (0.7.2.beta.1)
nokogiri (>= 1.2.0) nokogiri (>= 1.2.0)
rack (>= 1.0) rack (>= 1.0)
rack-test (>= 0.5.3) rack-test (>= 0.5.3)
@ -223,17 +236,17 @@ DEPENDENCIES
aws aws
bson_ext (>= 1.0.1) bson_ext (>= 1.0.1)
capybara capybara
capybara-envjs (>= 0.1.5)
carrierwave (= 0.5.0.beta2) carrierwave (= 0.5.0.beta2)
cgi_multipart_eof_fix cgi_multipart_eof_fix
cucumber cucumber
cucumber-rails cucumber-rails
database_cleaner database_cleaner
devise (= 1.1.rc2) devise!
factory_girl_rails factory_girl_rails
fastthread fastthread
formtastic-rails3 (= 1.0.0.beta3) formtastic-rails3 (= 1.0.0.beta3)
haml (= 3.0.13) growl-glue
haml (= 3.0.15)
heroku heroku
httparty (= 0.6.1) httparty (= 0.6.1)
inherited_resources (= 1.1.2) inherited_resources (= 1.1.2)
@ -243,12 +256,14 @@ DEPENDENCIES
mimetype-fu mimetype-fu
mocha! mocha!
mongo_session_store (= 2.0.0.pre) mongo_session_store (= 2.0.0.pre)
mongoid (= 2.0.0.beta.15) mongoid!
mongoid_acts_as_tree (= 0.1.5) mongoid_acts_as_tree (= 0.1.5)
mongrel mongrel
pickle!
pickle-mongoid
rails (= 3.0.0.rc) rails (= 3.0.0.rc)
rmagick (= 2.12.2) rmagick (= 2.12.2)
rspec-rails (= 2.0.0.beta.18) rspec-rails (= 2.0.0.beta.19)
ruby-debug ruby-debug
spork spork
warden warden

View File

@ -7,7 +7,7 @@ require 'rake'
require 'rake/testtask' require 'rake/testtask'
require 'rake/rdoctask' require 'rake/rdoctask'
Rails::Application.load_tasks Locomotive::Application.load_tasks
begin begin
require "jeweler" require "jeweler"

View File

@ -1,12 +1,11 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
protect_from_forgery protect_from_forgery
# rescue_from Exception, :with => :render_error protected
private rescue_from Exception, :with => :render_error
def render_error(exception) def render_error
ActionDispatch::ShowExceptions.new(self).send(:log_error, exception) # TODO: find a better way to access log_error
render :template => "/admin/errors/500", :layout => 'admin/box', :status => 500 render :template => "/admin/errors/500", :layout => 'admin/box', :status => 500
end end
end end

View File

@ -7,7 +7,7 @@ class AssetCollection
field :slug field :slug
## associations ## ## associations ##
belongs_to_related :site referenced_in :site
embeds_many :assets embeds_many :assets
## behaviours ## ## behaviours ##

View File

@ -23,7 +23,7 @@ class ContentInstance
before_create :add_to_list_bottom before_create :add_to_list_bottom
## named scopes ## ## named scopes ##
named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
## methods ## ## methods ##

View File

@ -12,7 +12,7 @@ class ContentType
field :api_enabled, :type => Boolean, :default => false field :api_enabled, :type => Boolean, :default => false
## associations ## ## associations ##
belongs_to_related :site referenced_in :site
embeds_many :contents, :class_name => 'ContentInstance' do embeds_many :contents, :class_name => 'ContentInstance' do
def visible def visible
@target.find_all { |c| c.visible? } @target.find_all { |c| c.visible? }

View File

@ -6,9 +6,11 @@ module Models
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
before_validation do |p|
before_create { |p| p.parts << PagePart.build_body_part if p.parts.empty? } if p.parts.empty?
p.parts << PagePart.build_body_part(p.respond_to?(:body) ? p.body : nil)
end
end
end end
module InstanceMethods module InstanceMethods

View File

@ -7,7 +7,7 @@ module Models
included do included do
belongs_to_related :content_type referenced_in :content_type
field :templatized, :type => Boolean, :default => false field :templatized, :type => Boolean, :default => false

View File

@ -1,7 +1,7 @@
class Layout < LiquidTemplate class Layout < LiquidTemplate
## associations ## ## associations ##
has_many_related :pages references_many :pages
embeds_many :parts, :class_name => 'PagePart' embeds_many :parts, :class_name => 'PagePart'
## callbacks ## ## callbacks ##

View File

@ -8,7 +8,7 @@ class LiquidTemplate
field :value field :value
## associations ## ## associations ##
belongs_to_related :site referenced_in :site
## callbacks ## ## callbacks ##
before_validation :normalize_slug before_validation :normalize_slug

View File

@ -6,7 +6,7 @@ class Membership
field :admin, :type => Boolean, :default => false field :admin, :type => Boolean, :default => false
## associations ## ## associations ##
belongs_to_related :account referenced_in :account
embedded_in :site, :inverse_of => :memberships embedded_in :site, :inverse_of => :memberships
## validations ## ## validations ##

View File

@ -15,9 +15,12 @@ class Page
field :published, :type => Boolean, :default => false field :published, :type => Boolean, :default => false
field :cache_strategy, :default => 'none' field :cache_strategy, :default => 'none'
# allows newly pages to have a default body
attr_accessor :body
## associations ## ## associations ##
belongs_to_related :site referenced_in :site
belongs_to_related :layout referenced_in :layout
embeds_many :parts, :class_name => 'PagePart' embeds_many :parts, :class_name => 'PagePart'
## callbacks ## ## callbacks ##
@ -31,10 +34,10 @@ class Page
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 } validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
## named scopes ## ## named scopes ##
named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
named_scope :index, :where => { :slug => 'index', :depth => 0 } scope :index, :where => { :slug => 'index', :depth => 0 }
named_scope :not_found, :where => { :slug => '404', :depth => 0 } scope :not_found, :where => { :slug => '404', :depth => 0 }
named_scope :published, :where => { :published => true } scope :published, :where => { :published => true }
## behaviours ## ## behaviours ##
liquify_template :joined_parts liquify_template :joined_parts

View File

@ -16,7 +16,7 @@ class PagePart
validates_presence_of :name, :slug validates_presence_of :name, :slug
## named scopes ## ## named scopes ##
named_scope :enabled, where(:disabled => false) scope :enabled, where(:disabled => false)
## methods ## ## methods ##
@ -24,10 +24,10 @@ class PagePart
"{% capture content_for_#{self.slug} %}#{self.value}{% endcapture %}" "{% capture content_for_#{self.slug} %}#{self.value}{% endcapture %}"
end end
def self.build_body_part def self.build_body_part(body_content = nil)
self.new({ self.new({
:name => I18n.t('attributes.defaults.page_parts.name'), :name => I18n.t('attributes.defaults.page_parts.name'),
:value => I18n.t('attributes.defaults.pages.other.body'), :value => body_content || I18n.t('attributes.defaults.pages.other.body'),
:slug => 'layout' :slug => 'layout'
}) })
end end

View File

@ -10,12 +10,12 @@ class Site
field :meta_description field :meta_description
## associations ## ## associations ##
has_many_related :pages references_many :pages
has_many_related :layouts references_many :layouts
has_many_related :snippets references_many :snippets
has_many_related :theme_assets references_many :theme_assets
has_many_related :asset_collections references_many :asset_collections
has_many_related :content_types references_many :content_types
embeds_many :memberships embeds_many :memberships
## validations ## ## validations ##
@ -31,8 +31,8 @@ class Site
after_destroy :destroy_in_cascade! after_destroy :destroy_in_cascade!
## named scopes ## ## named scopes ##
named_scope :match_domain, lambda { |domain| { :where => { :domains => domain } } } scope :match_domain, lambda { |domain| { :where => { :domains => domain } } }
named_scope :match_domain_with_exclusion_of, lambda { |domain, site| { :where => { :domains => domain, :_id.ne => site.id } } } scope :match_domain_with_exclusion_of, lambda { |domain, site| { :where => { :domains => domain, :_id.ne => site.id } } }
## methods ## ## methods ##
@ -82,7 +82,8 @@ class Site
self.pages.create({ self.pages.create({
:slug => slug, :slug => slug,
:title => I18n.t("attributes.defaults.pages.#{slug}.title"), :title => I18n.t("attributes.defaults.pages.#{slug}.title"),
:body => I18n.t("attributes.defaults.pages.#{slug}.body") :body => I18n.t("attributes.defaults.pages.#{slug}.body"),
:published => true
}) })
end end
end end

View File

@ -15,7 +15,7 @@ class ThemeAsset
mount_uploader :source, ThemeAssetUploader mount_uploader :source, ThemeAssetUploader
## associations ## ## associations ##
belongs_to_related :site referenced_in :site
## callbacks ## ## callbacks ##
before_validation :sanitize_slug before_validation :sanitize_slug

View File

@ -33,6 +33,7 @@
%ul{ :id => "parts" } %ul{ :id => "parts" }
= f.fields_for :parts do |g| = f.fields_for :parts do |g|
%li{ :style => "#{'display: none' if g.object.disabled?}" } %li{ :style => "#{'display: none' if g.object.disabled?}" }
= g.label :value, g.object.name, :style => "display:none"
%code= g.text_area :value %code= g.text_area :value
= g.hidden_field :name = g.hidden_field :name
= g.hidden_field :slug = g.hidden_field :slug

View File

@ -1,2 +1,2 @@
default: features --format pretty --color --tags ~@wip default: features --require features --format pretty --color --tags ~@wip
wip: features --tags @wip wip: features --require features --tags @wip

View File

@ -11,7 +11,6 @@ Locomotive::Application.configure do
# Show full error reports and disable caching # Show full error reports and disable caching
config.consider_all_requests_local = true config.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
# Don't care if the mailer can't send # Don't care if the mailer can't send

View File

@ -121,12 +121,6 @@ Devise.setup do |config|
# are using only default views. # are using only default views.
config.scoped_views = true config.scoped_views = true
# By default, devise detects the role accessed based on the url. So whenever
# accessing "/users/sign_in", it knows you are accessing an User. This makes
# routes as "/sign_in" not possible, unless you tell Devise to use the default
# scope, setting true below.
config.use_default_scope = true
# Configure the default scope used by Devise. By default it's the first devise # Configure the default scope used by Devise. By default it's the first devise
# role declared in your routes. # role declared in your routes.
config.default_scope = :account config.default_scope = :account

View File

@ -1,5 +1,5 @@
# Locomotive::Application.routes.draw do |map| # Locomotive::Application.routes.draw do |map|
Rails.application.routes.draw do |map| Rails.application.routes.draw do
constraints(Locomotive::Routing::DefaultConstraint) do constraints(Locomotive::Routing::DefaultConstraint) do
root :to => 'home#show' root :to => 'home#show'

139
doc/PICKLE_STEPS Normal file
View File

@ -0,0 +1,139 @@
== API
==== Given steps
"Given <b>a model</b> exists", e.g.
Given a user exists
Given a user: "fred" exists
Given the user exists
"Given <b>a model</b> exists with <b>fields</b>", e.g.
Given a user exists with name: "Fred"
Given a user exists with name: "Fred", activated: false
You can refer to other models in the fields
Given a user exists
And a post exists with author: the user
Given a person: "fred" exists
And a person: "ethel" exists
And a fatherhood exists with parent: user "fred", child: user "ethel"
"Given <b>n models</b> exist", e.g.
Given 10 users exist
"Given <b>n models</b> exist with <b>fields</b>", examples:
Given 10 users exist with activated: false
"Given the following <b>models</b> exist:", examples:
Given the following users exist
| name | activated |
| Fred | false |
| Ethel | true |
==== Then steps
===== Asserting existence of models
"Then <b>a model</b> should exist", e.g.
Then a user should exist
"Then <b>a model</b> should exist with <b>fields</b>", e.g.
Then a user: "fred" should exist with name: "Fred" # we can label the found user for later use
You can use other models, booleans, numerics, and strings as fields
Then a person should exist with child: person "ethel"
Then a user should exist with activated: false
Then a user should exist with activated: true, email: "fred@gmail.com"
"Then <b>n models</b> should exist", e.g.
Then 10 events should exist
"Then <b>n models</b> should exist with <b>fields</b>", e.g.
Then 2 people should exist with father: person "fred"
"Then the following <b>models</b> exist". This allows the creation of multiple models
using a table syntax. Using a column with the singularized name of the model creates a referenceable model. E.g.
Then the following users exist:
| name | activated |
| Freddy | false |
Then the following users exist:
| user | name | activated |
| Fred | Freddy | false |
===== Asserting associations
One-to-one assocs: "Then <b>a model</b> should be <b>other model</b>'s <b>association</b>", e.g.
Then the person: "fred" should be person: "ethel"'s father
Many-to-one assocs: "Then <b>a model</b> should be [in|one of] <b>other model</b>'s <b>association</b>", e.g.
Then the person: "ethel" should be one of person: "fred"'s children
Then the comment should be in the post's comments
===== Asserting predicate methods
"Then <b>a model</b> should [be|have] [a|an] <b>predicate</b>", e.g.
Then the user should have a status # => user.status.should be_present
Then the user should have a stale password # => user.should have_stale_password
Then the car: "batmobile" should be fast # => car.should be_fast
"Then <b>a model</b> should not [be|have] [a|an] <b>predicate</b>", e.g.
Then person: "fred" should not be childless # => fred.should_not be_childless
=== Regexps for use in your own steps
By default you get some regexps available in the main namespace for use
in creating your own steps: `capture_model`, `capture_fields`, and others (see lib/pickle.rb)
(You can use any of the regexps that Pickle uses by using the Pickle.parser namespace, see
Pickle::Parser::Matchers for the methods available)
*capture_model*
Given /^#{capture_model} exists$/ do |model_name|
model(model_name).should_not == nil
end
Then /^I should be at the (.*?) page$/ |page|
if page =~ /#{capture_model}'s/
url_for(model($1))
else
# ...
end
end
Then /^#{capture_model} should be one of #{capture_model}'s posts$/ do |post, forum|
post = model!(post)
forum = model!(forum)
forum.posts.should include(post)
end
*capture_fields*
This is useful for setting attributes, and knows about pickle model names so that you
can build up composite objects with ease
Given /^#{capture_model} exists with #{capture_fields}$/ do |model_name, fields|
create_model(model_name, fields)
end
# example of use
Given a user exists
And a post exists with author: the user # this step will assign the above user as :author on the post

View File

@ -1,2 +0,0 @@
Use this README file to introduce your application and point to useful places in the API for learning more.
Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.

19
doc/autotest_setup Normal file
View File

@ -0,0 +1,19 @@
# ADD THIS to your ~/.autotest
require 'rubygems'
require 'growl_glue'
GrowlGlue::Autotest.initialize
Autotest.add_hook :initialize do |at|
# Ignore files in tmp/
at.add_exception %r%^\./tmp%
at.add_exception %r%^\./sass-cache%
at.add_exception %r%^\./log%
at.add_exception %r%^\./spec/tmp%
at.add_exception %r%^\./doc%
end
# RUN `autotest` in order to autotest your specs
# RUN `AUTOFEATURE=true autotest` in order to autotest your cucumber features

75
doc/feature_list.txt Normal file
View File

@ -0,0 +1,75 @@
General Cucumbering Strategy:
Admin Pages
Contents
Pages
- Create a Page (DONE)
- Create a Sub Page
- Create another Sub Page
- Move Sub Pages around (use Selenium for this)
- Edit a Page (DONE)
- Delete a Page
Models
- Create a New Model
- Add a Few Field Types
- Edit a Model
- Add a New Model Entry
- Delete a Model Entry
Assets
- Create a New Collection
- Edit Custom Fields
- Remove a Collection
- Create an Asset
- Delete an Asset (broken i think)
- Edit an Asset
Settings
Site
- Edit Settings
- Add Access Point
- Remove Access Point
- Add Account
- Remove Account
Layouts
- Add New Layout
- Add Images to Layout
- Assign Layout to a Page
Snippets
- Create new Snippet
- Edit Snippet
- Delete Snippet
- Use Snippet in a Page
Theme files
- Add New Stylesheet
- Edit Stylesheet
- Delete Stylesheet
- Add New Javascript
- Edit Javascript
- Delete Javascript
- Add New Image
- Edit Image
- Delete Image
My Account
- Edit Password
- Edit Email
- Change Language
- Create New Site
Authentication Stuff
- Log In
- Log out
- Forgot password
Rendering Features
- Basic render (DONE)
- using a layout (DONE)
- using a layout with multiple parts (DONE)
- using snippets
- referencing images
- using data models
- loop through data models

View File

@ -1,11 +1,13 @@
@site_up Feature: Cross Domain Authentication
@another_site_up
@authenticated
Feature:
In order to manage a new site I created In order to manage a new site I created
As an administrator signed in another site of mine As an administrator signed in another site of mine
I want to bypass the authentication I want to bypass the authentication
Background:
Given I have the site: "test site" set up
And I have the site: "another site" set up
And I am an authenticated user
Scenario: Successful authentication Scenario: Successful authentication
When I go to pages When I go to pages
Then I should see "Locomotive test website" Then I should see "Locomotive test website"

View File

@ -1,19 +1,21 @@
@site_up
Feature: Login Feature: Login
In order to access locomotive admin panel In order to access locomotive admin panel
As an administrator As an administrator
I want to log in I want to log in
Background:
Given I have the site: "test site" set up
Scenario: Successful authentication Scenario: Successful authentication
When I go to login When I go to login
And I fill in "admin_email" with "admin@locomotiveapp.org" And I fill in "Email" with "admin@locomotiveapp.org"
And I fill in "admin_password" with "easyone" And I fill in "Password" with "easyone"
And I press "Log in" And I press "Log in"
Then I should see "Listing pages" Then I should see "Listing pages"
Scenario: Failed authentication Scenario: Failed authentication
When I go to login When I go to login
And I fill in "admin_email" with "admin@locomotiveapp.org" And I fill in "Email" with "admin@locomotiveapp.org"
And I fill in "admin_password" with "" And I fill in "Password" with ""
And I press "Log in" And I press "Log in"
Then I should not see "Listing pages" Then I should not see "Listing pages"

View File

@ -1,10 +1,12 @@
@site_up Feature: Manage Pages
@authenticated
Feature: Manage Skills
In order to manage pages In order to manage pages
As an administrator As an administrator
I want to add/edit/delete pages of my site I want to add/edit/delete pages of my site
Background:
Given I have the site: "test site" set up
And I am an authenticated user
Scenario: Pages list is not accessible for non authenticated accounts Scenario: Pages list is not accessible for non authenticated accounts
Given I am not authenticated Given I am not authenticated
When I go to pages When I go to pages
@ -16,7 +18,7 @@ Scenario: Creating a valid page
And I fill in "Title" with "Test" And I fill in "Title" with "Test"
And I fill in "Slug" with "test" And I fill in "Slug" with "test"
And I select "Home page" from "Parent" And I select "Home page" from "Parent"
And I fill in "page_parts_attributes_0_value" with "Lorem ipsum...." And I fill in "Body" with "Lorem ipsum...."
And I press "Create" And I press "Create"
Then I should see "Page was successfully created." Then I should see "Page was successfully created."
And I should have "Lorem ipsum...." in the test page layout And I should have "Lorem ipsum...." in the test page layout
@ -25,7 +27,7 @@ Scenario: Updating a valid page
When I go to pages When I go to pages
And I follow "Home page" And I follow "Home page"
And I fill in "Title" with "Home page !" And I fill in "Title" with "Home page !"
And I fill in "page_parts_attributes_0_value" with "My new content is here" And I fill in "Body" with "My new content is here"
And I press "Update" And I press "Update"
Then I should see "Page was successfully updated." Then I should see "Page was successfully updated."
And I should have "My new content is here" in the index page layout And I should have "My new content is here" in the index page layout

View File

@ -0,0 +1,88 @@
Feature: Engine
As a website user
I want to be able to view someones created locomotive pages
Background:
Given I have the site: "test site" set up
Scenario: Simple Page
Given a simple page named "hello-world" with the body:
"""
Hello World
"""
When I view the rendered page at "/hello-world"
Then the rendered output should look like:
"""
Hello World
"""
Scenario: Simple Page with layout
Given a layout named "above_and_below" with the source:
"""
<div class="header"></div>
{{ content_for_layout }}
<div class="footer"></div>
"""
And a page named "hello-world-with-layout" with the layout "above_and_below" and the body:
"""
Hello World
"""
When I view the rendered page at "/hello-world-with-layout"
Then the rendered output should look like:
"""
<div class="header"></div>
Hello World
<div class="footer"></div>
"""
Scenario: Page with Parts
Given a layout named "layout_with_sidebar" with the source:
"""
<div class="header"></div>
<div class="content">
<div class="sidebar">{{ content_for_sidebar }}</div>
<div class="body">
{{ content_for_layout }}
</div>
</div>
<div class="footer"></div>
"""
And a page named "hello-world-multipart" with the layout "layout_with_sidebar" and the body:
"""
IM IN UR BODY OUTPUTTING SUM CODEZ!!
"""
And the page named "hello-world-multipart" has the part "sidebar" with the content:
"""
IM IN UR SIDEBAR PUTTING OUT LINKZ
"""
When I view the rendered page at "/hello-world-multipart"
Then the rendered output should look like:
"""
<div class="header"></div>
<div class="content">
<div class="sidebar">IM IN UR SIDEBAR PUTTING OUT LINKZ</div>
<div class="body">
IM IN UR BODY OUTPUTTING SUM CODEZ!!
</div>
</div>
<div class="footer"></div>
"""
@wip
Scenario: Simple Page with Admin Inline Editing
Given a simple page named "hello-world-inline" with the body:
"""
{% block hello %}Hello World{% endblock %}
"""
When And I'm an admin
And I view the rendered page at "/hello-world-inline"
Then the rendered output shoud look like:
"""
<div class="inline-editing" data-url="/admin/parts/XXXX" data-title="hello">Hello World</div>
"""

View File

@ -1,27 +1,13 @@
Before('@site_up') do
create_site_and_admin_account
create_layout_samples
end
Before('@another_site_up') do
add_new_site
end
Before('@authenticated') do
Given %{I am an authenticated user}
end
### Authentication ### Authentication
Given /^I am not authenticated$/ do Given /^I am not authenticated$/ do
visit('/admin/sign_out') visit('/admin/sign_out')
end end
Given /^I am an authenticated user$/ do Given /^I am an authenticated user$/ do
Given %{I go to login} Given %{I go to login}
And %{I fill in "admin_email" with "admin@locomotiveapp.org"} And %{I fill in "Email" with "admin@locomotiveapp.org"}
And %{I fill in "admin_password" with "easyone"} And %{I fill in "Password" with "easyone"}
And %{I press "Log in"} And %{I press "Log in"}
end end
@ -32,16 +18,6 @@ Then /^I am redirected to "([^\"]*)"$/ do |url|
visit location visit location
end end
### Pages
Then /^I should have "(.*)" in the (.*) page (.*)$/ do |content, page_slug, slug|
page = @site.pages.where(:slug => page_slug).first
part = page.parts.where(:slug => slug).first
part.should_not be_nil
part.value.should == content
end
### Cross-domain authentication ### Cross-domain authentication
When /^I forget to press the button on the cross-domain notice page$/ do When /^I forget to press the button on the cross-domain notice page$/ do
@ -51,27 +27,13 @@ end
### Common ### Common
def create_site_and_admin_account
@site = Factory(:site, :name => 'Locomotive test website', :subdomain => 'test')
@admin = Factory(:account, { :name => 'Admin', :email => 'admin@locomotiveapp.org' })
@site.memberships.build :account => @admin, :admin => true
@site.save
end
def add_new_site
@another_site = Factory.build(:site, :name => 'Locomotive test website #2', :subdomain => 'test2')
@another_site.memberships.build :account => @admin, :admin => true
@another_site.save
end
def create_layout_samples def create_layout_samples
Factory(:layout, :site => @site, :name => 'One column', :value => %{<html> Factory(:layout, :site => @site, :name => 'One column', :value => %{<html>
<head> <head>
<title>My website</title> <title>My website</title>
</head> </head>
<body> <body>
<div id="main">\{\{ content_for_layout \}\}</div> <div id="main">{{ content_for_layout }}</div>
</body> </body>
</html>}) </html>})
Factory(:layout, :site => @site)
end end

View File

@ -0,0 +1,69 @@
### Pages
# helps create a simple content page (parent: "index") with a slug, contents, and layout
def create_content_page(page_slug, page_contents, layout = nil)
@home = @site.pages.where(:slug => "index").first || Factory(:page)
page = @site.pages.create(:slug => page_slug, :body => page_contents, :layout => layout, :parent => @home, :title => "some title", :published => true)
page.should be_valid
page
end
# creates a page
Given /^a simple page named "([^"]*)" with the body:$/ do |page_slug, page_contents|
@page = create_content_page(page_slug, page_contents)
end
# creates a page (that has a layout)
Given /^a page named "([^"]*)" with the layout "([^"]*)" and the body:$/ do |page_slug, layout_name, page_contents|
layout = @site.layouts.where(:name => layout_name).first
raise "Could not find layout: #{layout_name}" unless layout
@page = create_content_page(page_slug, page_contents, layout)
end
# creates a layout
Given /^a layout named "([^"]*)" with the source:$/ do |layout_name, layout_body|
@layout = Factory(:layout, :name => layout_name, :value => layout_body, :site => @site)
end
# creates a part within a page
Given /^the page named "([^"]*)" has the part "([^"]*)" with the content:$/ do |page_slug, part_slug, part_contents|
page = @site.pages.where(:slug => page_slug).first
raise "Could not find page: #{page_slug}" unless page
# find or crate page part
part = page.parts.where(:slug => part_slug).first
unless part
part = page.parts.build(:name => part_slug.titleize, :slug => part_slug)
end
# set part value
part.value = part_contents
part.should be_valid
# save page with embedded part
page.save
end
# try to render a page by slug
When /^I view the rendered page at "([^"]*)"$/ do |path|
visit "http://#{@site.domains.first}#{path}"
end
# checks to see if a string is in the slug
Then /^I should have "(.*)" in the (.*) page (.*)$/ do |content, page_slug, part_slug|
page = @site.pages.where(:slug => page_slug).first
raise "Could not find page: #{page_slug}" unless page
part = page.parts.where(:slug => part_slug).first
raise "Could not find part: #{part_slug} within page: #{page_slug}" unless part
part.value.should == content
end
# checks if the rendered body matches a string
Then /^the rendered output should look like:$/ do |body_contents|
page.body.should == body_contents
end

View File

@ -0,0 +1,100 @@
# this file generated by script/generate pickle
# create a model
Given(/^#{capture_model} exists?(?: with #{capture_fields})?$/) do |name, fields|
create_model(name, fields)
end
# create n models
Given(/^(\d+) #{capture_plural_factory} exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
count.to_i.times { create_model(plural_factory.singularize, fields) }
end
# create models from a table
Given(/^the following #{capture_plural_factory} exists?:?$/) do |plural_factory, table|
create_models_from_table(plural_factory, table)
end
# find a model
Then(/^#{capture_model} should exist(?: with #{capture_fields})?$/) do |name, fields|
find_model!(name, fields)
end
# not find a model
Then(/^#{capture_model} should not exist(?: with #{capture_fields})?$/) do |name, fields|
find_model(name, fields).should be_nil
end
# find models with a table
Then(/^the following #{capture_plural_factory} should exists?:?$/) do |plural_factory, table|
find_models_from_table(plural_factory, table).should_not be_any(&:nil?)
end
# find exactly n models
Then(/^(\d+) #{capture_plural_factory} should exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
find_models(plural_factory.singularize, fields).size.should == count.to_i
end
# assert equality of models
Then(/^#{capture_model} should be #{capture_model}$/) do |a, b|
model!(a).should == model!(b)
end
# assert model is in another model's has_many assoc
Then(/^#{capture_model} should be (?:in|one of|amongst) #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
model!(owner).send(association).should include(model!(target))
end
# assert model is not in another model's has_many assoc
Then(/^#{capture_model} should not be (?:in|one of|amongst) #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
model!(owner).send(association).should_not include(model!(target))
end
# assert model is another model's has_one/belongs_to assoc
Then(/^#{capture_model} should be #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
model!(owner).send(association).should == model!(target)
end
# assert model is not another model's has_one/belongs_to assoc
Then(/^#{capture_model} should not be #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
model!(owner).send(association).should_not == model!(target)
end
# assert model.predicate?
Then(/^#{capture_model} should (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
if model!(name).respond_to?("has_#{predicate.gsub(' ', '_')}")
model!(name).should send("have_#{predicate.gsub(' ', '_')}")
else
model!(name).should send("be_#{predicate.gsub(' ', '_')}")
end
end
# assert not model.predicate?
Then(/^#{capture_model} should not (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
if model!(name).respond_to?("has_#{predicate.gsub(' ', '_')}")
model!(name).should_not send("have_#{predicate.gsub(' ', '_')}")
else
model!(name).should_not send("be_#{predicate.gsub(' ', '_')}")
end
end
# model.attribute.should eql(value)
# model.attribute.should_not eql(value)
Then(/^#{capture_model}'s (\w+) (should(?: not)?) be #{capture_value}$/) do |name, attribute, expectation, expected|
actual_value = model(name).send(attribute)
expectation = expectation.gsub(' ', '_')
case expected
when 'nil', 'true', 'false'
actual_value.send(expectation, send("be_#{expected}"))
when /^[+-]?[0-9_]+(\.\d+)?$/
actual_value.send(expectation, eql(expected.to_f))
else
actual_value.to_s.send(expectation, eql(eval(expected)))
end
end
# assert size of association
Then /^#{capture_model} should have (\d+) (\w+)$/ do |name, size, association|
model!(name).send(association).size.should == size.to_i
end

View File

@ -0,0 +1,14 @@
# Creates a Site record
#
# examples:
# - I have the site: "some site" set up
# - I have the site: "some site" set up with name: "Something", domain: "test2"
#
Given /^I have the site: "([^"]*)" set up(?: with #{capture_fields})?$/ do |site_factory, fields|
@site = Factory(site_factory, parse_fields(fields))
@site.should_not be_nil
@admin = @site.memberships.first.account
@admin.should_not be_nil
end

View File

@ -0,0 +1,4 @@
require 'database_cleaner'
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
Before{ DatabaseCleaner.clean }

View File

@ -16,14 +16,16 @@ require 'capybara/rails'
require 'capybara/cucumber' require 'capybara/cucumber'
require 'capybara/session' require 'capybara/session'
require 'capybara/envjs' # envjs doesnt work at the moment
# require 'capybara/envjs'
# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
# order to ease the transition to Capybara we set the default here. If you'd # order to ease the transition to Capybara we set the default here. If you'd
# prefer to use XPath just remove this line and adjust any selectors in your # prefer to use XPath just remove this line and adjust any selectors in your
# steps to use the XPath syntax. # steps to use the XPath syntax.
Capybara.default_selector = :css Capybara.default_selector = :css
Capybara.javascript_driver = :envjs
Capybara.javascript_driver = :selenium
# If you set this to false, any error raised from within your app will bubble # If you set this to false, any error raised from within your app will bubble
# up to your step definition and out to cucumber unless you catch it somewhere # up to your step definition and out to cucumber unless you catch it somewhere
@ -36,21 +38,9 @@ Capybara.javascript_driver = :envjs
# of your scenarios, as this makes it hard to discover errors in your application. # of your scenarios, as this makes it hard to discover errors in your application.
ActionController::Base.allow_rescue = false ActionController::Base.allow_rescue = false
require 'factory_girl'
Locomotive.configure do |config| Locomotive.configure do |config|
config.default_domain = 'example.com' config.default_domain = 'example.com'
end end
Capybara.default_host = 'test.example.com' Capybara.default_host = 'test.example.com'
# Capybara.app_host = 'http://test.example.com'
# How to clean your database when transactions are turned off. See
# http://github.com/bmabey/database_cleaner for more info.
begin
require 'database_cleaner'
require 'database_cleaner/cucumber'
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
rescue LoadError => ignore_if_database_cleaner_not_present
puts "Database Cleaner not Present"
end

View File

@ -0,0 +1,2 @@
require 'factory_girl'

View File

@ -0,0 +1,24 @@
# this file generated by script/generate pickle [paths] [email]
#
# Make sure that you are loading your factory of choice in your cucumber environment
#
# For machinist add: features/support/machinist.rb
#
# require 'machinist/active_record' # or your chosen adaptor
# require File.dirname(__FILE__) + '/../../spec/blueprints' # or wherever your blueprints are
# Before { Sham.reset } # to reset Sham's seed between scenarios so each run has same random sequences
#
# For FactoryGirl add: features/support/factory_girl.rb
#
# require 'factory_girl'
# require File.dirname(__FILE__) + '/../../spec/factories' # or wherever your factories are
#
# You may also need to add gem dependencies on your factory of choice in <tt>config/environments/cucumber.rb</tt>
require 'pickle/world'
# Example of configuring pickle:
#
Pickle.configure do |config|
config.adapters = [:factory_girl]
# config.map 'I', 'myself', 'me', 'my', :to => 'user: "me"'
end

View File

@ -11,7 +11,6 @@ module Locomotive
# see actionpack/lib/action_dispatch/http/url.rb for more information # see actionpack/lib/action_dispatch/http/url.rb for more information
def self.domain_and_subdomain(request) def self.domain_and_subdomain(request)
subdomain = extract_subdomain(request)
[extract_domain(request), extract_subdomain(request)] [extract_domain(request), extract_subdomain(request)]
end end

View File

@ -888,7 +888,7 @@ Gem::Specification.new do |s|
"vendor/plugins/custom_fields/lib/custom_fields.rb", "vendor/plugins/custom_fields/lib/custom_fields.rb",
"vendor/plugins/custom_fields/lib/custom_fields/custom_fields_for.rb", "vendor/plugins/custom_fields/lib/custom_fields/custom_fields_for.rb",
"vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/embeds_many.rb", "vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/embeds_many.rb",
"vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/has_many_related.rb", "vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/references_many.rb",
"vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/proxy.rb", "vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/associations/proxy.rb",
"vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/document.rb", "vendor/plugins/custom_fields/lib/custom_fields/extensions/mongoid/document.rb",
"vendor/plugins/custom_fields/lib/custom_fields/field.rb", "vendor/plugins/custom_fields/lib/custom_fields/field.rb",

View File

@ -5,6 +5,20 @@ Factory.define :site do |s|
s.created_at Time.now s.created_at Time.now
end end
Factory.define "test site", :parent => :site do |s|
s.name 'Locomotive test website'
s.subdomain 'test'
s.after_build do |site_test|
site_test.memberships.build :account => Account.where(:name => "Admin").first || Factory("admin user"), :admin => true
end
end
Factory.define "another site", :parent => "test site" do |s|
s.name "Locomotive test website #2"
s.subdomain "test2"
end
# Accounts ## # Accounts ##
Factory.define :account do |a| Factory.define :account do |a|
a.name 'Bart Simpson' a.name 'Bart Simpson'
@ -14,30 +28,47 @@ Factory.define :account do |a|
a.locale 'en' a.locale 'en'
end end
Factory.define "admin user", :parent => :account do |a|
a.name "Admin"
a.email "admin@locomotiveapp.org"
end
Factory.define "frenchy user", :parent => :account do |a|
a.name "Jean Claude"
a.email "jean@frenchy.fr"
a.locale 'fr'
end
## Memberships ## ## Memberships ##
Factory.define :membership do |m| Factory.define :membership do |m|
m.association :account, :factory => :account
m.admin true m.admin true
m.account{ Account.where(:name => "Bart Simpson").first || Factory(:account) }
end end
## Pages ## ## Pages ##
Factory.define :page do |p| Factory.define :page do |p|
p.association :site, :factory => :site
p.title 'Home page' p.title 'Home page'
p.slug 'index' p.slug 'index'
p.published true
p.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
# Factory.define "unpub"
## Liquid templates ## ## Liquid templates ##
Factory.define :liquid_template do |t| Factory.define :liquid_template do |t|
t.association :site, :factory => :site
t.name 'Simple one' t.name 'Simple one'
t.slug 'simple_one' t.slug 'simple_one'
t.value %{simple liquid template} t.value %{simple liquid template}
t.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
## Layouts ## ## Layouts ##
Factory.define :layout do |l| Factory.define :layout do |l|
l.association :site, :factory => :site
l.name '1 main column + sidebar' l.name '1 main column + sidebar'
l.value %{<html> l.value %{<html>
<head> <head>
@ -48,30 +79,35 @@ Factory.define :layout do |l|
<div id="main">\{\{ content_for_layout | textile \}\}</div> <div id="main">\{\{ content_for_layout | textile \}\}</div>
</body> </body>
</html>} </html>}
l.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
## Snippets ## ## Snippets ##
Factory.define :snippet do |s| Factory.define :snippet do |s|
s.association :site, :factory => :site
s.name 'My website title' s.name 'My website title'
s.slug 'header' s.slug 'header'
s.value %{<title>Acme</title>} s.value %{<title>Acme</title>}
s.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
## Theme assets ## ## Theme assets ##
Factory.define :theme_asset do |a| Factory.define :theme_asset do |a|
a.association :site a.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
## Asset collections ## ## Asset collections ##
Factory.define :asset_collection do |s| Factory.define :asset_collection do |s|
s.association :site, :factory => :site
s.name 'Trip to Chicago' s.name 'Trip to Chicago'
s.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
## Content types ## ## Content types ##
Factory.define :content_type do |t| Factory.define :content_type do |t|
t.association :site, :factory => :site
t.name 'My project' t.name 'My project'
t.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end

View File

@ -334,6 +334,32 @@ describe Page do
end end
describe "creating a new page" do
context "with a body" do
before do
@site = Factory(:site, :subdomain => "somethingweird")
@page = Page.create({
:slug => "some_slug",
:title => "Page Title",
:body => "Page Body",
:published => true,
:site => @site
})
end
it "should be valid" do
@page.should be_valid
end
it "should render the passed in body attribute of the page" do
@page.render(Liquid::Context.new).should == "Page Body"
end
end
end
describe 'templatized extension' do describe 'templatized extension' do
before(:each) do before(:each) do

View File

@ -13,6 +13,14 @@ Rspec.configure do |config|
config.before(:each) do config.before(:each) do
Locomotive.config.heroku = false Locomotive.config.heroku = false
Mongoid.master.collections.select { |c| c.name != 'system.indexes' }.each(&:drop) end
require 'database_cleaner'
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
end
config.before(:each) do
DatabaseCleaner.clean
end end
end end

View File

@ -7,12 +7,12 @@ class Project
field :name field :name
field :description field :description
has_many_related :people references_many :people
embeds_many :tasks embeds_many :tasks
custom_fields_for :people custom_fields_for :people
custom_fields_for :tasks custom_fields_for :tasks
named_scope :ordered, :order_by => [[:name, :asc]] scope :ordered, :order_by => [[:name, :asc]]
end end