dead_simple_cms 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+