introduce the proxy collection liquid drop so that we can paginate for real + fix the cucumber features for the public part of the engine

This commit is contained in:
Didier Lafforgue 2012-02-17 00:51:33 +01:00
parent 02e48f5c36
commit 085a54d7c1
38 changed files with 149 additions and 102 deletions

View File

@ -33,7 +33,7 @@ group :test do
# gem 'growl-glue'
# gem 'cucumber-rails'
gem 'cucumber-rails'
gem 'rspec-rails', '~> 2.8.0'
gem 'shoulda-matchers'

View File

@ -135,6 +135,10 @@ GEM
gherkin (~> 2.7.1)
json (>= 1.4.6)
term-ansicolor (>= 1.0.6)
cucumber-rails (1.2.1)
capybara (>= 1.1.2)
cucumber (>= 1.1.3)
nokogiri (>= 1.5.0)
database_cleaner (0.7.1)
devise (1.5.3)
bcrypt-ruby (~> 3.0)
@ -317,6 +321,7 @@ DEPENDENCIES
coffee-rails (~> 3.2.2)
compass!
compass-rails!
cucumber-rails
custom_fields!
database_cleaner
factory_girl_rails (~> 1.6.0)

View File

@ -11,8 +11,8 @@ If we have to give a couple of features to describe our application, there will
* nice looking UI (see http://www.locomotivecms.com for some screenshots)
* flexible content types
* content localization out of the box
* playing smoothly with Heroku and MongoHQ
* inline editing (beta)
* playing smoothly with Heroku, Bushido and MongoHQ
* inline editing (wip)
* API
h2. Strategy / Development status
@ -24,8 +24,8 @@ h2. Gems
Here is a short list of main gems / technologies used in the application.
* Rails 3.1 (3.2 soon)
* Mongoid 2.4.2 (with MongoDB 2.0)
* Rails 3.2
* Mongoid 2.4.3 (with MongoDB 2.0)
* Liquid
* Devise
* Carrierwave
@ -40,7 +40,7 @@ See the "official website":http://www.locomotivecms.com
h2. Upgrading
If you wish to upgrade your locomotive install from an older version to the current 1.0.0rc1 then "please refer to the upgrade guide":http://www.locomotivecms.com/support/howto/upgrade
We work on the procedure to upgrade from a previous version of the engine (below the 2.0.0)
h2. Community

View File

@ -5,6 +5,38 @@ rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
# === Locomotive tasks ===
load 'lib/tasks/locomotive.rake'
# === Gems install tasks ===
Bundler::GemHelper.install_tasks
# === Travis
task :travis do
["rspec spec", "cucumber -b"].each do |cmd|
puts "Starting to run #{cmd}..."
system("export DISPLAY=:99.0 && bundle exec #{cmd}")
raise "#{cmd} failed!" unless $?.exitstatus == 0
end
end
# === RSpec ===
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
# === Cucumber ===
load 'lib/tasks/cucumber.rake'
# === Default task ===
task :default => [:spec, :cucumber]
# begin
# require 'rdoc/task'
# rescue LoadError
@ -13,20 +45,6 @@ end
# RDoc::Task = Rake::RDocTask
# end
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
load 'lib/tasks/locomotive.rake'
# FIXME: it is disabled because it loaded twice the Locomotive engine
# load 'rails/tasks/engine.rake'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
Bundler::GemHelper.install_tasks
# # require File.expand_path('../config/application', __FILE__)
#

View File

@ -1,3 +1,4 @@
@wip
@javascript
Feature: Engine
As an author

View File

@ -5,16 +5,17 @@ Given %r{^I have an? "([^"]*)" model which has many "([^"]*)"$} do |parent_model
end
@child_model = FactoryGirl.build(:content_type, :site => @site, :name => child_model).tap do |ct|
ct.entries_custom_fields.build :label => 'Body', :type => 'string', :required => false
ct.entries_custom_fields.build :label => parent_model.singularize, :kind => 'has_one', :required => false, :target => parent_model
ct.entries_custom_fields.build :label => parent_model.singularize.downcase, :type => 'belongs_to', :required => false, :class_name => @parent_model.klass_with_custom_fields(:entries).to_s
ct.save!
end
@parent_model.entries_custom_fields.build({
:label => child_model,
:kind => 'has_many',
:target => @child_model.content_klass.to_s,
:reverse_lookup => @child_model.content_klass.custom_field_alias_to_name(parent_model.downcase.singularize)
:type => 'has_many',
:class_name => @child_model.klass_with_custom_fields(:entries).to_s,
:inverse_of => parent_model.singularize.downcase
})
@parent_model.save
end
Then /^I should be able to view a paginaed list of a has many association$/ do
@ -23,17 +24,14 @@ Then /^I should be able to view a paginaed list of a has many association$/ do
# Create contents
article = @parent_model.entries.create!(:slug => 'parent', :body => 'Parent')
@child_model.entries.create!(:slug => 'one', :body => 'One', :custom_field_2 => article.id.to_s)
@child_model.entries.create!(:slug => 'two', :body => 'Two', :custom_field_2 => article.id.to_s)
@child_model.entries.create!(:slug => 'three', :body => 'Three', :custom_field_2 => article.id.to_s)
@child_model.entries.each do |comment|
article.comments << comment
end
@child_model.entries.create!(:slug => 'one', :body => 'One', :article => article)
@child_model.entries.create!(:slug => 'two', :body => 'Two', :article => article)
@child_model.entries.create!(:slug => 'three', :body => 'Three', :article => article)
# Create a page
raw_template = %{
{% for article in contents.articles %}
{% for article in models.articles %}
{{ article.body }}
{% paginate article.comments by 2 %}
{% for comment in paginate.collection %}
{{ comment.body }}
@ -48,11 +46,11 @@ Then /^I should be able to view a paginaed list of a has many association$/ do
# The page should have the first two comments
visit '/hello'
page.should have_content 'One'
page.should have_content 'Two'
page.should_not have_content 'Three'
# The second page should have the last comment
click_link '2'
page.should_not have_content 'One'

View File

@ -1,6 +1,6 @@
Then /^I should be able to display paginated models$/ do
# Create our article model and three articles
@article_model = FactoryGirl.build(:content_type, :site => @site, :name => 'Articles').tap do |ct|
@article_model = FactoryGirl.build(:content_type, :site => @site, :name => 'Articles', :order_by => '_position').tap do |ct|
ct.entries_custom_fields.build :label => 'Body', :type => 'string', :required => false
ct.save!
end

View File

@ -1,4 +1,5 @@
require 'locomotive/liquid/drops/base'
require 'locomotive/liquid/drops/proxy_collection'
%w{. tags drops filters}.each do |dir|
Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib }

View File

@ -9,6 +9,10 @@ module Locomotive
self._source._id.to_s
end
def _label
@_label ||= self._source._label
end
# Returns the next content for the parent content type.
# If no content is found, nil is returned.
#
@ -40,11 +44,16 @@ module Locomotive
if not @@forbidden_attributes.include?(meth.to_s)
value = self._source.send(meth)
end
end
def _label
@_label ||= self._source._label
if value.respond_to?(:all)
# returns a mongoid criterion in order to chain pagination criteria
value.all
else
value
end
else
nil
end
end
end

View File

@ -5,47 +5,16 @@ module Locomotive
def before_method(meth)
type = @context.registers[:site].content_types.where(:slug => meth.to_s).first
ProxyCollection.new(type)
ContentTypeProxyCollection.new(type)
end
end
class ProxyCollection < ::Liquid::Drop
class ContentTypeProxyCollection < ProxyCollection
def initialize(content_type)
@content_type = content_type
@collection = nil
end
def first
self.collection.first
end
def last
self.collection.last
end
def each(&block)
self.collection.each(&block)
end
def each_with_index(&block)
self.collection.each_with_index(&block)
end
def count
@count ||= self.collection.count
end
alias :size :count
alias :length :count
def empty?
self.collection.empty?
end
def any?
self.collection.any?
@collection = nil
end
def public_submission_url
@ -59,16 +28,11 @@ module Locomotive
klass.send(meth, :ordered_entries)
else
Rails.logger.warn "[Liquid template] trying to call #{meth} on a content_type object"
# klass.send(meth)
end
end
protected
def paginate(options = {})
self.collection.page(options[:page]).per(options[:per_page])
end
def collection
@collection ||= @content_type.ordered_entries(@context['with_scope'])
end

View File

@ -0,0 +1,56 @@
module Locomotive
module Liquid
module Drops
class ProxyCollection < ::Liquid::Drop
def initialize(collection)
@collection = collection
end
def first
self.collection.first
end
def last
self.collection.last
end
def each(&block)
self.collection.each(&block)
end
def each_with_index(&block)
self.collection.each_with_index(&block)
end
def count
@count ||= self.collection.count
end
alias :size :count
alias :length :count
def empty
self.collection.empty?
end
def any
self.collection.any?
end
protected
def paginate(options = {})
@collection = collection.page(options[:page]).per(options[:per_page])
end
def collection
@collection
end
end
end
end
end

View File

@ -3,6 +3,7 @@
require 'mongoid'
module Mongoid#:nodoc:
module Document #:nodoc:
def as_json(options = {})
attrs = super(options)
@ -21,6 +22,12 @@ module Mongoid#:nodoc:
class RawArray < ::Array; end
end
class Criteria
def to_liquid
Locomotive::Liquid::Drops::ProxyCollection.new(self)
end
end
# without callback feature
module Callbacks #:nodoc:
module ClassMethods #:nodoc:
@ -34,7 +41,6 @@ module Mongoid#:nodoc:
# make the validators work with localized field
module Validations #:nodoc:
def read_attribute_for_validation_with_localization(attr)
if fields[attr.to_s] && fields[attr.to_s].localized?
send(attr.to_sym)
@ -46,30 +52,10 @@ module Mongoid#:nodoc:
alias_method_chain :read_attribute_for_validation, :localization
class PresenceValidator < ActiveModel::EachValidator
def validate_each(document, attribute, value)
document.errors.add(attribute, :blank, options) if value.blank?
end
end
end
#
# class UniquenessValidator < ActiveModel::EachValidator
#
# protected
#
# def criterion_with_localization(document, attribute, value)
# field = document.fields[attribute.to_s]
# if field && field.localized? && !value.blank?
# value = document.send(attribute.to_sym)
# end
#
# criterion_without_localization(document, attribute, value)
# end
#
# alias_method_chain :criterion, :localization
#
# end
#
# end
end

View File

@ -17,6 +17,10 @@ module Locomotive
output = @page.render(locomotive_context)
# puts "======================"
# puts output.inspect
# puts "======================"
self.prepare_and_set_response(output)
end
end

View File

@ -4,6 +4,7 @@
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
require 'rails'
unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
@ -14,19 +15,19 @@ begin
require 'cucumber/rake/task'
namespace :cucumber do
Cucumber::Rake::Task.new(:ok, 'Run features that should pass') do |t|
Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
t.fork = true # You may get faster startup if you set this to false
t.profile = 'default'
end
Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'wip'
end
Cucumber::Rake::Task.new(:rerun, 'Record failing features and run only them if any exist') do |t|
Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
t.binary = vendored_cucumber_bin
t.fork = true # You may get faster startup if you set this to false
t.profile = 'rerun'
@ -50,6 +51,10 @@ begin
STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
end
# In case we don't have ActiveRecord, append a no-op task that we can depend upon.
task 'db:test:prepare' do
end
task :stats => 'cucumber:statsetup'
rescue LoadError
desc 'cucumber rake task not available (cucumber not installed)'