sethyates-content_manager 0.4.0 → 1.0.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 (40) hide show
  1. data/README.rdoc +253 -1
  2. data/VERSION +1 -1
  3. data/content_manager.gemspec +38 -2
  4. data/doc/created.rid +1 -0
  5. data/doc/files/README_rdoc.html +487 -0
  6. data/doc/fr_class_index.html +26 -0
  7. data/doc/fr_file_index.html +27 -0
  8. data/doc/fr_method_index.html +26 -0
  9. data/doc/index.html +24 -0
  10. data/doc/rdoc-style.css +208 -0
  11. data/generators/component_scaffold/USAGE +28 -0
  12. data/generators/component_scaffold/component_scaffold_generator.rb +84 -0
  13. data/generators/component_scaffold/templates/controller.rb +85 -0
  14. data/generators/component_scaffold/templates/model.rb +5 -0
  15. data/generators/component_scaffold/templates/style.css +1 -0
  16. data/generators/component_scaffold/templates/view_edit.html.erb +13 -0
  17. data/generators/component_scaffold/templates/view_index.html.erb +22 -0
  18. data/generators/component_scaffold/templates/view_new.html.erb +12 -0
  19. data/generators/component_scaffold/templates/view_show.html.erb +3 -0
  20. data/generators/content_scaffold/USAGE +28 -0
  21. data/generators/content_scaffold/content_scaffold_generator.rb +83 -0
  22. data/generators/content_scaffold/templates/controller.rb +85 -0
  23. data/generators/content_scaffold/templates/model.rb +8 -0
  24. data/generators/content_scaffold/templates/view_edit.html.erb +13 -0
  25. data/generators/content_scaffold/templates/view_index.html.erb +22 -0
  26. data/generators/content_scaffold/templates/view_new.html.erb +12 -0
  27. data/generators/content_scaffold/templates/view_show.html.erb +8 -0
  28. data/lib/component.rb +28 -0
  29. data/lib/content/adapters/base.rb +79 -0
  30. data/lib/content/adapters/cabinet_adapter.rb +62 -0
  31. data/lib/content/adapters/tyrant_adapter.rb +73 -0
  32. data/lib/content/item.rb +178 -0
  33. data/lib/content/item_association_class_methods.rb +148 -0
  34. data/lib/content/item_class_methods.rb +71 -0
  35. data/lib/content/item_dirty_methods.rb +171 -0
  36. data/lib/content/item_finder_class_methods.rb +203 -0
  37. data/lib/content/manager.rb +105 -0
  38. data/lib/content/sublayout.rb +44 -0
  39. data/lib/content/template.rb +24 -0
  40. metadata +38 -2
@@ -0,0 +1,71 @@
1
+ module Content
2
+ module ItemClassMethods
3
+ def establish_connection(options = {})
4
+ conn_options = ::YAML::load_file('config/content.yml')[::RAILS_ENV]
5
+ conn_options.merge!(options) unless options.nil?
6
+ conn_type = conn_options[:type] || conn_options["type"] || :cabinet
7
+ $adapter = "content/adapters/#{conn_type}_adapter".camelize.constantize.new(conn_options)
8
+ end
9
+
10
+ def connection()
11
+ $adapter ||= establish_connection
12
+ end
13
+
14
+ def self_and_descendants_from_active_record#nodoc:
15
+ klass = self
16
+ classes = [klass]
17
+ while klass != klass.base_class
18
+ classes << klass = klass.superclass
19
+ end
20
+ classes
21
+ rescue
22
+ [self]
23
+ end
24
+
25
+ def human_name(options = {})
26
+ defaults = self_and_descendants_from_active_record.map do |klass|
27
+ :"#{klass.name.underscore}"
28
+ end
29
+ defaults << name
30
+ defaults.shift.to_s.humanize
31
+ end
32
+
33
+ def human_attribute_name(attr_name)
34
+ attr_name.to_s.humanize
35
+ end
36
+
37
+ def create(attributes = nil, &block)
38
+ if attributes.is_a?(Array)
39
+ attributes.collect { |attr| create(attr, &block) }
40
+ else
41
+ object = new(attributes)
42
+ yield(object) if block_given?
43
+ object.save
44
+ object
45
+ end
46
+ end
47
+
48
+ def update(id, attributes)
49
+ if id.is_a?(Array)
50
+ idx = -1
51
+ id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
52
+ else
53
+ object = find(id)
54
+ object.update_attributes(attributes)
55
+ object
56
+ end
57
+ end
58
+
59
+ def destroy(id)
60
+ if id.is_a?(Array)
61
+ id.map { |one_id| destroy(one_id) }
62
+ else
63
+ find(id).destroy
64
+ end
65
+ end
66
+
67
+ def delete(id)
68
+ destroy id
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,171 @@
1
+ module Content
2
+ # Track unsaved attribute changes.
3
+ #
4
+ # A newly instantiated object is unchanged:
5
+ # person = Person.find_by_name('uncle bob')
6
+ # person.changed? # => false
7
+ #
8
+ # Change the name:
9
+ # person.name = 'Bob'
10
+ # person.changed? # => true
11
+ # person.name_changed? # => true
12
+ # person.name_was # => 'uncle bob'
13
+ # person.name_change # => ['uncle bob', 'Bob']
14
+ # person.name = 'Bill'
15
+ # person.name_change # => ['uncle bob', 'Bill']
16
+ #
17
+ # Save the changes:
18
+ # person.save
19
+ # person.changed? # => false
20
+ # person.name_changed? # => false
21
+ #
22
+ # Assigning the same value leaves the attribute unchanged:
23
+ # person.name = 'Bill'
24
+ # person.name_changed? # => false
25
+ # person.name_change # => nil
26
+ #
27
+ # Which attributes have changed?
28
+ # person.name = 'bob'
29
+ # person.changed # => ['name']
30
+ # person.changes # => { 'name' => ['Bill', 'bob'] }
31
+ #
32
+ # Before modifying an attribute in-place:
33
+ # person.name_will_change!
34
+ # person.name << 'by'
35
+ # person.name_change # => ['uncle bob', 'uncle bobby']
36
+ module ItemDirtyMethods
37
+ DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
38
+
39
+ def self.included(base)
40
+ # base.attribute_method_suffix *DIRTY_SUFFIXES
41
+ base.alias_method_chain :write_attribute, :dirty
42
+ base.alias_method_chain :save, :dirty
43
+ base.alias_method_chain :save!, :dirty
44
+ base.alias_method_chain :update, :dirty
45
+ # base.alias_method_chain :reload, :dirty
46
+
47
+ base.send(:extend, ClassMethods)
48
+ end
49
+
50
+ # Do any attributes have unsaved changes?
51
+ # person.changed? # => false
52
+ # person.name = 'bob'
53
+ # person.changed? # => true
54
+ def changed?
55
+ !changed_attributes.empty?
56
+ end
57
+
58
+ # List of attributes with unsaved changes.
59
+ # person.changed # => []
60
+ # person.name = 'bob'
61
+ # person.changed # => ['name']
62
+ def changed
63
+ changed_attributes.keys
64
+ end
65
+
66
+ # Map of changed attrs => [original value, new value].
67
+ # person.changes # => {}
68
+ # person.name = 'bob'
69
+ # person.changes # => { 'name' => ['bill', 'bob'] }
70
+ def changes
71
+ changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
72
+ end
73
+
74
+ # Attempts to +save+ the record and clears changed attributes if successful.
75
+ def save_with_dirty(*args) #:nodoc:
76
+ if status = save_without_dirty(*args)
77
+ changed_attributes.clear
78
+ end
79
+ status
80
+ end
81
+
82
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
83
+ def save_with_dirty!(*args) #:nodoc:
84
+ status = save_without_dirty!(*args)
85
+ changed_attributes.clear
86
+ status
87
+ end
88
+
89
+ # <tt>reload</tt> the record and clears changed attributes.
90
+ def reload_with_dirty(*args) #:nodoc:
91
+ record = reload_without_dirty(*args)
92
+ changed_attributes.clear
93
+ record
94
+ end
95
+
96
+ private
97
+ # Map of change <tt>attr => original value</tt>.
98
+ def changed_attributes
99
+ @changed_attributes ||= {}
100
+ end
101
+
102
+ # Handle <tt>*_changed?</tt> for +method_missing+.
103
+ def attribute_changed?(attr)
104
+ changed_attributes.include?(attr)
105
+ end
106
+
107
+ # Handle <tt>*_change</tt> for +method_missing+.
108
+ def attribute_change(attr)
109
+ [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
110
+ end
111
+
112
+ # Handle <tt>*_was</tt> for +method_missing+.
113
+ def attribute_was(attr)
114
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
115
+ end
116
+
117
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
118
+ def attribute_will_change!(attr)
119
+ changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
120
+ end
121
+
122
+ def clone_attribute_value(method, attr)
123
+ val = __send__(method, attr)
124
+ val.dup unless val.nil?
125
+ end
126
+
127
+ # Wrap write_attribute to remember original attribute value.
128
+ def write_attribute_with_dirty(attr, value)
129
+ # The attribute already has an unsaved change.
130
+ if changed_attributes.include?(attr)
131
+ old = changed_attributes[attr]
132
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
133
+ else
134
+ old = clone_attribute_value(:read_attribute, attr)
135
+ changed_attributes[attr] = old if field_changed?(attr, old, value)
136
+ end
137
+
138
+ # Carry on.
139
+ write_attribute_without_dirty(attr, value)
140
+ end
141
+
142
+ def update_with_dirty
143
+ if partial_updates?
144
+ # Serialized attributes should always be written in case they've been
145
+ # changed in place.
146
+ update_without_dirty(changed | self.class.serialized_attributes.keys)
147
+ else
148
+ update_without_dirty
149
+ end
150
+ end
151
+
152
+ def field_changed?(attr, old, value)
153
+ old != value
154
+ end
155
+
156
+ module ClassMethods
157
+ def self.extended(base)
158
+ base.metaclass.alias_method_chain(:alias_attribute, :dirty)
159
+ end
160
+
161
+ def alias_attribute_with_dirty(new_name, old_name)
162
+ alias_attribute_without_dirty(new_name, old_name)
163
+ DIRTY_SUFFIXES.each do |suffix|
164
+ module_eval <<-STR, __FILE__, __LINE__+1
165
+ def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
166
+ STR
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,203 @@
1
+ module Content
2
+ module ItemFinderClassMethods
3
+ # Find operates with four different retrieval approaches:
4
+ #
5
+ # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
6
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
7
+ # * Find first - This will return the first record matched by the options used. These options can either be specific
8
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
9
+ # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
10
+ # * Find last - This will return the last record matched by the options used. These options can either be specific
11
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
12
+ # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
13
+ # * Find all - This will return all the records matched by the options used.
14
+ # If no records are found, an empty array is returned. Use
15
+ # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
16
+ #
17
+ # All approaches accept an options hash as their last parameter.
18
+ #
19
+ # ==== Parameters
20
+ #
21
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
22
+ # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
23
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
24
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
25
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
26
+ # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
27
+ #
28
+ # ==== Examples
29
+ #
30
+ # # find by id
31
+ # Person.find(1) # returns the object for ID = 1
32
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
33
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
34
+ # Person.find([1]) # returns an array for the object with ID = 1
35
+ # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
36
+ #
37
+ # Note that returned records may not be in the same order as the ids you
38
+ # provide since database rows are unordered. Give an explicit <tt>:order</tt>
39
+ # to ensure the results are sorted.
40
+ #
41
+ # ==== Examples
42
+ #
43
+ # # find first
44
+ # Person.find(:first) # returns the first object fetched by SELECT * FROM people
45
+ # Person.find(:first, :conditions => [ "user_name = ?", user_name])
46
+ # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
47
+ # Person.find(:first, :order => "created_on DESC", :offset => 5)
48
+ #
49
+ # # find last
50
+ # Person.find(:last) # returns the last object fetched by SELECT * FROM people
51
+ # Person.find(:last, :conditions => [ "user_name = ?", user_name])
52
+ # Person.find(:last, :order => "created_on DESC", :offset => 5)
53
+ #
54
+ # # find all
55
+ # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
56
+ # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
57
+ # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
58
+ # Person.find(:all, :offset => 10, :limit => 10)
59
+ # Person.find(:all, :include => [ :account, :friends ])
60
+ # Person.find(:all, :group => "category")
61
+ #
62
+ def find(*args)
63
+ options = {}
64
+ which = args.shift
65
+ if which.is_a? Symbol
66
+ given_options = args.shift
67
+ options.merge!(given_options) unless given_options.nil?
68
+ else
69
+ id = which
70
+ which = :first
71
+ given_options = args.shift
72
+ options.merge!(given_options) unless given_options.nil?
73
+ end
74
+
75
+ options[:limit] = 1 if which == :first
76
+
77
+ if id.nil?
78
+ wrap_result which, connection.run_query(self, options)
79
+ elsif id.is_a? Array
80
+ id.collect {|one_id| find_by_id one_id}.compact
81
+ elsif options.keys.length == 0
82
+ find_by_id id
83
+ else
84
+ if options.has_key? :conditions
85
+ options[:conditions].merge!(:__id => id)
86
+ else
87
+ options[:conditions] = {:__id => id}
88
+ end
89
+ wrap_result which, connection.run_query(self, options)
90
+ end
91
+ end
92
+
93
+ def count(options)
94
+ connection.count(self, options)
95
+ end
96
+
97
+ def paginate(*args)
98
+ options = args.first.dup
99
+ options[:conditions] = (options[:conditions] || {}).merge(:content_type => name)
100
+ options[:limit] = (options[:per_page] || 25)
101
+ options[:offset] = ((options[:page] || 1) - 1) * options[:limit]
102
+ options.delete :page
103
+ options.delete :per_page
104
+ find(:all, options)
105
+ end
106
+
107
+ def find_by_id(id)
108
+ wrap_result :first, connection.get_record_by_id(self, id.to_i)
109
+ end
110
+
111
+ def find_each(options)
112
+ #TODO - run the query
113
+ if block_given?
114
+ #each ... yield(row)
115
+ end
116
+ end
117
+
118
+ def all
119
+ if name == "Content::Item"
120
+ find :all
121
+ else
122
+ find_all_by_content_type name
123
+ end
124
+ end
125
+
126
+ def first
127
+ if name == "Content::Item"
128
+ find :first
129
+ else
130
+ find_by_content_type name
131
+ end
132
+ end
133
+
134
+ def last
135
+ all.last
136
+ end
137
+
138
+ def method_missing(name, *arguments, &block)
139
+ name_s = name.to_s
140
+ if name_s =~ /^find_all(_by)?_(.+)$/
141
+ obj = polymorphic_finder :all, $2, arguments
142
+ elsif name_s =~ /^find_by_(.+)$/
143
+ obj = polymorphic_finder :first, $1, arguments
144
+ elsif name_s =~ /^find_last(_by)?_(.+)$/
145
+ obj = polymorphic_finder :last, $2, arguments
146
+ elsif name_s =~ /^find_or_create_by_(.+)$/
147
+ obj = polymorphic_finder :first, $1, arguments
148
+ if obj.nil?
149
+ self.create(arguments, &block)
150
+ end
151
+ elsif name_s =~ /^paginate_by_(.+)$/
152
+ obj = polymorphic_pager $1, arguments
153
+ else
154
+ obj = super
155
+ end
156
+ yield(obj) if !obj.nil? and block_given?
157
+ obj
158
+ end
159
+
160
+ protected
161
+ def hash_zip(keys, values, default=nil, &block)
162
+ hash = block_given? ? Hash.new(&block) : Hash.new(default)
163
+ keys.zip(values) { |k,v| hash[k]=v }
164
+ hash
165
+ end
166
+
167
+ def polymorphic_finder(which, name, arguments)
168
+ find which, :conditions => hash_zip(name.split(/_and_/), arguments)
169
+ end
170
+
171
+ def polymorphic_pager(name, arguments)
172
+ names = name.split(/_and_/)
173
+ options = arguments.length > names.length ? arguments.last : {}
174
+ if options.has_key? :conditions
175
+ options[:conditions].merge! hash_zip(names, arguments)
176
+ else
177
+ options.merge!({:conditions => hash_zip(names, arguments)})
178
+ end
179
+ paginate options
180
+ end
181
+
182
+ def create_item(attrs)
183
+ if attrs.nil?
184
+ nil
185
+ elsif !attrs[:content_type].nil?
186
+ attrs[:content_type].to_s.camelize.constantize.new(attrs)
187
+ elsif self == Content::Item
188
+ Content::Item.new(attrs)
189
+ end
190
+ end
191
+
192
+ def wrap_result(which, attrs)
193
+ unless attrs.nil?
194
+ attrs = [attrs] unless attrs.is_a?(Array)
195
+ case which
196
+ when :first then create_item attrs.first
197
+ when :last then create_item attrs.last
198
+ else attrs.collect { |item| create_item(item) }.compact
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,105 @@
1
+ module Content
2
+ module Manager
3
+ def self.load_content_item(params)
4
+ url = params["content_item_url"]
5
+ unless url.nil?
6
+ if url.is_a? Array
7
+ url = "/#{url.join('/')}"
8
+ end
9
+ if url.match(/^(.+)\.([a-z]+)$/i)
10
+ params[:format] = $2
11
+ url = $1
12
+ end
13
+ params["content_item_url"] = url
14
+ Content::Item.find_by_url(url)
15
+ end
16
+ end
17
+
18
+ def show
19
+ render404 and return if current_content_item.nil? or current_content_item.template.nil? or current_content_item.template.sublayout.nil?
20
+ respond_to do |format|
21
+ format.html { prerender_containers and render :template => "sublayouts/#{current_content_item.template.sublayout}", :layout => false }
22
+ format.xml { render :xml => current_content_item }
23
+ end
24
+ end
25
+
26
+ protected
27
+ def prerender_containers
28
+ begin
29
+ sublayout = Content::Sublayout.find_by_path(current_content_item.template.sublayout)
30
+ sublayout.containers.each {|name| content_for name, render_container(name) }
31
+ rescue RuntimeError => err
32
+ render500(err) and return false
33
+ end
34
+ true
35
+ end
36
+
37
+ def content_for(name, content)
38
+ name = "layout" if name.to_s == "contents"
39
+ ivar = "@content_for_#{name}"
40
+ instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}")
41
+ end
42
+
43
+ #
44
+ # Renders the given component
45
+ #
46
+ # url_options the options that would be passed to url_for
47
+ #
48
+ def render_component(url_options)
49
+ url = url_for(url_options)
50
+ querystring = URI.parse(url).query
51
+
52
+ env = {
53
+ "rack.version" => [0, 1],
54
+ "rack.input" => StringIO.new(""),
55
+ "rack.errors" => $stderr,
56
+ "rack.url_scheme" => "http",
57
+ "rack.run_once" => false,
58
+ "rack.multithread" => false,
59
+ "rack.multiprocess" => false,
60
+ "QUERY_STRING" => querystring,
61
+ "REQUEST_METHOD" => "GET",
62
+ "PATH_INFO" => url,
63
+ "REQUEST_PATH" => url,
64
+ "REQUEST_URI" => url
65
+ }
66
+
67
+ %w(SERVER_SOFTWARE HTTP_USER_AGENT HTTP_ACCEPT_ENCODING HTTP_ACCEPT_CHARSET
68
+ HTTP_ACCEPT_LANGUAGE HTTP_KEEP_ALIVE HTTP_COOKIE HTTP_VERSION SERVER_PROTOCOL HTTP_HOST
69
+ SERVER_NAME SERVER_PORT REMOTE_ADDR SCRIPT_NAME).each { |key| env[key] = request.env[key] }
70
+
71
+ resp = ActionController::Routing::Routes.call(env)
72
+ raise resp[2].body unless resp[0] == 200
73
+ resp[2].body
74
+ end
75
+
76
+ #
77
+ # Renders a container
78
+ #
79
+ # name the name of the container to render
80
+ #
81
+ def render_container(name)
82
+ unless current_content_item.nil? or current_content_item.template.nil? or current_content_item.template[name].nil?
83
+ current_content_item.template.get_container(name).collect {|component|
84
+ render_component(:controller => "components/#{component.keys.first.to_s.pluralize}", :action => "show", :id => component.values.first)
85
+ }
86
+ end
87
+ end
88
+
89
+ def render404
90
+ respond_to do |format|
91
+ format.html { render :template => "errors/error_404", :status => 404 }
92
+ format.all { render :nothing => true, :status => 404 }
93
+ end
94
+ true
95
+ end
96
+
97
+ def render500(err)
98
+ respond_to do |format|
99
+ format.html { render :text => err, :layout => false, :status => 500 }
100
+ format.all { render :nothing => true, :status => 500 }
101
+ end
102
+ true
103
+ end
104
+ end
105
+ end