sethyates-content_manager 0.4.0 → 1.0.0

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