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,45 @@
1
+ module DeadSimpleCMS
2
+ module Attribute
3
+ class Collection
4
+ include Util::Identifier
5
+
6
+ attr_reader :attributes
7
+
8
+ delegate :[], :[]=, :to => :attributes
9
+
10
+ # The method used to construct the identifier (key) by which the attribute (value) will be stored with.
11
+ class_attribute :dictionary_identifier_method
12
+ self.dictionary_identifier_method = :identifier
13
+
14
+ def initialize(identifier, options={})
15
+ @attributes = Attribute::Type::Base.new_dictionary(:identifier_method => dictionary_identifier_method)
16
+ super
17
+ end
18
+
19
+ # Play nicely with Rails fields_for.
20
+ def persisted?
21
+ false
22
+ end
23
+
24
+ def update_attributes(attributes)
25
+ attributes.each { |k, v| send("#{k}=", v) }
26
+ end
27
+
28
+ def add_attribute(attribute)
29
+ attributes.add(attribute)
30
+ attribute_accessor(attribute)
31
+ end
32
+
33
+ private
34
+
35
+ def attribute_accessor(attribute)
36
+ identifier = attribute.send(dictionary_identifier_method)
37
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
38
+ def #{identifier} ; attributes[#{identifier.inspect}].value ; end
39
+ def #{identifier}=(v) ; attributes[#{identifier.inspect}].value = v ; end
40
+ RUBY
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,104 @@
1
+ module DeadSimpleCMS
2
+ module Attribute
3
+ module Type
4
+ class String < Base
5
+ self.default_input_type = :string
6
+ include CollectionSupport
7
+ end
8
+ class Text < String
9
+ self.default_input_type = :text
10
+ end
11
+ class Symbol < Base
12
+ self.default_input_type = :string
13
+ def convert_value(value)
14
+ value.to_s.to_sym if value.present?
15
+ end
16
+ private :convert_value
17
+ end
18
+ class Boolean < Base
19
+ self.default_input_type = :radio
20
+
21
+ include CollectionSupport
22
+
23
+ def initialize(identifier, options={})
24
+ options.update(:collection => [true, false], :default => false)
25
+ super
26
+ end
27
+
28
+ private
29
+
30
+ def convert_value(value)
31
+ value.is_a?(::String) ? ["true", "1"].include?(value.downcase) : !!value
32
+ end
33
+
34
+ end
35
+ class Numeric < Base
36
+ self.default_input_type = :string
37
+ include CollectionSupport
38
+ end
39
+ class Integer < Numeric
40
+ def convert_value(value)
41
+ value && value.to_i
42
+ end
43
+ end
44
+ # Public: File attributes are stored at some publicly accessible url.
45
+ class File < Base
46
+ self.default_input_type = :file
47
+
48
+ attr_accessor :data, :file_ext
49
+
50
+ class_attribute :uploader_class
51
+
52
+ alias :url :value
53
+
54
+ def initialize(identifier, options={})
55
+ @data = options[:data]
56
+ @file_ext = options[:file_ext] || "dat"
57
+ super
58
+ end
59
+
60
+ # Public: Takes the current #value, detects if its an object to upload and replaces the #value with the url
61
+ # to be stored in the CMS.
62
+ def upload!
63
+ raise NotImplementedError, "Please define an uploader class (see DeadSimpleCMS::FileUploader::Base)" unless uploader_class
64
+ return unless data
65
+
66
+ s3_uploader = uploader_class.new(self)
67
+ s3_uploader.upload!
68
+ self.value = s3_uploader.url
69
+ end
70
+
71
+ private
72
+
73
+ def convert_value(value)
74
+ case value
75
+ when ::String, NilClass
76
+ value
77
+ when ActionDispatch::Http::UploadedFile
78
+ @file_ext = value.original_filename[/\.(.+)$/, 1]
79
+ value.rewind # make sure it's rewound
80
+ self.data = value.read
81
+ @value # just return the same stored @value so it doesn't change for now
82
+ else
83
+ raise("Don't know how to convert value: #{value.inspect}")
84
+ end
85
+ end
86
+
87
+ end
88
+ class Image < File
89
+
90
+ attr_reader :width, :height
91
+
92
+ def initialize(identifier, options={})
93
+ @width, @height = options.values_at(:width, :height)
94
+ super
95
+ end
96
+
97
+ def hint
98
+ super || "Image should be #{width} x #{height}."
99
+ end
100
+
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,79 @@
1
+ module DeadSimpleCMS
2
+ module Attribute
3
+ module Type
4
+ class Base
5
+
6
+ include Util::Identifier
7
+
8
+ class << self
9
+ # Public: the method name used to identify this type in the Builder
10
+ attr_writer :builder_method_name
11
+
12
+ # If not provided on the subclass infer it from the class name. Because of how class_attribute works, this
13
+ # method will be overwritten when someone explicitly calls .builder_method_name= on a subclass.
14
+ def builder_method_name
15
+ @builder_method_name ||= name.demodulize.underscore
16
+ end
17
+
18
+ end
19
+
20
+ # Public: a Symbol representing the default input type required for forms
21
+ class_attribute :default_input_type, :instance_writer => false
22
+
23
+ VALID_INPUT_TYPES = [:string, :text, :select, :file, :radio].freeze
24
+
25
+
26
+ attr_reader :hint, :input_type, :group_hierarchy, :required
27
+ attr_accessor :section
28
+
29
+ def initialize(identifier, options={})
30
+ options.reverse_merge!(:group_hierarchy => [], :input_type => default_input_type, :required => false)
31
+ @hint, @default, @input_type, @group_hierarchy, @section, @required =
32
+ options.values_at(:hint, :default, :input_type, :group_hierarchy, :section, :required)
33
+ raise("Invalid input type: #{input_type.inspect}. Should be one of #{VALID_INPUT_TYPES}.") unless VALID_INPUT_TYPES.include?(input_type)
34
+ super
35
+ end
36
+
37
+ def root_group?
38
+ group_hierarchy.last.try(:root?)
39
+ end
40
+
41
+ # Public: The identifier on the section level. It must be unique amongst the groups.
42
+ def section_identifier
43
+ (group_hierarchy + [self]).map(&:identifier).join("_").to_sym
44
+ end
45
+
46
+ def default
47
+ @default.is_a?(Proc) ? @default.call : @default
48
+ end
49
+
50
+ def value=(value)
51
+ @value = convert_value(value)
52
+ end
53
+
54
+ # Public: Returns the non-blank value from the storage or the default.
55
+ def value
56
+ return @value if instance_variable_defined?(:@value) # If the value was set to nil, we should return that value.
57
+ attributes = attributes_from_storage
58
+ @value = attributes.key?(section_identifier) ? attributes[section_identifier] : default
59
+ end
60
+
61
+ def inspect
62
+ ivars = [:identifier, :hint, :default, :required, :input_type].map { |m| ivar = "@#{m}" ; "#{ivar}=#{instance_variable_get(ivar).inspect}" }
63
+ "#<#{self.class} #{ivars.join(", ")}"
64
+ end
65
+
66
+ private
67
+
68
+ def attributes_from_storage
69
+ section.storage.read
70
+ end
71
+
72
+ def convert_value(value)
73
+ value.presence
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,26 @@
1
+ module DeadSimpleCMS
2
+ module Attribute
3
+ module Type
4
+ module CollectionSupport
5
+
6
+ VALID_COLLECTION_INPUT_TYPES = [:select, :radio].freeze
7
+
8
+ DEFAULT_COLLECTION_INPUT_TYPE = :select
9
+
10
+ def initialize(identifier, options={})
11
+ if @collection = options[:collection]
12
+ # Pick either the options[:input_type], default_input_type, or the :select. Whichever matches the valid types.
13
+ options[:input_type] = ([options[:input_type], default_input_type, DEFAULT_COLLECTION_INPUT_TYPE] & VALID_COLLECTION_INPUT_TYPES).first
14
+ end
15
+ super
16
+ end
17
+
18
+ # Public: For performance and loading reasons, we allow the ability to pass through a collection as a lambda.
19
+ def collection
20
+ @collection.is_a?(Proc) ? @collection.call : @collection
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ module DeadSimpleCMS
2
+ class Configuration
3
+
4
+ def section(identifier, options={}, &block)
5
+ section = DeadSimpleCMS::Section.new(identifier, options, &block)
6
+ DeadSimpleCMS.sections.add(section)
7
+ # Convenience method, allows one to access DeadSimpleCMS.sections.<section_name>
8
+ DeadSimpleCMS.sections.instance_eval %{def #{section.identifier} ; self[#{section.identifier.inspect}] ; end}
9
+ end
10
+
11
+ def group_configuration(identifier, options={}, &block)
12
+ DeadSimpleCMS.group_configurations.add(Group::Configuration.new(identifier, options, &block))
13
+ end
14
+
15
+ def register_attribute_classes(*classes)
16
+ classes.each do |klass|
17
+ DeadSimpleCMS::Section::Builder.define_attribute_builder_method(klass)
18
+ Group::Configuration.define_attribute_builder_method(klass)
19
+ end
20
+ end
21
+
22
+ def storage_class(klass=nil, options={})
23
+ if klass
24
+ klass = "DeadSimpleCMS::Storage::#{klass.to_s.classify}".constantize if klass.is_a?(Symbol)
25
+ options.each { |k, v| klass.send("#{k}=", v) }
26
+ Section.storage_class = klass
27
+ end
28
+ Section.storage_class
29
+ end
30
+
31
+ def storage_serializer_class(klass=nil)
32
+ Storage::Base.serializer_class = klass if klass
33
+ Storage::Base.serializer_class
34
+ end
35
+
36
+ def default_form_builder(klass=nil)
37
+ if klass
38
+ klass = "DeadSimpleCMS::Rails::ActionView::FormBuilders::#{klass.to_s.classify}".constantize if klass.is_a?(Symbol)
39
+ DeadSimpleCMS::Rails::ActionView::Presenter.form_builder = klass
40
+ end
41
+ DeadSimpleCMS::Rails::ActionView::Presenter.form_builder
42
+ end
43
+
44
+ def file_uploader_class(klass=nil)
45
+ Attribute::Type::File.uploader_class = klass if klass
46
+ Attribute::Type::File.uploader_class
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ module DeadSimpleCMS
2
+ module FileUploader
3
+ # Base file uploader class. Inherit from this to build your own uploaders for files.
4
+ class Base
5
+
6
+ attr_reader :file_attribute
7
+ delegate :file_ext, :data, :to => :file_attribute
8
+
9
+ def initialize(file_attribute)
10
+ @file_attribute = file_attribute
11
+ end
12
+
13
+ def upload!
14
+ raise NotImplementedError, "Please overwrite this with your own upload functionality."
15
+ end
16
+
17
+ def url
18
+ raise NotImplementedError, "Please overwrite this with your own url constructor."
19
+ end
20
+
21
+ # Public: We need to have all these nesting to protect against corner-case collisons.
22
+ def path(namespace="dead_simple_cms")
23
+ # dead_simple_cms/<section>/<group>/attribute.jpg
24
+ @path ||= [namespace, *[file_attribute.section, *file_attribute.group_hierarchy, file_attribute].map(&:identifier)].compact * "/" + "." + file_ext
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module DeadSimpleCMS
2
+ # Public: A Group is essentially an Attribute::Collection with the ability to have it's own groups on it with infinite
3
+ # nesting of groups.
4
+ class Group < Attribute::Collection
5
+
6
+ ROOT_IDENTIFIER = :root
7
+
8
+ attr_reader :groups, :presenter_class, :render_proc
9
+
10
+ def initialize(identifier, options={})
11
+ @groups = DeadSimpleCMS::Group.new_dictionary
12
+ super
13
+ end
14
+
15
+ def self.root
16
+ new(ROOT_IDENTIFIER)
17
+ end
18
+
19
+ # Public: Set different mechanisms for rendering this group.
20
+ def display(presenter_class=nil, &block)
21
+ @presenter_class = presenter_class
22
+ @render_proc = block
23
+ end
24
+
25
+ # Public: If a presenter class was specified, returns an instance of the presenter.
26
+ def presenter(view_context, *args)
27
+ @presenter_class.new(view_context, *args) if @presenter_class
28
+ end
29
+
30
+ # Public: Render the group using the passed in proc in the scope of the template.
31
+ def render(view_context, *args)
32
+ if @render_proc
33
+ view_context.instance_exec(self, *args, &@render_proc)
34
+ elsif presenter = presenter(view_context, self, *args)
35
+ presenter.render
36
+ end
37
+ end
38
+
39
+ def root?
40
+ identifier == ROOT_IDENTIFIER
41
+ end
42
+
43
+ def add_group(group)
44
+ groups.add(group)
45
+ group_accessor(group)
46
+ end
47
+
48
+ private
49
+
50
+ # The <group>_attributes method plays nice with form builder's fields_for.
51
+ def group_accessor(group)
52
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
53
+ def #{group.identifier}_attributes=(v) ; groups[#{group.identifier.inspect}].update_attributes(v) ; end
54
+ def #{group.identifier} ; groups[#{group.identifier.inspect}] ; end
55
+ RUBY
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ module DeadSimpleCMS
2
+ class Group
3
+ class Configuration
4
+
5
+ include Util::Identifier
6
+
7
+ attr_reader :options, :attribute_arguments, :presenter_class, :render_proc
8
+
9
+ def initialize(identifier, options={}, &block)
10
+ super(identifier, options)
11
+ @options = options
12
+ @attribute_arguments = {}
13
+ instance_eval(&block)
14
+ end
15
+
16
+ def display(presenter_class=nil, &block)
17
+ @presenter_class = presenter_class if presenter_class
18
+ @render_proc = block if block_given?
19
+ end
20
+
21
+ def self.define_attribute_builder_method(klass)
22
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
23
+ def #{klass.builder_method_name}(identifier, options={})
24
+ attribute_arguments[identifier] = [#{klass.builder_method_name.inspect}, options]
25
+ end
26
+ RUBY
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ require 'delegate'
2
+ module DeadSimpleCMS
3
+ class Group
4
+ # Public: Presenter class used for rendering groups.
5
+ #
6
+ # Within the context of this group, you can call methods as if you were in the view_context.
7
+ class Presenter < SimpleDelegator
8
+
9
+ attr_reader :group
10
+
11
+ def initialize(view_context, group, *args)
12
+ @group = group
13
+ initialize_extra_arguments(*args)
14
+ super(view_context)
15
+ end
16
+
17
+ # Private: Initialize extra arguments for the presenter.
18
+ def initialize_extra_arguments(*args)
19
+ end
20
+
21
+ end
22
+ end
23
+ end