dead_simple_cms 0.9.0

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 (66) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +266 -0
  6. data/Rakefile +17 -0
  7. data/dead_simple_cms.gemspec +47 -0
  8. data/lib/dead_simple_cms.rb +132 -0
  9. data/lib/dead_simple_cms/attribute/collection.rb +45 -0
  10. data/lib/dead_simple_cms/attribute/type/all.rb +104 -0
  11. data/lib/dead_simple_cms/attribute/type/base.rb +79 -0
  12. data/lib/dead_simple_cms/attribute/type/collection_support.rb +26 -0
  13. data/lib/dead_simple_cms/configuration.rb +50 -0
  14. data/lib/dead_simple_cms/file_uploader/base.rb +29 -0
  15. data/lib/dead_simple_cms/group.rb +58 -0
  16. data/lib/dead_simple_cms/group/configuration.rb +32 -0
  17. data/lib/dead_simple_cms/group/presenter.rb +23 -0
  18. data/lib/dead_simple_cms/rails/action_controller/extensions.rb +24 -0
  19. data/lib/dead_simple_cms/rails/action_controller/fragment_sweeper.rb +19 -0
  20. data/lib/dead_simple_cms/rails/action_view/extensions.rb +14 -0
  21. data/lib/dead_simple_cms/rails/action_view/form_builders/default.rb +16 -0
  22. data/lib/dead_simple_cms/rails/action_view/form_builders/interface.rb +72 -0
  23. data/lib/dead_simple_cms/rails/action_view/form_builders/simple_form.rb +43 -0
  24. data/lib/dead_simple_cms/rails/action_view/form_builders/simple_form_with_bootstrap.rb +19 -0
  25. data/lib/dead_simple_cms/rails/action_view/presenter.rb +63 -0
  26. data/lib/dead_simple_cms/section.rb +103 -0
  27. data/lib/dead_simple_cms/section/builder.rb +73 -0
  28. data/lib/dead_simple_cms/storage/base.rb +49 -0
  29. data/lib/dead_simple_cms/storage/database.rb +33 -0
  30. data/lib/dead_simple_cms/storage/memory.rb +20 -0
  31. data/lib/dead_simple_cms/storage/rails_cache.rb +19 -0
  32. data/lib/dead_simple_cms/storage/redis.rb +23 -0
  33. data/lib/dead_simple_cms/util/identifier.rb +40 -0
  34. data/lib/dead_simple_cms/util/identifier/dictionary.rb +65 -0
  35. data/lib/dead_simple_cms/version.rb +3 -0
  36. data/spec/dead_simple_cms/attribute/collection_spec.rb +46 -0
  37. data/spec/dead_simple_cms/attribute/type/all_spec.rb +252 -0
  38. data/spec/dead_simple_cms/attribute/type/base_spec.rb +117 -0
  39. data/spec/dead_simple_cms/configuration_spec.rb +117 -0
  40. data/spec/dead_simple_cms/file_uploader/base_spec.rb +35 -0
  41. data/spec/dead_simple_cms/group/configuration_spec.rb +24 -0
  42. data/spec/dead_simple_cms/group/presenter_spec.rb +28 -0
  43. data/spec/dead_simple_cms/group_spec.rb +65 -0
  44. data/spec/dead_simple_cms/rails/action_view/form_builders/default_spec.rb +8 -0
  45. data/spec/dead_simple_cms/rails/action_view/form_builders/simple_form_spec.rb +8 -0
  46. data/spec/dead_simple_cms/rails/action_view/form_builders/simple_form_with_bootstrap_spec.rb +8 -0
  47. data/spec/dead_simple_cms/rails/action_view/fragment_sweeper_spec.rb +22 -0
  48. data/spec/dead_simple_cms/rails/action_view/presenter_spec.rb +54 -0
  49. data/spec/dead_simple_cms/section/builder_spec.rb +28 -0
  50. data/spec/dead_simple_cms/section_spec.rb +78 -0
  51. data/spec/dead_simple_cms/storage/base_spec.rb +59 -0
  52. data/spec/dead_simple_cms/storage/database_spec.rb +51 -0
  53. data/spec/dead_simple_cms/storage/memory_spec.rb +25 -0
  54. data/spec/dead_simple_cms/storage/rails_cache_spec.rb +33 -0
  55. data/spec/dead_simple_cms/storage/redis_spec.rb +33 -0
  56. data/spec/dead_simple_cms/util/identifier/dictionary/duplciate_item_spec.rb +10 -0
  57. data/spec/dead_simple_cms/util/identifier/dictionary/invalid_entry_class_spec.rb +10 -0
  58. data/spec/dead_simple_cms/util/identifier/dictionary_spec.rb +42 -0
  59. data/spec/dead_simple_cms/util/identifier_spec.rb +27 -0
  60. data/spec/setup.rb +26 -0
  61. data/spec/setup/banner_presenter.rb +19 -0
  62. data/spec/setup/rspec_template_builder.rb +104 -0
  63. data/spec/setup/shared.rb +57 -0
  64. data/spec/setup/test_file_uploader.rb +12 -0
  65. data/spec/spec_helper.rb +48 -0
  66. metadata +221 -0
@@ -0,0 +1,49 @@
1
+ module DeadSimpleCMS
2
+ module Storage
3
+ class Base
4
+
5
+ # Public: The serializer class used to convert the attributes hash into a String.
6
+ class_attribute :serializer_class
7
+
8
+ attr_reader :section
9
+
10
+ def initialize(section)
11
+ @section = section
12
+ end
13
+
14
+ # Public: Return a hash of the typed attributes from the data source.
15
+ def read
16
+ @_cache ||= (value = read_value) ? serializer_class.load(read_value) : {}
17
+ end
18
+
19
+ # Public: Write all the attributes of the section to it's data source. Returns the attributes_hash.
20
+ def write
21
+ hash = attributes_hash
22
+ write_value(serializer_class.dump(hash))
23
+ @_cache = hash # set @_cache after the write
24
+ end
25
+
26
+ # Public: Unique key for use in the storage mechanism.
27
+ def unique_key
28
+ @unique_key ||= section.identifier
29
+ end
30
+
31
+ private
32
+
33
+ # Public: Values to store.
34
+ def attributes_hash
35
+ attributes_hash = section.attributes.to_hash
36
+ attributes_hash.each { |k, attr| attributes_hash[k] = attr.value }
37
+ end
38
+
39
+ def read_value
40
+ raise(NotImplementedError, "Please provide a read_value method for #{self.class}")
41
+ end
42
+
43
+ def write_value(value)
44
+ raise(NotImplementedError, "Please provide a write_value method for #{self.class}")
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ module DeadSimpleCMS
2
+ module Storage
3
+ class Database < Base
4
+
5
+ # Public: Table used to store the key/value pairs for the CMS
6
+ #
7
+ # Structure
8
+ # * key: varchar
9
+ # * value: text
10
+ #
11
+ class Table < ActiveRecord::Base
12
+ self.table_name = "dead_simple_cms"
13
+ validates :key, :presence => true, :uniqueness => true
14
+ end
15
+
16
+ class_attribute :active_record
17
+ self.active_record = Table
18
+
19
+ private
20
+
21
+ def read_value
22
+ record = active_record.find_by_key(unique_key)
23
+ record && record.value
24
+ end
25
+
26
+ def write_value(value)
27
+ record = active_record.find_or_create_by_key(unique_key)
28
+ record.update_attribute(:value, value)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ module DeadSimpleCMS
2
+ module Storage
3
+ # Public: Only used for testing and test-driving purposes. Once your application shutdown, you will lose the CMS information
4
+ class Memory < Base
5
+
6
+ @@cache = {}
7
+
8
+ private
9
+
10
+ def read_value
11
+ @@cache[unique_key]
12
+ end
13
+
14
+ def write_value(value)
15
+ @@cache[unique_key] = value
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module DeadSimpleCMS
2
+ module Storage
3
+ # Public: Store all the values in whatever is defined for the rails cache.
4
+ # Does not support storage of data with the value (ie no file storage support).
5
+ class RailsCache < Base
6
+
7
+ private
8
+
9
+ def read_value
10
+ ::Rails.cache.read(unique_key)
11
+ end
12
+
13
+ def write_value(value)
14
+ ::Rails.cache.write(unique_key, value, :expires_in => 5.years.from_now) # some really long time
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module DeadSimpleCMS
2
+ module Storage
3
+ class Redis < Base
4
+ class << self
5
+ attr_writer :connection
6
+ def connection
7
+ @connection ||= (defined?(REDIS) && REDIS) || raise(NotImplementedError, "Redis connection not set.")
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def read_value
14
+ self.class.connection.get(unique_key)
15
+ end
16
+
17
+ def write_value(value)
18
+ self.class.connection.set(unique_key, value)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ require 'dead_simple_cms/util/identifier/dictionary'
2
+ module DeadSimpleCMS
3
+ module Util
4
+
5
+ # Public: A module for the convention of having an identifier:
6
+ #
7
+ # * an identifier (Symbol)
8
+ # * a label (default: titleize) to present to the world
9
+ # * a collection for looking up items based on their `identifier`
10
+ #
11
+ # We specifically got away from having a "name" as that usually leads to confusion. Is the name the "foo_bar" name or the
12
+ # pretty "Foo Bar" name?
13
+ module Identifier
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ attr_accessor :label, :identifier
18
+ end
19
+
20
+ module InstanceMethods
21
+
22
+ def initialize(identifier, options={})
23
+ @identifier = identifier.to_sym
24
+ @label = options[:label] || identifier.to_s.titleize
25
+ super()
26
+ end
27
+
28
+ end
29
+
30
+ module ClassMethods
31
+
32
+ # Public: creates a new dictionary class.
33
+ def new_dictionary(*args, &block)
34
+ Dictionary.new(self, *args, &block)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ module DeadSimpleCMS
2
+ module Util
3
+ module Identifier
4
+ # Public: a dictionary of objects along with their identifiers. Since we are indexing by the identifier, we extend
5
+ # from Hash since that is our primary data store for the objects. We also enforce the condition that the values must
6
+ # be of a +target_class+.
7
+ class Dictionary < Hash
8
+ class DuplciateItem < StandardError ; end
9
+ class InvalidEntryClass < StandardError ; end
10
+
11
+ attr_reader :target_class, :identifier_method
12
+
13
+ def initialize(target_class, *args, &block)
14
+ options = args.extract_options!
15
+ @identifier_method = options[:identifier_method] || :identifier
16
+ @target_class = target_class
17
+ super(*args, &block)
18
+ end
19
+
20
+ def identifiers
21
+ keys
22
+ end
23
+
24
+ # Public: Convenience method for adding item by it's identifier.
25
+ def add(item)
26
+ self[to_identifier(item)] = item
27
+ end
28
+
29
+ def include?(key)
30
+ key?(to_identifier(key))
31
+ end
32
+
33
+ def to_hash
34
+ Hash[self]
35
+ end
36
+
37
+ # Public: set a record for the dictionary.
38
+ #
39
+ # * If the value is not of type target_class, raise an error.
40
+ # * If the record already exists, raise an error.
41
+ def []=(key, value)
42
+ raise InvalidEntryClass, "Only entries with class #{target_class} can be added" unless value.is_a?(target_class)
43
+ identifier = to_identifier(key)
44
+ raise DuplciateItem, "#{identifier} already present in dictionary." if key?(identifier)
45
+ super(identifier, value)
46
+ end
47
+
48
+ def [](key)
49
+ super(to_identifier(key))
50
+ end
51
+
52
+ private
53
+
54
+ # Private: Cast an identifier or other object into an identifier.
55
+ def to_identifier(identifier)
56
+ case identifier
57
+ when String, Symbol then identifier.to_sym
58
+ when Util::Identifier then identifier.send(identifier_method) # if module is included
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module DeadSimpleCMS
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,46 @@
1
+ require "spec_helper"
2
+
3
+ describe DeadSimpleCMS::Attribute::Collection do
4
+
5
+ subject { described_class.new(:collection_identifier) }
6
+
7
+ describe ".dictionary_identifier_method" do
8
+ it "should default to :identifier" do
9
+ described_class.dictionary_identifier_method.should == :identifier
10
+ end
11
+ end
12
+
13
+ its(:identifier) { should == :collection_identifier }
14
+
15
+ its(:attributes) { should be_an_instance_of(DeadSimpleCMS::Util::Identifier::Dictionary) }
16
+
17
+ its (:persisted?) { should be false }
18
+
19
+ describe "#update_attributes" do
20
+
21
+ it "should delegate to the setter on #attributes" do
22
+ subject.should_receive(:foo=).with(:bar)
23
+ subject.update_attributes(:foo => :bar)
24
+ end
25
+
26
+ end
27
+
28
+ describe "#add_attribute" do
29
+
30
+ let(:string_attribute) { DeadSimpleCMS::Attribute::Type::String.new(:string_attr) }
31
+
32
+ before(:each) { subject.add_attribute(string_attribute) }
33
+
34
+ it "should create a getter method" do
35
+ string_attribute.stub(:value).and_return("value")
36
+ subject.string_attr.should == "value"
37
+ end
38
+
39
+ it "should create a setter method" do
40
+ subject.string_attr = "value1"
41
+ subject.attributes[:string_attr].value.should == "value1"
42
+ end
43
+ end
44
+
45
+ end
46
+
@@ -0,0 +1,252 @@
1
+ require "spec_helper"
2
+
3
+ shared_examples_for DeadSimpleCMS::Attribute::Type::CollectionSupport do
4
+ describe "#initialize" do
5
+ context "when collection is provided" do
6
+
7
+ let(:options) { {} }
8
+
9
+ subject { described_class.new(:some_identifier, options.update(:collection => %w{a b c})) }
10
+
11
+ context "and valid :input_type is specified" do
12
+ let(:options) { {:input_type => :radio} }
13
+ its(:input_type) { should == :radio }
14
+ end
15
+
16
+ context "and valid :input_type is not specified" do
17
+ let(:options) { {:input_type => :string} }
18
+
19
+ def self.default_input_type(type)
20
+ around(:each) do |example|
21
+ tmp = described_class.default_input_type
22
+ described_class.default_input_type = type
23
+ example.run
24
+ described_class.default_input_type = tmp
25
+ end
26
+ end
27
+
28
+ context "and default_input_type is not valid" do
29
+ default_input_type(:string)
30
+ its(:input_type) { should == described_class::DEFAULT_COLLECTION_INPUT_TYPE }
31
+ end
32
+
33
+ context "and default_input_type is valid" do
34
+ default_input_type(:radio)
35
+ its(:input_type) { should == :radio }
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ shared_context "Attribute Setup" do
44
+
45
+ let(:options) { {} }
46
+
47
+ subject { described_class.new(:sample_identifier, options) }
48
+
49
+ end
50
+ describe DeadSimpleCMS::Attribute::Type::String do
51
+
52
+ include_context "Attribute Setup"
53
+
54
+ it_behaves_like DeadSimpleCMS::Attribute::Type::CollectionSupport
55
+
56
+ its(:default_input_type) { should == :string }
57
+
58
+ end
59
+ describe DeadSimpleCMS::Attribute::Type::Text do
60
+
61
+ include_context "Attribute Setup"
62
+
63
+ its(:default_input_type) { should == :text }
64
+
65
+ end
66
+ describe DeadSimpleCMS::Attribute::Type::Symbol do
67
+
68
+ include_context "Attribute Setup"
69
+
70
+ its(:default_input_type) { should == :string }
71
+
72
+ end
73
+ describe DeadSimpleCMS::Attribute::Type::Boolean do
74
+
75
+ include_context "Attribute Setup"
76
+
77
+ it_behaves_like DeadSimpleCMS::Attribute::Type::CollectionSupport
78
+
79
+ its(:default_input_type) { should == :radio }
80
+ its(:collection) { should == [true, false] }
81
+
82
+ describe "#convert_value" do
83
+ it %{should convert "true" or "1" into TrueClass} do
84
+ ["True", "true", "1", "TruE"].each do |str|
85
+ subject.send(:convert_value, str).should be true
86
+ end
87
+ end
88
+
89
+ it %{should interpret everything else as false} do
90
+ %w{abcs false 0 dude sweet}.each do |str|
91
+ subject.send(:convert_value, str).should be false
92
+ end
93
+ end
94
+
95
+ context "when a non-String is passed in" do
96
+ it "should interpret true or false as themselves" do
97
+ [true, false].each do |bool|
98
+ subject.send(:convert_value, bool).should be bool
99
+ end
100
+ end
101
+
102
+ it "should interpret nil as false" do
103
+ subject.send(:convert_value, nil).should be false
104
+ end
105
+
106
+ it "should interpret other objects" do
107
+ [Object.new, :dfdf].each { |x| subject.send(:convert_value, x).should be true }
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ describe DeadSimpleCMS::Attribute::Type::Numeric do
114
+
115
+ include_context "Attribute Setup"
116
+
117
+ it_behaves_like DeadSimpleCMS::Attribute::Type::CollectionSupport
118
+
119
+ its(:default_input_type) { should == :string }
120
+
121
+ end
122
+ describe DeadSimpleCMS::Attribute::Type::Integer do
123
+
124
+ include_context "Attribute Setup"
125
+
126
+ it_behaves_like DeadSimpleCMS::Attribute::Type::CollectionSupport
127
+
128
+ its(:default_input_type) { should == :string }
129
+
130
+ describe "#convert_value" do
131
+ it "should convert the value into an integer" do
132
+ subject.send(:convert_value, "545").should be 545
133
+ end
134
+
135
+ it "should return nil if nil passed in" do
136
+ subject.send(:convert_value, nil).should be nil
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+ describe DeadSimpleCMS::Attribute::Type::File do
143
+
144
+ include_context "Attribute Setup"
145
+
146
+ its(:default_input_type) { should == :file }
147
+
148
+ let(:file_ext) { "jpg" }
149
+ let(:data) { double("data") }
150
+ let(:options) { {:data => data, :file_ext => file_ext}}
151
+ let(:uploaded_file) do
152
+ require 'stringio'
153
+ ActionDispatch::Http::UploadedFile.new(:filename => "filename.png", :tempfile => StringIO.new("file information") )
154
+ end
155
+
156
+ its(:data) { should == data }
157
+
158
+ describe "#file_ext" do
159
+ its(:file_ext) { should == file_ext }
160
+
161
+ it "should default to 'dat' if none provided" do
162
+ described_class.new(:identifier).file_ext.should == "dat"
163
+ end
164
+ end
165
+
166
+ describe "#convert_value" do
167
+ it "should return the value if it is a String" do
168
+ subject.send(:convert_value, "string").should == "string"
169
+ end
170
+
171
+ it "should return the value if it is nil" do
172
+ subject.send(:convert_value, nil).should be nil
173
+ end
174
+
175
+ context "when ActionDispatch::Http::UploadedFile is passed in" do
176
+ it "should set the file_ext from the uploaded filename" do
177
+ subject.send(:convert_value, uploaded_file)
178
+ subject.file_ext.should == "png"
179
+ end
180
+
181
+ it "should set the data" do
182
+ subject.send(:convert_value, uploaded_file)
183
+ subject.data.should == "file information"
184
+ end
185
+
186
+ it "should return the stored @value" do
187
+ subject.instance_variable_set(:@value, :value)
188
+ subject.send(:convert_value, uploaded_file).should == :value
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "#upload!" do
194
+
195
+ context "when uploader_class is not set" do
196
+ around(:each) do |example|
197
+ tmp = described_class.uploader_class
198
+ described_class.uploader_class = nil
199
+ example.run
200
+ described_class.uploader_class = tmp
201
+ end
202
+
203
+ it "should raise an error if the uploader_class is not set" do
204
+ lambda { subject.upload! }.should raise_error(NotImplementedError)
205
+ end
206
+
207
+ end
208
+
209
+ context "when uploader_class is set" do
210
+ let(:file_uploader_class) do
211
+ Class.new(DeadSimpleCMS::FileUploader::Base) do
212
+ def upload! ; end
213
+ def url; "new url" end
214
+ end
215
+ end
216
+
217
+ before(:each) do
218
+ subject.uploader_class = file_uploader_class
219
+ end
220
+ it "should call upload!" do
221
+ subject.should_receive(:upload!).once
222
+ subject.upload!
223
+ end
224
+
225
+ it "should set the url with the value" do
226
+ subject.upload!
227
+ subject.value.should == "new url"
228
+ end
229
+
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+ describe DeadSimpleCMS::Attribute::Type::Image do
236
+
237
+ include_context "Attribute Setup"
238
+
239
+ let(:options) { {:width => 406, :height => 600} }
240
+
241
+ its(:width) { should == 406 }
242
+ its(:height) { should == 600 }
243
+
244
+ describe "#hint" do
245
+ context "when not set" do
246
+ its(:hint) { should == "Image should be 406 x 600." }
247
+ end
248
+ end
249
+
250
+ end
251
+
252
+