generator to create a relationship between 2 content types

did committed Jan 17, 2015
commit a1cc65c35cdc49035e315882182f2a4fa692fbab
Showing 19 changed files with 445 additions and 5 deletions
locomotive/wagon.rb b/lib/locomotive/wagon.rb +1 -0
@@ @@ -143,6 +143,7 @@ module Locomotive
require lib
generator = lib.camelize.constantize.new(args, options, { behavior: :skip })
+ generator.destination_root = args.last
generator.force_color_if_asked(options)
generator.invoke_all
end
locomotive/wagon/cli.rb b/lib/locomotive/wagon/cli.rb +24 -1
@@ @@ -55,7 +55,7 @@ module Locomotive
desc 'content_type SLUG FIELDS', 'Creates a content type with the specified slug and fields.'
method_option :name, aliases: '-n', type: :string, default: nil, optional: true, desc: 'Name of the content type as it will be displayed in the back-office'
long_desc <<-LONGDESC
- Creates a content type with the specified slug and fields.
+ Create a content type with the specified slug and fields.
SLUG should be plural, lowercase, and underscored.
@@ @@ -83,6 +83,29 @@ module Locomotive
end
end
+ desc 'relationship SOURCE TYPE TARGET', 'Relate 2 existing content types.'
+ long_desc <<-LONGDESC
+ Relate 2 existing content types.
+
+ SOURCE AND TARGET are the slugs of the content types.
+ There should be plural, lowercase, and underscored.
+
+ TYPE values: belongs_to, has_many, or many_to_many.
+
+ Examples:
+
+ * wagon generate relationship posts belongs_to categories
+
+ * wagon generate relationship projects many_to_many developers
+ LONGDESC
+ def relationship(source, type, target)
+ force_color_if_asked(options)
+
+ if check_path!
+ Locomotive::Wagon.generate :relationship, [source, type, target, self.options.delete('path')], self.options
+ end
+ end
+
desc 'page FULLPATH', 'Create a page. No need to pass an extension to the FULLPATH arg'
method_option :title, aliases: '-t', type: 'string', default: nil, desc: 'Title of the page'
method_option :haml, aliases: '-h', type: 'boolean', default: nil, desc: 'add a HAML extension to the file'
locomotive/wagon/generators/relationship.rb b/lib/locomotive/wagon/generators/relationship.rb +97 -0
@@ @@ -0,0 +1,97 @@
+ require 'thor/group'
+ require 'thor/error'
+ require 'active_support'
+ require 'active_support/core_ext'
+
+ module Locomotive
+ module Wagon
+ module Generators
+ class Relationship < Thor::Group
+
+ include Thor::Actions
+ include Locomotive::Wagon::CLI::ForceColor
+
+ argument :source # slug of a content type
+ argument :type # belongs_to, has_many or many_to_many
+ argument :target # slug of a content type
+ argument :target_path # path to the site
+
+ def content_types_must_exist
+ unless File.exists?(File.join(destination_root, source_path))
+ fail Thor::Error, "The #{source} content type does not exist"
+ end
+
+ unless File.exists?(File.join(destination_root, target_path))
+ fail Thor::Error, "The #{target} content type does not exist"
+ end
+ end
+
+ def modify_content_types
+ case type.to_sym
+ when :belongs_to
+ append_to_file source_path, build_belongs_to_field(source, target)
+ append_to_file target_path, build_has_many_field(target, source)
+ when :has_many
+ append_to_file source_path, build_has_many_field(source, target)
+ append_to_file target_path, build_belongs_to_field(target, source)
+ when :many_to_many
+ append_to_file source_path, build_many_to_many_field(source, target)
+ append_to_file target_path, build_many_to_many_field(target, source)
+ else
+ fail Thor::Error, "#{type} is an unknown relationship type"
+ end
+ end
+
+ protected
+
+ def source_path
+ "app/content_types/#{source}.yml"
+ end
+
+ def target_path
+ "app/content_types/#{target}.yml"
+ end
+
+ def build_belongs_to_field(source_class, target_class)
+ in_yaml({
+ target_class.singularize => {
+ 'label' => target_class.singularize.humanize,
+ 'type' => 'belongs_to',
+ 'class_name' => target_class
+ }
+ })
+ end
+
+ def build_has_many_field(source_class, target_class)
+ in_yaml({
+ target_class => {
+ 'label' => target_class.humanize,
+ 'type' => 'has_many',
+ 'class_name' => target_class,
+ 'inverse_of' => source_class.singularize,
+ 'ui_enabled' => true
+ }
+ })
+ end
+
+ def build_many_to_many_field(source_class, target_class)
+ in_yaml({
+ target_class => {
+ 'label' => target_class.humanize,
+ 'type' => 'many_to_many',
+ 'class_name' => target_class,
+ 'inverse_of' => source_class,
+ 'ui_enabled' => true
+ }
+ })
+ end
+
+ def in_yaml(hash)
+ [hash].to_yaml.gsub(/^(---\s+)/, "\n")
+ end
+
+ end
+
+ end
+ end
+ end
locomotive/wagon/generators/snippet.rb b/lib/locomotive/wagon/generators/snippet.rb +3 -3
@@ @@ -36,7 +36,7 @@ module Locomotive
_slug = slug.clone.downcase.gsub(/[-]/, '_')
options = { slug: _slug, translated: false }
- file_path = File.join(pages_path, _slug)
+ file_path = File.join(snippets_path, _slug)
template "template.#{extension}.tt", "#{file_path}.#{extension}", options
@@ @@ -52,7 +52,7 @@ module Locomotive
protected
- def pages_path
+ def snippets_path
File.join(target_path, 'app', 'views', 'snippets')
end
@@ @@ -60,4 +60,4 @@ module Locomotive
end
end
- end
\ No newline at end of file
+ end
spec/fixtures/blog/app/content_types/comments.yml +20 -0
@@ @@ -0,0 +1,20 @@
+ name: Comments
+ slug: comments
+ description: Comments
+ label_field_name: name
+ order_by: created_at
+ order_direction: asc
+ public_submission_enabled: true
+
+ fields:
+ - name:
+ label: Name
+ type: string
+ required: true
+ localized: false
+
+ - description:
+ label: Description
+ type: text
+ required: false
+ localized: false
spec/fixtures/blog/app/content_types/posts.yml +19 -0
@@ @@ -0,0 +1,19 @@
+ name: Posts
+ slug: posts
+ description: A description of the content type for the editors
+ label_field_name: title
+ order_by: manually
+
+ fields:
+ - title:
+ label: Title
+ type: string
+ required: true
+ localized: false
+
+ - description:
+ label: Description
+ type: text
+ required: false
+ localized: false
+ text_formatting: html
spec/fixtures/blog/app/views/pages/404.liquid +11 -0
@@ @@ -0,0 +1,11 @@
+ ---
+ title: Page not found
+ published: false
+ ---
+ {% extends index %}
+
+ {% block 'main' %}
+
+ <p>Page not found</p>
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/blog/app/views/pages/about-us.liquid.haml +29 -0
@@ @@ -0,0 +1,29 @@
+ ---
+ title: About us (test)
+
+ # true if the page is included in the menu
+ listed: true
+
+ # true if the page is published
+ published: true
+
+ # position among sibling pages
+ # position: 1
+
+ # sets a redirection to the given url (301)
+ # redirect_url: "<url to a page or to a remote url>"
+
+ # content type that this page is templatizing
+ # content_type: "<slug of one of the content types>"
+
+ # editable_elements:
+ # 'some_block/some_slug': "<text>"
+ # 'some_block/some_slug': "<relative path to the file under the public/samples folder>"
+ ---
+ {% extends parent %}
+
+ {% block main %}
+
+ %p Hello world
+
+ {% endblock %}
\ No newline at end of file
spec/fixtures/blog/app/views/pages/index.liquid +25 -0
@@ @@ -0,0 +1,25 @@
+ ---
+ title: Home page
+ published: true
+ ---
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>{{ site.name }}</title>
+ <meta name="keywords" value="{{ site.meta_keywords }}" />
+ <meta name="description" value="{{ site.meta_description }}" />
+ </head>
+ <body>
+ {% block 'main' %}
+ <h1>{{ page.title }}</h1>
+ <p>keywords = {{ params.q }}</p>
+ <p>post list</p>
+ <ul>
+ {% for post in contents.posts %}
+ <li><a href="/posts/{{ post._permalink }}">{{ post.title }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
+ </body>
+ </html>
\ No newline at end of file
spec/fixtures/blog/app/views/pages/posts.fr.liquid +13 -0
@@ @@ -0,0 +1,13 @@
+ ---
+ title: Liste des articles
+ published: true
+ ---
+ {% extends 'index' %}
+ {% block main %}
+ <h1>Liste des articles</h1>
+ <ul>
+ {% for post in contents.posts %}
+ <li><strong>{{post.title}}</strong> (<a href="/posts/{{ post._permalink }}">more</a>)</li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
\ No newline at end of file
spec/fixtures/blog/app/views/pages/posts.liquid +13 -0
@@ @@ -0,0 +1,13 @@
+ ---
+ title: List of posts
+ published: true
+ ---
+ {% extends 'index' %}
+ {% block main %}
+ <h1>List of posts</h1>
+ <ul>
+ {% for post in contents.posts %}
+ <li><strong>{{post.title}}</strong> (<a href="/posts/{{ post._permalink }}">more</a>)</li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
\ No newline at end of file
spec/fixtures/blog/app/views/pages/posts/content_type_template.fr.liquid +14 -0
@@ @@ -0,0 +1,14 @@
+ ---
+ title: Template d'un post
+ published: false
+ content_type: posts
+ ---
+ <h1>{{ post.title }}</h1>
+ {{ post.description }}
+ <hr />
+ <p>commentaires:</p>
+ <ul>
+ {% for comment in post.comments %}
+ <li><strong>{{comment.name}}</strong><br />{{ comment.description }}</li>
+ {% endfor %}
+ </ul>
\ No newline at end of file
spec/fixtures/blog/app/views/pages/posts/content_type_template.liquid +15 -0
@@ @@ -0,0 +1,15 @@
+ ---
+ title: Post page
+ published: false
+ content_type: posts
+ ---
+
+ <h1>{{ post.title }}</h1>
+ {{ post.description }}
+ <hr />
+ <p>comments:</p>
+ <ul>
+ {% for comment in post.comments %}
+ <li><strong>{{comment.name}}</strong><br />{{ comment.description }}</li>
+ {% endfor %}
+ </ul>
spec/fixtures/blog/config/deploy.yml +4 -0
@@ @@ -0,0 +1,4 @@
+ # development:
+ # host: sample.example.com:8080
+ # email: estelle@locomotivecms.com
+ # password: simplepassword
spec/fixtures/blog/config/site.yml +16 -0
@@ @@ -0,0 +1,16 @@
+ # TODO: explain it
+ name: Blog
+
+ # TODO: explain it
+ # subdomain: sample
+
+ # TODO: explain it
+ # domains: [www.example.com, example.com]
+
+ # TODO: explain it
+ locales: [en, fr]
+
+ # TODO: explain it
+ seo_title: locoblog
+ meta_keywords: "some meta keywords"
+ meta_description: "some meta description"
spec/fixtures/blog/data/comments.yml +16 -0
@@ @@ -0,0 +1,16 @@
+ - "Sample 1":
+ description: "Laboriosam dignissimos quo ut possimus voluptatem. Autem iusto itaque molestiae similique cum vel. Sunt quibusdam ea illo ut in ratione tempore."
+ post: post-1
+
+ - "Sample 2":
+ description: "Rem non fugit aperiam et qui animi. Et aut eum possimus nihil ad. Qui eum ut voluptatem et aut magni aspernatur deleniti. Consequatur id aut id. Hic dolores quidem quas."
+ post: post-1
+
+ - "Sample 3":
+ description: "Voluptatum quisquam assumenda dolor nobis exercitationem est iusto expedita. Molestiae nulla quasi et quibusdam. Iste amet sed corrupti minima numquam quia dolorem ab. Hic itaque doloribus iure totam at praesentium. Illum dolorem incidunt quos laborum."
+ post: post-2
+
+ - "Sample 4":
+ description: "Possimus unde et dolor. Molestias est harum laborum. Nisi est sequi animi et. Qui suscipit ut consequatur odio autem quia est quia."
+ post: post-2
+
spec/fixtures/blog/data/posts.yml +7 -0
@@ @@ -0,0 +1,7 @@
+ - "Post 1":
+ _permalink: post-1
+ description: "Assumenda et quasi illum praesentium facere labore. Dolores quia aliquam vel officiis consequatur repellendus eum et. Repudiandae assumenda ipsa sed. Non perferendis eligendi aut. Magnam aut quos excepturi nihil necessitatibus a et ratione."
+
+ - "Post 2":
+ _permalink: post-2
+ description: "Sit ipsa exercitationem harum ut sint accusantium delectus. Eum reprehenderit minus consequatur sed ad. Nulla ut rem blanditiis tenetur officia reprehenderit assumenda libero. Ut nam sapiente voluptatibus."
spec/integration/generators/relationship_spec.rb +98 -0
@@ @@ -0,0 +1,98 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../integration_helper'
+
+ require 'locomotive/wagon'
+ require 'locomotive/wagon/cli'
+
+ describe 'Locomotive::Wagon::Generators::Relationship' do
+
+ before(:all) { make_working_copy_of_site(:blog) }
+ after(:all) { remove_working_copy_of_site(:blog) }
+
+ let(:path) { working_copy_of_site(:blog) }
+ let(:source_slug) { 'comments' }
+ let(:target_slug) { 'posts' }
+ let(:type) { 'belongs_to' }
+ let(:options) { { 'force_color' => true, 'path' => path, 'quiet' => true } }
+
+ subject { Locomotive::Wagon.generate(:relationship, [source_slug, type, target_slug, options.delete('path')], options) }
+
+ describe 'wrong parameters' do
+
+ describe 'unknown slugs' do
+
+ let(:source_slug) { 'authors' }
+
+ it { lambda { subject }.should raise_error 'The authors content type does not exist' }
+
+ end
+
+ describe 'unknown type' do
+
+ let(:type) { 'has_one' }
+
+ it { lambda { subject }.should raise_error 'has_one is an unknown relationship type' }
+
+ end
+
+ end
+
+ describe 'generating a belongs_to relationship' do
+
+ before { subject }
+
+ it 'adds code to the source content type' do
+ read_content_type(:comments).should include <<-EXPECTED
+ - post:
+ label: Post
+ type: belongs_to
+ class_name: posts
+ EXPECTED
+ end
+
+ it 'adds code the target content type' do
+ read_content_type(:posts).should include <<-EXPECTED
+ - comments:
+ label: Comments
+ type: has_many
+ class_name: comments
+ inverse_of: post
+ EXPECTED
+ end
+
+ end
+
+ describe 'generating a many_to_many relationship' do
+
+ before { subject }
+
+ let(:type) { 'many_to_many' }
+
+ it 'adds code to the source content type' do
+ read_content_type(:comments).should include <<-EXPECTED
+ - posts:
+ label: Posts
+ type: many_to_many
+ class_name: posts
+ inverse_of: comments
+ EXPECTED
+ end
+
+ it 'adds code the target content type' do
+ read_content_type(:posts).should include <<-EXPECTED
+ - comments:
+ label: Comments
+ type: many_to_many
+ class_name: comments
+ inverse_of: posts
+ EXPECTED
+ end
+
+ end
+
+ def read_content_type(name)
+ File.read(File.join(path, 'app/content_types', "#{name}.yml"))
+ end
+
+ end
spec/support/helpers.rb +20 -1
@@ @@ -15,6 +15,25 @@ module Spec
end
end
+ def working_copy_of_site(name)
+ tmp_path = File.expand_path('../../tmp', __FILE__)
+ tmp_path = FileUtils.mkdir_p(tmp_path)
+ File.join(tmp_path, name.to_s)
+ end
+
+ def make_working_copy_of_site(name)
+ source = File.join(File.expand_path('../../fixtures', __FILE__), name.to_s)
+ target = working_copy_of_site(name)
+
+ FileUtils.cp_r(source, target)
+ end
+
+ def remove_working_copy_of_site(name)
+ path = working_copy_of_site(name)
+
+ # FileUtils.rm_rf(path)
+ end
+
def run_server
path = 'spec/fixtures/default'
Locomotive::Wagon::Logger.setup(path, false)
@@ @@ -31,4 +50,4 @@ module Spec
end
end
- end
\ No newline at end of file
+ end