rename translation files (convention) + email notification when adding contents (api)

This commit is contained in:
dinedine 2011-01-18 15:24:42 +01:00
parent 6eead019d4
commit 2fc99bf90a
24 changed files with 352 additions and 223 deletions

View File

@ -0,0 +1,15 @@
module Admin
class Notifications < ActionMailer::Base
default :from => Locomotive.config.mailer_sender
def new_content_instance(account, content)
@account, @content = account, content
subject = t('admin.notifications.new_content_instance.subject', :type => content.content_type.name, :locale => account.locale)
mail :subject => subject, :to => account.email
end
end
end

View File

@ -21,6 +21,7 @@ class ContentInstance
before_save :set_slug before_save :set_slug
before_save :set_visibility before_save :set_visibility
before_create :add_to_list_bottom before_create :add_to_list_bottom
after_create :send_notifications
## named scopes ## ## named scopes ##
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
@ -92,4 +93,18 @@ class ContentInstance
self.content_type.highlighted_field._alias.to_sym self.content_type.highlighted_field._alias.to_sym
end end
def send_notifications
return unless self.content_type.api_enabled?
accounts = self.content_type.site.accounts.to_a
self.content_type.api_accounts.each do |account_id|
next if account_id.blank?
account = accounts.detect { |a| a.id.to_s == account_id.to_s }
Admin::Notifications.new_content_instance(account, self).deliver
end
end
end end

View File

@ -10,6 +10,7 @@ class ContentType
field :highlighted_field_name field :highlighted_field_name
field :group_by_field_name field :group_by_field_name
field :api_enabled, :type => Boolean, :default => false field :api_enabled, :type => Boolean, :default => false
field :api_accounts, :type => Array
## associations ## ## associations ##
referenced_in :site referenced_in :site

View File

@ -1,5 +1,5 @@
- content_for :head do - content_for :head do
= include_javascripts :custom_fields = include_javascripts :content_types, :custom_fields
= include_stylesheets :fancybox = include_stylesheets :fancybox
= f.inputs :name => :information do = f.inputs :name => :information do
@ -18,5 +18,9 @@
= f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false = f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false
= f.custom_input :api_enabled, :css => 'toggle' do = f.custom_input :api_enabled, :css => 'toggle' do
= f.check_box :api_enabled = f.check_box :api_enabled
= hidden_field_tag 'content_type[api_accounts][]', ''
= f.input :api_accounts, :as => :select, :collection => current_site.accounts.collect { |a| ["#{a.name} <#{a.email}>", a.id] }, :include_blank => false, :multiple => true, :wrapper_html => { :class => 'multiple', :style => (f.object.api_enabled? ? '' : 'display: none') }

View File

@ -1,8 +1,5 @@
- title t('.title') - title t('.title')
- content_for :head do
= include_javascripts :content_types
- content_for :submenu do - content_for :submenu do
= render 'admin/shared/menu/contents' = render 'admin/shared/menu/contents'

View File

@ -0,0 +1,17 @@
%p= t('.title', :name => @account.name, :date => I18n.l(Time.now), :locale => @account.locale)
%hr
%p
%b= t('.type', :type => @content.content_type.name, :locale => @account.locale)
%br
%i= @content.content_type.description
%hr
%ul
- @content.custom_fields.each do |field|
%li
%strong= field.label
&nbsp;-&nbsp;
%i= @content.send(field._name)

View File

@ -1,4 +1,4 @@
require 'devise' require 'locomotive'
## patches ## ## patches ##
@ -31,7 +31,7 @@ end
# four configuration values can also be set straight in your models. # four configuration values can also be set straight in your models.
Devise.setup do |config| Devise.setup do |config|
# Configure the e-mail address which will be shown in DeviseMailer. # Configure the e-mail address which will be shown in DeviseMailer.
config.mailer_sender = "support@locomotiveapp.org" config.mailer_sender = Locomotive.config.mailer_sender
# ==> Configuration for any authentication mechanism # ==> Configuration for any authentication mechanism
# Configure which keys are used when authenticating an user. By default is # Configure which keys are used when authenticating an user. By default is

View File

@ -39,4 +39,7 @@ Locomotive.configure do |config|
# default locale (for now, only en and fr are supported) # default locale (for now, only en and fr are supported)
config.default_locale = :en config.default_locale = :en
# Configure the e-mail address which will be shown in the DeviseMailer, NotificationMailer, ...etc
config.mailer_sender = 'support@example.com'
end end

View File

@ -42,6 +42,12 @@ en:
notice: "The page you requested does not exist." notice: "The page you requested does not exist."
link: "&rarr; Back to the application" link: "&rarr; Back to the application"
notifications:
new_content_instance:
subject: "[%{type}] new"
title: "Hi %{name}, just to let you know that a new instance has been created on %{date}"
type: "Model: %{type}"
custom_fields: custom_fields:
edit: edit:
title: Editing custom field title: Editing custom field
@ -292,64 +298,3 @@ en:
title: "Step 3/3 &mdash; Create first site" title: "Step 3/3 &mdash; Create first site"
explanations: "This is the last step of the installation. You can upload a theme as a zip file. We have free available themes <a href=\"http://www.locomotivecms.com/support/themes\">here</a>." explanations: "This is the last step of the installation. You can upload a theme as a zip file. We have free available themes <a href=\"http://www.locomotivecms.com/support/themes\">here</a>."
next: Create site next: Create site
formtastic:
titles:
information: General information
meta: SEO Metadata
code: Code
raw_template: Template
credentials: Credentials
language: Language
sites: Sites
access_points: Access points
memberships: Accounts
membership_email: Account email
file: File
preview: Preview
options: Advanced options
custom_fields: Custom fields
other_fields: Other information
presentation: Presentation
attributes: Attributes
upload: Upload
labels:
theme_asset:
new:
source: File
edit:
source: Replace file
custom_fields:
field:
_alias: Alias
import:
new:
source: File
samples: Copy samples
reset: Reset site
hints:
page:
published: "Only authenticated accounts can view unpublished pages."
cache_strategy: "Cache the page for better performance. The 'Simple' choice is a good compromise."
templatized: "Use the page as a template for a model you defined."
snippet:
slug: "You need to know it in order to insert the snippet inside a page"
site:
meta_keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO."
meta_description: "Meta description used within the head tag of the page. Required for SEO."
domain_name: "ex: locomotiveapp.org"
theme_asset:
slug: "You do not need to add the extension file (.css or .js)"
edit:
source: "You can replace it by a file of the same extension"
custom_fields:
field:
_alias: "Property available in liquid templates"
hint: "Text displayed in the model form just below the field"
import:
source: "A zipfile containing a database.yml along with assets and templates"
samples: "If enabled, the import process will also copy contents and assets"
reset: "If enabled, all the data of your site will be destroyed before importing the new site"

View File

@ -42,6 +42,12 @@ fr:
update: Mettre à jour update: Mettre à jour
send: Envoyer send: Envoyer
notifications:
new_content_instance:
subject: "[%{type}] nouveau"
title: "Bonjour %{name}, nous voulions vous faire savoir qu'une nouvelle instance a été créée le %{date}"
type: "Modèle: %{type}"
custom_fields: custom_fields:
edit: edit:
title: Editer champ personnalisé title: Editer champ personnalisé
@ -293,62 +299,3 @@ fr:
title: "Étape 3/3 &mdash; Créer premier site" title: "Étape 3/3 &mdash; Créer premier site"
explanations: "C'est la dernière étape de l'installation. Vous pouvez uploader un theme sous forme d'un fichier zip. Nous avons quelques themes disponibles <a href=\"http://www.locomotivecms.com/support/themes\">ici</a>." explanations: "C'est la dernière étape de l'installation. Vous pouvez uploader un theme sous forme d'un fichier zip. Nous avons quelques themes disponibles <a href=\"http://www.locomotivecms.com/support/themes\">ici</a>."
next: Créer site next: Créer site
formtastic:
titles:
information: Informations générales
meta: SEO Metadata
code: Code
raw_template: Gabarit
credentials: Informations de connexion
language: Langue
sites: Sites
access_points: Points d'accès
memberships: Comptes
membership_email: Email compte
file: Fichier
preview: Aperçu
options: Options avancées
custom_fields: Champs personnalisés
other_fields: Autres informations
presentation: Présentation
attributes: Propriétés
upload: Envoi au serveur
labels:
theme_asset:
new:
source: Fichier
edit:
source: Nouveau fichier
custom_fields:
field:
_alias: Alias
import:
new:
source: Fichier
samples: Copier contenu
reset: Remettre à zéro
hints:
page:
published: "Seuls les administrateurs authentifiés peuvent voir une page non publiée."
cache_strategy: "Cache la page pour de meilleure performance. L'option 'Simple' est le meilleur compromis."
templatized: "Utilise la page comme un template pour un modèle défini."
snippet:
slug: "Utilisé pour insérer le snippet dans une page."
site:
meta_keywords: "Mots-clés utilisés à l'intérieur de la balise HEAD. Ils sont séparés par un espace. Requis pour un meilleur référencement."
meta_description: "Description utilisée à l'intérieur de la balise HEAD. Requis pour un meilleur référencement."
domain_name: "ex: locomotiveapp.org"
theme_asset:
slug: "Vous n'avez pas besoin de mettre l'extension du fichier (.css ou .js)"
edit:
source: "Vous pouvez le remplacer par un fichier avec la meme extension."
custom_fields:
field:
_alias: "Champ utilisable dans les templates liquid"
hint: "Texte affiché dans le formulaire de l'élément juste en dessous du champ."
import:
source: "Un fichier zip contenant database.yml, les fichiers du thème et les templates de page"
samples: "Si activé, les contenus et les média seront aussi copiés lors de l'import"
reset: "Si activé, toutes les données de votre site seront détruites avant l'import du nouveau site"

View File

@ -0,0 +1,65 @@
en:
formtastic:
titles:
information: General information
meta: SEO Metadata
code: Code
raw_template: Template
credentials: Credentials
language: Language
sites: Sites
access_points: Access points
memberships: Accounts
membership_email: Account email
file: File
preview: Preview
options: Advanced options
custom_fields: Custom fields
other_fields: Other information
presentation: Presentation
attributes: Attributes
upload: Upload
labels:
theme_asset:
new:
source: File
edit:
source: Replace file
custom_fields:
field:
_alias: Alias
import:
new:
source: File
samples: Copy samples
reset: Reset site
content_type:
api_accounts: Notified Accounts
hints:
page:
published: "Only authenticated accounts can view unpublished pages."
cache_strategy: "Cache the page for better performance. The 'Simple' choice is a good compromise."
templatized: "Use the page as a template for a model you defined."
snippet:
slug: "You need to know it in order to insert the snippet inside a page"
site:
meta_keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO."
meta_description: "Meta description used within the head tag of the page. Required for SEO."
domain_name: "ex: locomotiveapp.org"
theme_asset:
slug: "You do not need to add the extension file (.css or .js)"
edit:
source: "You can replace it by a file of the same extension"
custom_fields:
field:
_alias: "Property available in liquid templates"
hint: "Text displayed in the model form just below the field"
import:
source: "A zipfile containing a database.yml along with assets and templates"
samples: "If enabled, the import process will also copy contents and assets"
reset: "If enabled, all the data of your site will be destroyed before importing the new site"
content_type:
api_enabled: "It is used to let people from outside to create new instances (example: messages in a contact form)"
api_accounts: "A notification email will be sent to each of the accounts listed above when a new instance is created"

View File

@ -0,0 +1,64 @@
fr:
formtastic:
titles:
information: Informations générales
meta: SEO Metadata
code: Code
raw_template: Gabarit
credentials: Informations de connexion
language: Langue
sites: Sites
access_points: Points d'accès
memberships: Comptes
membership_email: Email compte
file: Fichier
preview: Aperçu
options: Options avancées
custom_fields: Champs personnalisés
other_fields: Autres informations
presentation: Présentation
attributes: Propriétés
upload: Envoi au serveur
labels:
theme_asset:
new:
source: Fichier
edit:
source: Nouveau fichier
custom_fields:
field:
_alias: Alias
import:
new:
source: Fichier
samples: Copier contenu
reset: Remettre à zéro
content_type:
api_accounts: Comptes à notifier
hints:
page:
published: "Seuls les administrateurs authentifiés peuvent voir une page non publiée."
cache_strategy: "Cache la page pour de meilleure performance. L'option 'Simple' est le meilleur compromis."
templatized: "Utilise la page comme un template pour un modèle défini."
snippet:
slug: "Utilisé pour insérer le snippet dans une page."
site:
meta_keywords: "Mots-clés utilisés à l'intérieur de la balise HEAD. Ils sont séparés par un espace. Requis pour un meilleur référencement."
meta_description: "Description utilisée à l'intérieur de la balise HEAD. Requis pour un meilleur référencement."
domain_name: "ex: locomotiveapp.org"
theme_asset:
slug: "Vous n'avez pas besoin de mettre l'extension du fichier (.css ou .js)"
edit:
source: "Vous pouvez le remplacer par un fichier avec la meme extension."
custom_fields:
field:
_alias: "Champ utilisable dans les templates liquid"
hint: "Texte affiché dans le formulaire de l'élément juste en dessous du champ."
import:
source: "Un fichier zip contenant database.yml, les fichiers du thème et les templates de page"
samples: "Si activé, les contenus et les média seront aussi copiés lors de l'import"
reset: "Si activé, toutes les données de votre site seront détruites avant l'import du nouveau site"
content_type:
api_enabled: "Utilisé pour autoriser la création de nouvelles instances de l'extérieur (ex.: les messages dans un formulaire de contact)"
api_accounts: "Un email de notification sera envoyé à chaque compte listé ci-dessus lors de la création d'une nouvelle instance"

View File

@ -1,5 +1,10 @@
BOARD: BOARD:
x notify accounts when new instance of models (opt): none, one or many accounts. Used for contact form.
x implementation
x emails
x tests
- fix bug issue about (custom fields)
BACKLOG: BACKLOG:
@ -10,7 +15,6 @@ BACKLOG:
- refactor slugify method (use parameterize + create a module) - refactor slugify method (use parameterize + create a module)
- validation for custom fields - validation for custom fields
- notify accounts when new instance of models (opt): none, one or many accounts. Used for contact form.
- global regions: keyword in editable element (http://www.mongodb.org/display/DOCS/Updating) - global regions: keyword in editable element (http://www.mongodb.org/display/DOCS/Updating)
- write my first tutorial about locomotive - write my first tutorial about locomotive

View File

@ -37,4 +37,7 @@ Locomotive.configure do |config|
# default locale (for now, only en and fr are supported) # default locale (for now, only en and fr are supported)
config.default_locale = :en config.default_locale = :en
# Configure the e-mail address which will be shown in the DeviseMailer, NotificationMailer, ...etc
config.mailer_sender = 'support@example.com'
end end

View File

@ -12,7 +12,8 @@ module Locomotive
:enable_logs => false, :enable_logs => false,
:heroku => false, :heroku => false,
:delayed_job => true, :delayed_job => true,
:default_locale => :en :default_locale => :en,
:mailer_sender => 'support@example.com'
} }
cattr_accessor :settings cattr_accessor :settings

View File

@ -64,3 +64,5 @@ class Hash
end end
end end

View File

@ -13,4 +13,20 @@ $(document).ready(function() {
}); });
$('#content_type_slug').keypress(function() { $(this).addClass('filled'); }); $('#content_type_slug').keypress(function() { $(this).addClass('filled'); });
// api enabled ?
console.log('subscribing...');
$.subscribe('toggle.content_type_api_enabled.checked', function(event, data) {
console.log('checked');
$('#content_type_api_accounts_input').show();
}, []);
$.subscribe('toggle.content_type_api_enabled.unchecked', function(event, data) {
console.log('unchecked');
$('#content_type_api_accounts_input').hide();
}, []);
}); });

View File

@ -10,103 +10,103 @@
*/ */
(function($) { (function($) {
/** /**
* Version 1.0 * Version 1.0
* Replaces checkboxes with a toggle switch. * Replaces checkboxes with a toggle switch.
* usage: $("input[type='checkbox']").checkToggle(settings); * usage: $("input[type='checkbox']").checkToggle(settings);
* *
* @name checkToggle * @name checkToggle
* @type jquery * @type jquery
* @param Hash settings Settings * @param Hash settings Settings
* @param String settings[on_label] Text used for the left-side (on) label. Defaults to "On" * @param String settings[on_label] Text used for the left-side (on) label. Defaults to "On"
* @param String settings[off_label] Text used for the right-side (off) label. Defaults to "Off" * @param String settings[off_label] Text used for the right-side (off) label. Defaults to "Off"
* @param String settings[on_bg_color] Hex background color for On state * @param String settings[on_bg_color] Hex background color for On state
* @param String settings[off_bg_color] Hex background color for Off state * @param String settings[off_bg_color] Hex background color for Off state
* @param String settings[skin_dir] Document relative (or absolute) path to the skin directory * @param String settings[skin_dir] Document relative (or absolute) path to the skin directory
* @param Bool settings[bypass_skin] Flags whether to bypass the inclusion of the skin.css file. Used if you've included the skin styles somewhere else already. * @param Bool settings[bypass_skin] Flags whether to bypass the inclusion of the skin.css file. Used if you've included the skin styles somewhere else already.
*/ */
$.fn.checkToggle = function(settings) { $.fn.checkToggle = function(settings) {
settings = $.extend({ settings = $.extend({
on_label : 'Yes', on_label : 'Yes',
on_bg_color : '#8FE38D', on_bg_color : '#8FE38D',
off_label : 'No', off_label : 'No',
off_bg_color: '#F8837C', off_bg_color: '#F8837C',
skin_dir : "skin/", skin_dir : "skin/",
bypass_skin : false bypass_skin : false
}, settings); }, settings);
// FIXME (Didier Lafforgue) it works but it doesn't scale if we handle another locale // FIXME (Didier Lafforgue) it works but it doesn't scale if we handle another locale
if (typeof I18nLocale != 'undefined' && I18nLocale == 'fr') { if (typeof I18nLocale != 'undefined' && I18nLocale == 'fr') {
settings.on_label = 'Oui'; settings.on_label = 'Oui';
settings.off_label = 'Non'; settings.off_label = 'Non';
} }
// append the skin styles // append the skin styles
// if(settings.bypass_skin == false){ // if(settings.bypass_skin == false){
// $("head").append('<link type="text/css" rel="stylesheet" href="'+settings.skin_dir+'skin.css" media="screen" />'); // $("head").append('<link type="text/css" rel="stylesheet" href="'+settings.skin_dir+'skin.css" media="screen" />');
// } // }
function toggle(element){ function toggle(element){
var checked = $(element).parent().parent().prev().attr("checked"); var checked = $(element).parent().parent().prev().attr("checked");
// if it's set to on // if it's set to on
if(checked){ if(checked){
$(element).animate({marginLeft: '34px'}, 100, $(element).animate({marginLeft: '34px'}, 100,
// callback function // callback function
function(){ function(){
$(element).parent().prev().css("color","#cccccc"); $(element).parent().prev().css("color","#cccccc");
$(element).parent().next().css("color","#333333"); $(element).parent().next().css("color","#333333");
$(element).parent().css("background-color", settings.off_bg_color); $(element).parent().css("background-color", settings.off_bg_color);
$(element).parent().parent().prev().removeAttr("checked"); $(element).parent().parent().prev().removeAttr("checked");
$(element).removeClass("left").addClass("right"); $(element).removeClass("left").addClass("right");
$.publish('toggle.' + $(element).parent().parent().prev().attr('id') + '.unchecked', []); $.publish('toggle.' + $(element).parent().parent().prev().attr('id') + '.unchecked', []);
}); });
}else{ }else{
$(element).animate({marginLeft: '0em'}, 100, $(element).animate({marginLeft: '0em'}, 100,
// callback function // callback function
function(){ function(){
$(element).parent().prev().css("color","#333333"); $(element).parent().prev().css("color","#333333");
$(element).parent().next().css("color","#cccccc"); $(element).parent().next().css("color","#cccccc");
$(element).parent().css("background-color", settings.on_bg_color); $(element).parent().css("background-color", settings.on_bg_color);
$(element).parent().parent().prev().attr("checked","checked"); $(element).parent().parent().prev().attr("checked","checked");
$(element).removeClass("right").addClass("left"); $(element).removeClass("right").addClass("left");
$.publish('toggle.' + $(element).parent().parent().prev().attr('id') + '.checked', []); $.publish('toggle.' + $(element).parent().parent().prev().attr('id') + '.checked', []);
}); });
} }
}; };
return this.each(function () { return this.each(function () {
if ($(this).hasClass('simple')) return; if ($(this).hasClass('simple')) return;
// hide the checkbox // hide the checkbox
$(this).css('display','none'); $(this).css('display','none');
// insert the new toggle markup // insert the new toggle markup
if($(this).attr("checked") == true){ if($(this).attr("checked") == true){
$(this).after('<div class="toggleSwitch"><span class="leftLabel">'+settings.on_label+'<\/span><div class="switchArea" style="background-color: '+settings.on_bg_color+'"><span class="switchHandle left" style="margin-left: 0em;"><\/span><\/div><span class="rightLabel" style="color:#cccccc">'+settings.off_label+'<\/span><\/div>'); $(this).after('<div class="toggleSwitch"><span class="leftLabel">'+settings.on_label+'<\/span><div class="switchArea" style="background-color: '+settings.on_bg_color+'"><span class="switchHandle left" style="margin-left: 0em;"><\/span><\/div><span class="rightLabel" style="color:#cccccc">'+settings.off_label+'<\/span><\/div>');
}else{ }else{
$(this).after('<div class="toggleSwitch"><span class="leftLabel" style="color:#cccccc;">'+settings.on_label+'<\/span><div class="switchArea" style="background-color: '+settings.off_bg_color+'"><span class="switchHandle right" style="margin-left:34px"><\/span><\/div><span class="rightLabel">'+settings.off_label+'<\/span><\/div>'); $(this).after('<div class="toggleSwitch"><span class="leftLabel" style="color:#cccccc;">'+settings.on_label+'<\/span><div class="switchArea" style="background-color: '+settings.off_bg_color+'"><span class="switchHandle right" style="margin-left:34px"><\/span><\/div><span class="rightLabel">'+settings.off_label+'<\/span><\/div>');
} }
// Bind the switchHandle click events to the internal toggle function // Bind the switchHandle click events to the internal toggle function
$(this).next().find('div.switchArea').bind("click", function () { $(this).next().find('div.switchArea').bind("click", function () {
toggle($(this).find('.switchHandle')); }) toggle($(this).find('.switchHandle')); })
}); });
}; };
})(jQuery); })(jQuery);

View File

@ -114,6 +114,8 @@ form.formtastic fieldset ol li code.nude textarea {
form.formtastic fieldset ol li select { font-size: 0.9em; position: relative; top: 2px; color: #787a89; } form.formtastic fieldset ol li select { font-size: 0.9em; position: relative; top: 2px; color: #787a89; }
form.formtastic fieldset ol li.multiple select { width: 45%; }
form.formtastic fieldset ol li.error input, form.formtastic fieldset ol li.error input,
form.formtastic fieldset ol li.error textarea, form.formtastic fieldset ol li.error textarea,
form.formtastic fieldset ol li.error code iframe { border: 2px solid #ec3f48 !important; } form.formtastic fieldset ol li.error code iframe { border: 2px solid #ec3f48 !important; }

View File

@ -60,6 +60,34 @@ describe ContentInstance do
end end
describe '#api' do
before(:each) do
@account_1 = Factory.build('admin user', :id => '1')
@account_2 = Factory.build('frenchy user', :id => '2')
@content_type.api_enabled = true
@content_type.api_accounts = ['', @account_1.id, @account_2.id]
Site.any_instance.stubs(:accounts).returns([@account_1, @account_2])
@content = build_content
end
it 'does not send email notifications if the api is disabled' do
@content_type.api_enabled = false
Admin::Notifications.expects(:new_content_instance).never
@content.save
end
it 'sends email notifications when a new instance is created' do
Admin::Notifications.expects(:new_content_instance).with(@account_1, @content).returns(mock('mailer', :deliver => true))
Admin::Notifications.expects(:new_content_instance).with(@account_2, @content).returns(mock('mailer', :deliver => true))
@content.save
end
end
def build_content(options = {}) def build_content(options = {})
@content_type.contents.build({ :title => 'Locomotive', :description => 'Lorem ipsum....' }.merge(options)) @content_type.contents.build({ :title => 'Locomotive', :description => 'Lorem ipsum....' }.merge(options))
end end