newsletter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +20 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +26 -0
  6. data/Gemfile.lock +199 -0
  7. data/Guardfile +24 -0
  8. data/LICENSE +22 -0
  9. data/MIT-LICENSE +20 -0
  10. data/Manifest.txt +220 -0
  11. data/Procfile +2 -0
  12. data/README +243 -0
  13. data/README.rdoc +3 -0
  14. data/Rakefile +29 -0
  15. data/app/assets/images/newsletter/.gitkeep +0 -0
  16. data/app/assets/javascripts/newsletter/application.js +15 -0
  17. data/app/assets/stylesheets/newsletter/application.css +13 -0
  18. data/app/controllers/newsletter/application_controller.rb +27 -0
  19. data/app/controllers/newsletter/areas_controller.rb +63 -0
  20. data/app/controllers/newsletter/designs_controller.rb +51 -0
  21. data/app/controllers/newsletter/elements_controller.rb +75 -0
  22. data/app/controllers/newsletter/fields_controller.rb +49 -0
  23. data/app/controllers/newsletter/newsletters_controller.rb +104 -0
  24. data/app/controllers/newsletter/pieces_controller.rb +72 -0
  25. data/app/helpers/newsletter/application_helper.rb +4 -0
  26. data/app/helpers/newsletter/areas_helper.rb +4 -0
  27. data/app/helpers/newsletter/assets_helper.rb +4 -0
  28. data/app/helpers/newsletter/designs_helper.rb +11 -0
  29. data/app/helpers/newsletter/elements_helper.rb +4 -0
  30. data/app/helpers/newsletter/field_values_helper.rb +4 -0
  31. data/app/helpers/newsletter/fields_helper.rb +4 -0
  32. data/app/helpers/newsletter/newsletters_helper.rb +43 -0
  33. data/app/helpers/newsletter/pieces_helper.rb +4 -0
  34. data/app/models/newsletter/area.rb +50 -0
  35. data/app/models/newsletter/asset.rb +31 -0
  36. data/app/models/newsletter/design.rb +160 -0
  37. data/app/models/newsletter/element.rb +169 -0
  38. data/app/models/newsletter/field/inline_asset.rb +67 -0
  39. data/app/models/newsletter/field/text.rb +15 -0
  40. data/app/models/newsletter/field/text_area.rb +15 -0
  41. data/app/models/newsletter/field.rb +115 -0
  42. data/app/models/newsletter/field_value.rb +20 -0
  43. data/app/models/newsletter/newsletter.rb +125 -0
  44. data/app/models/newsletter/piece.rb +96 -0
  45. data/app/models/newsletter.rb +11 -0
  46. data/app/uploaders/newsletter/asset_uploader.rb +51 -0
  47. data/app/views/layouts/newsletter/application.html.erb +135 -0
  48. data/app/views/newsletter/areas/_area.html.erb +49 -0
  49. data/app/views/newsletter/designs/_area_fields.html.erb +4 -0
  50. data/app/views/newsletter/designs/_form.html.erb +41 -0
  51. data/app/views/newsletter/designs/_newsletter_area.html.erb +17 -0
  52. data/app/views/newsletter/designs/edit.html.erb +14 -0
  53. data/app/views/newsletter/designs/index.html.erb +31 -0
  54. data/app/views/newsletter/designs/new.html.erb +9 -0
  55. data/app/views/newsletter/designs/show.html.erb +22 -0
  56. data/app/views/newsletter/elements/_field_fields.html.erb +27 -0
  57. data/app/views/newsletter/elements/_form.html.erb +51 -0
  58. data/app/views/newsletter/elements/_newsletter_field.html.erb +37 -0
  59. data/app/views/newsletter/elements/edit.html.erb +12 -0
  60. data/app/views/newsletter/elements/index.html.erb +32 -0
  61. data/app/views/newsletter/elements/new.html.erb +9 -0
  62. data/app/views/newsletter/elements/show.html.erb +22 -0
  63. data/app/views/newsletter/fields/_form.html.erb +15 -0
  64. data/app/views/newsletter/fields/_inline_asset.html.erb +36 -0
  65. data/app/views/newsletter/fields/_text.html.erb +4 -0
  66. data/app/views/newsletter/fields/_text_area.html.erb +7 -0
  67. data/app/views/newsletter/newsletters/_form.html.erb +21 -0
  68. data/app/views/newsletter/newsletters/_head.html.erb +48 -0
  69. data/app/views/newsletter/newsletters/_newsletter.html.erb +6 -0
  70. data/app/views/newsletter/newsletters/_newsletter_area.html.erb +30 -0
  71. data/app/views/newsletter/newsletters/archive.html.erb +9 -0
  72. data/app/views/newsletter/newsletters/edit.html.erb +37 -0
  73. data/app/views/newsletter/newsletters/index.html.erb +29 -0
  74. data/app/views/newsletter/newsletters/new.html.erb +9 -0
  75. data/app/views/newsletter/newsletters/show.html.erb +6 -0
  76. data/app/views/newsletter/pieces/_form.html.erb +19 -0
  77. data/app/views/newsletter/pieces/edit.html.erb +11 -0
  78. data/app/views/newsletter/pieces/new.html.erb +10 -0
  79. data/app/views/newsletter/pieces/show.html.erb +2 -0
  80. data/assets/docs/index.html +45 -0
  81. data/assets/docs/newsletter_templates/index.html +45 -0
  82. data/config/routes.rb +33 -0
  83. data/db/migrate/001_newsletter_initial.rb +104 -0
  84. data/db/migrate/002_carrier_wave_for_assets.rb +9 -0
  85. data/db/pre-scoped/002_newsletter_scoped.rb +61 -0
  86. data/doc/README_FOR_APP +2 -0
  87. data/doc/newsletter.wiki.txt +130 -0
  88. data/doc/start_rails_engine_with_rspec.txt +43 -0
  89. data/engine_plan.rb +13 -0
  90. data/features/step_definitions/webrat_steps.rb +115 -0
  91. data/features/support/env.rb +17 -0
  92. data/features/support/paths.rb +27 -0
  93. data/lib/deleteable.rb +50 -0
  94. data/lib/newsletter/config.rb +50 -0
  95. data/lib/newsletter/engine.rb +62 -0
  96. data/lib/newsletter/settings.rb +50 -0
  97. data/lib/newsletter/version.rb +3 -0
  98. data/lib/newsletter.rb +7 -0
  99. data/lib/tasks/newsletter.rake +84 -0
  100. data/lib/tasks/newsletter_tasks.rake +4 -0
  101. data/lib/tasks/rspec.rake +158 -0
  102. data/newsletter.gemspec +30 -0
  103. data/newsletters/exports/example-export.yaml +213 -0
  104. data/script/rails +8 -0
  105. data/spec/rcov.opts +2 -0
  106. data/spec/spec.opts +4 -0
  107. data/spec/spec_helper.rb +41 -0
  108. data/spec/test_app/README.rdoc +261 -0
  109. data/spec/test_app/Rakefile +7 -0
  110. data/spec/test_app/app/assets/javascripts/application.js +15 -0
  111. data/spec/test_app/app/assets/stylesheets/application.css +13 -0
  112. data/spec/test_app/app/controllers/application_controller.rb +3 -0
  113. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  114. data/spec/test_app/app/mailers/.gitkeep +0 -0
  115. data/spec/test_app/app/models/.gitkeep +0 -0
  116. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  117. data/spec/test_app/config/application.rb +65 -0
  118. data/spec/test_app/config/boot.rb +10 -0
  119. data/spec/test_app/config/database.yml +25 -0
  120. data/spec/test_app/config/environment.rb +5 -0
  121. data/spec/test_app/config/environments/development.rb +37 -0
  122. data/spec/test_app/config/environments/production.rb +67 -0
  123. data/spec/test_app/config/environments/test.rb +37 -0
  124. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  125. data/spec/test_app/config/initializers/inflections.rb +15 -0
  126. data/spec/test_app/config/initializers/mime_types.rb +5 -0
  127. data/spec/test_app/config/initializers/secret_token.rb +7 -0
  128. data/spec/test_app/config/initializers/session_store.rb +8 -0
  129. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  130. data/spec/test_app/config/locales/en.yml +5 -0
  131. data/spec/test_app/config/newsletter.yml +9 -0
  132. data/spec/test_app/config/routes.rb +4 -0
  133. data/spec/test_app/config.ru +4 -0
  134. data/spec/test_app/db/migrate/20131222171229_newsletter_initial.rb +104 -0
  135. data/spec/test_app/db/schema.rb +116 -0
  136. data/spec/test_app/lib/assets/.gitkeep +0 -0
  137. data/spec/test_app/log/.gitkeep +0 -0
  138. data/spec/test_app/public/404.html +26 -0
  139. data/spec/test_app/public/422.html +26 -0
  140. data/spec/test_app/public/500.html +25 -0
  141. data/spec/test_app/public/favicon.ico +0 -0
  142. data/spec/test_app/script/rails +6 -0
  143. data/zeus.json +22 -0
  144. metadata +352 -0
@@ -0,0 +1,72 @@
1
+ module Newsletter
2
+ class PiecesController < ::Newsletter::ApplicationController
3
+ layout 'admin'
4
+ before_filter :find_piece, :except => [:new,:create,:index]
5
+ before_filter :find_newsletter
6
+ before_filter :find_element
7
+ before_filter :find_area
8
+
9
+ def index
10
+ @pieces = @newsletter.pieces.active
11
+ end
12
+
13
+ def show
14
+ end
15
+
16
+ def new
17
+ @piece = Piece.new({
18
+ :area_id => @area.id,
19
+ :element_id => @element.id,
20
+ :newsletter_id => @newsletter.id
21
+ })
22
+ end
23
+
24
+ def edit
25
+ end
26
+
27
+ def create
28
+ @piece = Piece.new(params[:piece])
29
+ if @piece.save
30
+ flash[:notice] = 'Piece was successfully created.'
31
+ redirect_to(edit_newsletter_path(@newsletter))
32
+ else
33
+ render :action => "new"
34
+ end
35
+ end
36
+
37
+ def update
38
+ if @piece.update_attributes(params[:piece])
39
+ flash[:notice] = 'Piece was successfully updated.'
40
+ redirect_to(edit_newsletter_path(@newsletter))
41
+ else
42
+ render :action => "edit"
43
+ end
44
+ end
45
+
46
+ def destroy
47
+ @piece.destroy
48
+ redirect_to(newsletter_path(@newsletter,:editor=>1))
49
+ end
50
+
51
+ protected
52
+
53
+ def find_piece
54
+ @piece = Piece.find_by_id(params[:id])
55
+ end
56
+
57
+ def find_newsletter
58
+ return @newsletter = @piece.newsletter unless @piece.nil?
59
+ @newsletter = ::Newsletter::Newsletter.find(params[:newsletter_id] || params[:piece][:newsletter_id])
60
+ end
61
+
62
+ def find_element
63
+ return @element = @piece.element unless @piece.nil?
64
+ @element = Element.find(params[:element_id] || params[:piece][:element_id])
65
+ end
66
+
67
+ def find_area
68
+ return @area = @piece.area unless @piece.nil?
69
+ @area = Area.find(params[:area_id] || params[:piece][:area_id])
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module AreasHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module AssetsHelper
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Newsletter
2
+ module DesignsHelper
3
+ #left 'newsletter' in function name for helper scoping
4
+ def add_newsletter_area_link(name)
5
+ link_to_function name, {:class => "button"} do |page|
6
+ page.insert_html :bottom, :areas, :partial => 'newsletter_area',
7
+ :object => Area.new
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module ElementsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module FieldValuesHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module FieldsHelper
3
+ end
4
+ end
@@ -0,0 +1,43 @@
1
+ module Newsletter
2
+ module NewslettersHelper
3
+ def render_area
4
+ render @newsletter.design.areas.by_newsletter(@newsletter)
5
+ end
6
+
7
+ def is_email?
8
+ params[:mode].eql?('email')
9
+ end
10
+
11
+ def filter(text)
12
+ new_text = filter_eols_to_brs(text,text)
13
+ new_text = filter_email_addresses_to_mailtos(new_text,text)
14
+ new_text = filter_urls_to_links(new_text,text)
15
+ new_text.html_safe
16
+ end
17
+
18
+ protected
19
+ def filter_eols_to_brs(text,orig_text)
20
+ return '' if text.blank?
21
+ return '' if orig_text =~ /<br\s*\/>/i
22
+ text.gsub(/(\r\n|\r|\n)/,'<br/>')
23
+ end
24
+
25
+ def filter_email_addresses_to_mailtos(text,orig_text)
26
+ return '' if text.blank?
27
+ return text if orig_text =~ /<a\s+href/i
28
+ text.gsub(/([a-z0-9!#\$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[A-Z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b)/mi,%Q|<a href="mailto:\\1" target="_blank">\\1</a>|)
29
+ end
30
+
31
+ def filter_urls_to_links(text,orig_text)
32
+ return '' if text.blank?
33
+ return text if orig_text =~ /<a\s+href/i
34
+ text.gsub(/(\b(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#\/%=~_|$]))/mi) do |match|
35
+ if match.to_s.include?('://')
36
+ %Q|<a href="#{match}" target="_blank">#{match}</a>|
37
+ else
38
+ %Q|<a href="http://#{match}" target="_blank">#{match}</a>|
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ module Newsletter
2
+ module PiecesHelper
3
+ end
4
+ end
@@ -0,0 +1,50 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ Areas define what elements can exist in a part of a newleter design. Also used to group and order actual Pieces in a filled in Newsletter.
6
+
7
+ =end
8
+
9
+ module Newsletter
10
+ class Area < ActiveRecord::Base
11
+ self.table_name = "#{::Newsletter.table_prefix}areas"
12
+ belongs_to :design, :class_name => 'Newsletter::Design'
13
+ has_and_belongs_to_many :elements, :order => 'name', :join_table =>
14
+ "#{::Newsletter.table_prefix}areas_#{::Newsletter.table_prefix}elements",
15
+ :class_name => 'Newsletter::Element'
16
+ has_many :pieces, :order => 'sequence', :class_name => "Newsletter::Piece"
17
+ belongs_to :updated_by, :class_name => 'User'
18
+
19
+ attr_accessor :should_destroy
20
+
21
+ attr_protected :id
22
+
23
+ validates_presence_of :name
24
+ #FIXME: make this work with deletable or convert to auditable, and extend it to access destroyed records
25
+ #validates_uniqueness_of :name, :scope => :design_id
26
+
27
+ scope :active, :conditions => {:deleted_at => nil}
28
+ scope :by_name, lambda {|name| {:conditions => {:name => name}}}
29
+
30
+ include Deleteable
31
+
32
+ # returns field data so that Newsletter::Design.export(instance) can export itself to a YAML file
33
+ def export_fields
34
+ { :name => name,
35
+ :description => description
36
+ }
37
+ end
38
+
39
+ # builds areas from data pulled out of an exported YAML file by Newsletter::Design.import(class)
40
+ def self.import(design,data)
41
+ area = Area.create(:name => data[:name], :description => data[:description])
42
+ area.design = design
43
+ area.save
44
+ end
45
+
46
+ def should_destroy?
47
+ should_destroy.to_i == 1
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ Wrapper for attachment_fu files plugin, and is used by NewsletterPieces to save assets.
6
+
7
+ =end
8
+
9
+ module Newsletter
10
+ class Asset < ActiveRecord::Base
11
+ self.table_name = "#{::Newsletter.table_prefix}assets"
12
+ belongs_to :field, :conditions => {:type => 'Newsletter::Field::InlineAsset'},
13
+ :class_name => 'Newsletter::Field::InlineAsset'
14
+ belongs_to :piece, :class_name => 'Newsletter::Piece'
15
+
16
+ mount_uploader :image, AssetUploader
17
+
18
+ attr_protected :id
19
+
20
+ scope :by_piece, lambda{|piece| where("piece_id IS NOT NULL AND piece_id=?", piece.try(:id)) }
21
+
22
+ def public_filename
23
+ return File.join(::Newsletter::Asset.build_public_dirname(id),File.basename(self[:image])) if self[:image].present?
24
+ nil
25
+ end
26
+
27
+ def self.build_public_dirname(id)
28
+ "#{::Newsletter.asset_path}/#{("%08d" %id)[-8,4]}/#{("%08d" %id)[-4,4]}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,160 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ Newsletter::Designs define a main layout, with areas to group Elements/Pieces.
6
+
7
+
8
+ =end
9
+
10
+ module Newsletter
11
+ class Design < ActiveRecord::Base
12
+ self.table_name = "#{::Newsletter.table_prefix}designs"
13
+ has_many :areas, :order => :name, :class_name => 'Newsletter::Area'
14
+ has_many :elements, :order => :name, :class_name => 'Newsletter::Element'
15
+ belongs_to :updated_by, :class_name => 'User'
16
+ after_create :write_design
17
+
18
+ accepts_nested_attributes_for :areas
19
+
20
+ attr_accessible :name, :description, :html_text, :areas_attributes
21
+
22
+ scope :active, :conditions => {:deleted_at => nil}
23
+
24
+ validates_presence_of :name
25
+
26
+ # attr_protected :id
27
+ #FIXME: make this work with deletable or convert to auditable, and extend it to access destroyed records
28
+ #validates_uniqueness_of :name
29
+
30
+ # Export a design's data to a YAML file.
31
+ def export(filename=nil)
32
+ filename = "#{::Newsletter.designs_path}/exports/#{name_as_path}-export.yaml" unless filename
33
+ FileUtils.mkdir_p(File.dirname(filename))
34
+ File.open(filename,'w') do |file|
35
+ YAML.dump( {
36
+ :name => name,
37
+ :html_text => html_text,
38
+ :description => description,
39
+ :areas => areas.collect{|area| area.export_fields},
40
+ :elements => elements.collect{|element| element.export_fields}
41
+ },file)
42
+ end
43
+ end
44
+
45
+ # Import a design from a YAML file,
46
+ # Parameters:
47
+ # filename - path/name of file on filesystem
48
+ # design_name => rename design if already taken
49
+ def self.import(filename,design_name=nil)
50
+ raise "You must give a filename to import!" unless filename
51
+ data = YAML.load_file(filename)
52
+ transaction do
53
+ data[:name] = design_name if design_name
54
+ design = Design.create!(:name => data[:name],
55
+ :html_text => data[:html_text],
56
+ :description => data[:description])
57
+ data[:areas].each do |area_data|
58
+ Area.import(design,area_data)
59
+ end
60
+ data[:elements].each do |element_data|
61
+ Element.import(design,element_data)
62
+ end
63
+ end
64
+ end
65
+
66
+ # returns path to newsletter design for use in views and is the same for actual file
67
+ def view_path(this_name=nil)
68
+ "#{base_design_path(this_name)}/layout.html.erb"
69
+ end
70
+
71
+ #
72
+ def base_design_path(this_name=nil)
73
+ "#{::Newsletter.designs_path}/designs/#{name_as_path(this_name)}"
74
+ end
75
+
76
+
77
+
78
+ def html_text
79
+ return @html_text if @html_text
80
+ @html_text = read_design
81
+ end
82
+
83
+ def html_text=(text)
84
+ @html_text = text
85
+ end
86
+
87
+ # def area_attributes=(area_attributes)
88
+ # area_attributes.each do |attributes|
89
+ # if attributes[:id].blank?
90
+ # Rails.logger.debug "Building Area : #{attributes.inspect}"
91
+ # areas.build(attributes)
92
+ # else
93
+ # Rails.logger.debug "Setting Area data: #{attributes.inspect}"
94
+ # area = areas.detect{|area| area.id == attributes[:id].to_i}
95
+ # area.attributes = attributes
96
+ # end
97
+ # end
98
+ # end
99
+
100
+
101
+ def name=(new_name)
102
+ return if self[:name].eql?(new_name)
103
+ @old_name = self[:name] unless @old_name
104
+ self[:name] = new_name
105
+ end
106
+
107
+ def save(*args)
108
+ transaction do
109
+ move_design_on_name_change
110
+ write_design
111
+ super
112
+ end
113
+ end
114
+
115
+ # returns a version of name that is nice for filesytem use
116
+ def name_as_path(this_name=nil)
117
+ this_name = name unless this_name
118
+ this_name.gsub(/[^a-zA-Z0-9-]/,'_')
119
+ end
120
+
121
+ include Deleteable
122
+ protected
123
+ def read_design
124
+ File.readlines(view_path).join
125
+ rescue => e
126
+ #flash[:warning] << "Couldn't open design for element '#{name}' which should exist at: #{file_path} #{e.message}"
127
+ ""
128
+ end
129
+
130
+ def write_all_designs
131
+ areas.collect{|area| area.elements}.flatten.uniq.each{|element| element.send(:write_design)}
132
+ write_design
133
+ end
134
+
135
+ def move_design_on_name_change
136
+ return unless @old_name and File.exists?(view_path(@old_name))
137
+ FileUtils.mv(base_design_path(@old_name),base_design_path)
138
+ end
139
+
140
+ def write_design
141
+ FileUtils.mkdir_p(File.dirname(view_path)) unless File.exists?(File.dirname(view_path))
142
+ File.open(view_path,File::WRONLY|File::TRUNC|File::CREAT) do |file|
143
+ file.write html_text
144
+ end
145
+ end
146
+
147
+
148
+ # def save_areas
149
+ # areas.each do |area|
150
+ # if area.should_destroy?
151
+ # Rails.logger.debug "Destroying newsletter area: #{area.inspect}"
152
+ # area.destroy
153
+ # else
154
+ # Rails.logger.debug "Saving newsletter area: #{area.inspect}"
155
+ # area.save
156
+ # end
157
+ # end
158
+ # end
159
+ end
160
+ end
@@ -0,0 +1,169 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ Newsletter::Elements define the way a Newsletter::Piece looks with an erb design and the data it can hold with Newsletter::Fields. They can exist in multiple Newsletter::Areas, but currently belong to only 1 design.
6
+
7
+ =end
8
+
9
+ module Newsletter
10
+ class Element < ActiveRecord::Base
11
+ self.table_name = "#{::Newsletter.table_prefix}elements"
12
+ has_and_belongs_to_many :areas, :class_name => 'Newsletter::Area',
13
+ :join_table => "#{::Newsletter.table_prefix}areas_#{::Newsletter.table_prefix}elements"
14
+ has_many :fields, :order => 'sequence', :class_name => 'Newsletter::Field'
15
+ has_many :pieces, :class_name => 'Newsletter::Piece'
16
+ belongs_to :design, :class_name => 'Newsletter::Design'
17
+ belongs_to :updated_by, :class_name => 'User'
18
+
19
+ scope :by_design, lambda{|design| {:conditions =>{:design_id => design.id}}}
20
+
21
+ validates_presence_of :name
22
+
23
+ accepts_nested_attributes_for :fields
24
+
25
+ attr_protected :id
26
+ #FIXME: make this work with deletable or convert to auditable, and extend it to access destroyed records
27
+ #validates_uniqueness_of :name, :scope => :design_id
28
+
29
+ # defines the design path for the element as used in a render :partial => (without '_')
30
+ def view_path(this_name=nil)
31
+ this_name = self[:name] unless this_name
32
+ "#{::Newsletter.designs_path}/designs/#{design.name.gsub(/[^a-zA-Z0-9-]/,'_')}/elements/#{name_as_path(this_name)}.html.erb"
33
+ end
34
+
35
+ # returns a version of name that is nice for filesytem use
36
+ def name_as_path(this_name=nil)
37
+ this_name = name unless this_name
38
+ this_name.gsub(/[^a-zA-Z0-9-]/,'_').downcase
39
+ end
40
+
41
+ # defines where the file is in the filesystem
42
+ def file_path(this_name=nil)
43
+ File.dirname(view_path(this_name)) + '/_' + File.basename(view_path(this_name))
44
+ end
45
+
46
+ # used to record old name of an element such that the design can be moved to the new name
47
+ def name=(new_name)
48
+ return if self[:name].eql?(new_name)
49
+ @old_name = self[:name] unless @old_name
50
+ self[:name] = new_name
51
+ end
52
+
53
+ # retrieves the html erb design from the file system
54
+ def html_text
55
+ return @html_text if @html_text
56
+ @html_text = read_design
57
+ end
58
+
59
+ # sets and saves the html erb design to update the file after a successful save
60
+ def html_text=(text)
61
+ @html_text = text
62
+ end
63
+
64
+ def update_attributes(params={})
65
+ transaction do
66
+ super
67
+ end
68
+ end
69
+ # {"0"=>{"name"=>"asdfas", "label"=>"asdfasd", "description"=>"fasdfasdf",
70
+ # "type"=>"Newsletter::Field::TextArea", "_destroy"=>"false", "id"=>"22"},
71
+ # "1"=>{"name"=>"asdfasdf", "label"=>"asdfasdf", "description"=>"asdfasdf",
72
+ # "type"=>"Newsletter::Field::InlineAsset", "_destroy"=>"false", "id"=>"23"}}
73
+ # # # used to modify Newsletter::Fields in-form
74
+ def fields_attributes=(fields_attributes)
75
+ @fields_attributes = fields_attributes
76
+ end
77
+
78
+ def save_fields
79
+ @fields_attributes.each_pair do |index,attributes|
80
+ should_destroy = ['true','1'].include?attributes.delete(:_destroy)
81
+ if attributes[:id].blank?
82
+ next if should_destroy
83
+ attributes.delete(:id)
84
+ klass = attributes.delete(:type)
85
+ fields << klass.constantize.new(attributes)
86
+ else
87
+ id = attributes.delete(:id).to_i
88
+ if should_destroy
89
+ fields.where(id: id).limit(1).each(&:destroy)
90
+ else
91
+ type = attributes.delete(:type)
92
+ field = fields.detect{|field| field.id == id}
93
+ field.update_attributes(attributes)
94
+ field = Field.morph(field,type) unless field.class.name.eql?(type)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # returns field data so that Newsletter::Design.export(instance) can export itself to a YAML file
101
+ def export_fields
102
+ { :name => name,
103
+ :description => description,
104
+ :html_text => html_text,
105
+ :areas => areas.collect{|area| area.export_fields},
106
+ :fields => fields.collect{|field| field.export_fields}
107
+ }
108
+ end
109
+
110
+ # builds areas from data pulled out of an exported YAML file by Newsletter::Design.import(class)
111
+ def self.import(design,data)
112
+ element = Element.new(:name => data[:name],
113
+ :html_text => data[:html_text],
114
+ :description => data[:description])
115
+ element.design = design
116
+ element.save
117
+ data[:areas].each do |area_data|
118
+ element.areas <<
119
+ Area.find_all_by_design_id_and_name(design.id,area_data[:name])
120
+ end
121
+ data[:fields].each do |field_data|
122
+ Field.import(element,field_data)
123
+ end
124
+ end
125
+
126
+ def save(*args)
127
+ transaction do
128
+ move_design_on_name_change
129
+ write_design
130
+ super
131
+ save_fields
132
+ end
133
+ end
134
+
135
+ include Deleteable
136
+ protected
137
+ def read_design
138
+ File.readlines(file_path).join
139
+ rescue => e
140
+ #send back an empty string if no file exists yet for the design... don't raise an error
141
+ ""
142
+ end
143
+
144
+ def write_design
145
+ FileUtils.mkdir_p(File.dirname(file_path)) unless File.exists?(File.dirname(file_path))
146
+ File.open(file_path,File::WRONLY|File::TRUNC|File::CREAT) do |file|
147
+ file.write html_text
148
+ end
149
+ end
150
+
151
+ def move_design_on_name_change
152
+ return unless @old_name and File.exists?(file_path(@old_name))
153
+ FileUtils.mv(file_path(@old_name),file_path)
154
+ end
155
+
156
+ # def save_fields
157
+ # Rails.logger.warn "Fields: #{fields.inspect}"
158
+ # fields.each do |field|
159
+ # if field.should_destroy?
160
+ # Rails.logger.warn "Destroy Field: #{field.inspect}"
161
+ # field.delete
162
+ # else
163
+ # Rails.logger.warn "Save Field: #{field.inspect}"
164
+ # field.save!
165
+ # end
166
+ # end
167
+ # end
168
+ end
169
+ end
@@ -0,0 +1,67 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ InlineAsset is a Newsletter::Field that allows either a url or an uploaded asset(image/document) to be used in a Newsletter.
6
+
7
+ =end
8
+
9
+ module Newsletter
10
+ class Field::InlineAsset < Field
11
+ has_many :assets, :foreign_key => :field_id,
12
+ :class_name => 'Newsletter::Asset'
13
+
14
+ attr_protected :id
15
+
16
+ # overridden from main class to choose between a Newsletter::Asset or a given URL
17
+ def value_for_piece(piece)
18
+ Value.new(:url => url_for_piece(piece), :text => get_value(piece,:text), :asset => asset(piece))
19
+ end
20
+
21
+ # overridden from main class to choose between a Newsletter::Asset or a given URL
22
+ def set_value_for_piece(piece,params)
23
+ if params[:url]
24
+ unless url_for_piece(piece).eql?(params[:url])
25
+ asset(piece).destroy if asset(piece)
26
+ set_value(piece,:url,params[:url])
27
+ end
28
+ end
29
+ set_value(piece,:text,params[:text]) if params[:text]
30
+ if params[:uploaded_data]
31
+ asset = asset(piece)
32
+ if asset && params[:asset_id] == asset.id.to_s
33
+ asset.image = params[:uploaded_data]
34
+ else
35
+ asset = assets.build
36
+ piece.assets << asset
37
+ asset.image = params[:uploaded_data]
38
+ end
39
+ asset.save
40
+ end
41
+ end
42
+
43
+ # uniformly get URL so we can know whether it has been modified and delete
44
+ # any asset uploaded
45
+ def url_for_piece(piece)
46
+ return "#{::Newsletter.site_url}/#{asset(piece).public_filename}" unless asset(piece).try(:public_filename).nil?
47
+ get_value(piece,:url)
48
+ end
49
+
50
+ def asset(piece)
51
+ assets.by_piece(piece).first
52
+ end
53
+
54
+ # create nicer accessors for use in design since this is more than one value, unlike other
55
+ # fields
56
+ class Value
57
+ attr_accessor :url, :text, :asset, :asset_id
58
+ def initialize(params)
59
+ @url = params[:url]
60
+ @text = params[:text]
61
+ @asset = params[:asset]
62
+ @asset_id = @asset.nil? ? nil : @asset.id
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ Dumb text field, created so that we can choose for them to have text areas and text fields.
6
+
7
+ =end
8
+
9
+ module Newsletter
10
+ class Field::Text < Field
11
+ def keys
12
+ ['text']
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ =begin rdoc
2
+ Author:: Chris Hauboldt (mailto:biz@lnstar.com)
3
+ Copyright:: 2009 Lone Star Internet Inc.
4
+
5
+ Dumb text area, created so that we can choose for them to have text areas and text fields.
6
+
7
+ =end
8
+
9
+ module Newsletter
10
+ class Field::TextArea < Field
11
+ def keys
12
+ ['text_area']
13
+ end
14
+ end
15
+ end