sferik-merb-admin 0.2.7 → 0.2.8

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.
data/README.markdown CHANGED
@@ -14,8 +14,8 @@ At the command prompt, type:
14
14
 
15
15
  In your app, add the following dependency to `config/dependencies.rb`:
16
16
 
17
- dependency "sferik-merb-admin", "0.2.7", :require_as => "merb-admin"
18
- dependency "dm-is-paginated", "0.0.1" # required for pagination
17
+ dependency "sferik-merb-admin", "0.2.8", :require_as => "merb-admin"
18
+ dependency "dm-is-paginated", "0.0.1" # if you want pagination support
19
19
 
20
20
  Add the following route to `config/router.rb`:
21
21
 
@@ -51,6 +51,11 @@ MerbAdmin does not implement any authorization scheme. Make sure to apply author
51
51
 
52
52
  ## Acknowledgements
53
53
 
54
- Many thanks to [Wilson Miner](http://www.wilsonminer.com) for contributing the stylesheets and javascripts from [Django](http://www.djangoproject.com).
54
+ Many thanks to:
55
+
56
+ * [Wilson Miner](http://www.wilsonminer.com) for contributing the stylesheets and javascripts from [Django](http://www.djangoproject.com)
57
+ * [Lori Holden](http://loriholden.com/) for providing pagination via [dm-is-paginated](http://github.com/lholden/dm-is-paginated)
58
+ * [Aaron Wheeler](http://fightinjoe.com/) for contributing libraries from [Merb AutoScaffold](http://github.com/fightinjoe/merb-autoscaffold)
59
+ * [why the lucky stiff](http://whytheluckystiff.net/) for [metaid](http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html)
55
60
 
56
61
  Also, thanks to [beer](http://www.anchorbrewing.com).
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ AUTHOR = "Erik Michaels-Ober"
12
12
  EMAIL = "sferik@gmail.com"
13
13
  HOMEPAGE = "http://twitter.com/sferik"
14
14
  SUMMARY = "MerbAdmin is a Merb plugin that provides an easy-to-use interface for managing your data."
15
- GEM_VERSION = "0.2.7"
15
+ GEM_VERSION = "0.2.8"
16
16
 
17
17
  spec = Gem::Specification.new do |s|
18
18
  s.rubyforge_project = "merb"
@@ -1,3 +1,6 @@
1
+ require File.join( File.dirname(__FILE__), '..', '..', 'lib', 'abstract_model' )
2
+ require File.join( File.dirname(__FILE__), '..', '..', 'lib', 'metaid' )
3
+
1
4
  class MerbAdmin::Main < MerbAdmin::Application
2
5
 
3
6
  before :find_models, :only => ['index']
@@ -10,54 +13,36 @@ class MerbAdmin::Main < MerbAdmin::Application
10
13
 
11
14
  def list
12
15
  options = {}
13
- filters = params[:filter] || {}
14
- filters.each_pair do |key, value|
15
- if @model.properties[key].primitive.to_s == "TrueClass"
16
- options.merge!(key.to_sym => (value == "true"))
17
- elsif @model.properties[key].primitive.to_s == "Integer" && @model.properties[key].type.respond_to?(:flag_map)
18
- options.merge!(key.to_sym => value.to_sym)
19
- end
20
- end
21
- if params[:query]
22
- condition_statement = []
23
- conditions = []
24
- @properties.each do |property|
25
- next unless property.type.to_s == "String"
26
- condition_statement << "#{property.field} LIKE ?"
27
- conditions << "%#{params[:query]}%"
28
- end
29
- conditions.unshift(condition_statement.join(" OR "))
30
- options.merge!(:conditions => conditions) unless conditions == [""]
31
- end
32
- if params[:sort]
33
- order = "[:#{params[:sort]}.#{params[:sort_reverse] ? 'desc' : 'asc'}]"
34
- options.merge!(:order => eval(order))
35
- end
16
+ merge_filter(options)
17
+ merge_query(options)
18
+ merge_sort(options)
19
+
36
20
  if !MerbAdmin[:paginate] || params[:all]
37
21
  options = {
38
22
  :limit => 200,
39
23
  }.merge(options)
40
- @objects = @model.all(options).reverse
24
+ @objects = @abstract_model.find_all(options).reverse
41
25
  else
42
26
  # monkey patch pagination
43
- @model.class_eval("is_paginated") unless @model.respond_to?(:paginated)
27
+ @abstract_model.model.class_eval("is_paginated") unless @abstract_model.model.respond_to?(:paginated)
44
28
  @current_page = (params[:page] || 1).to_i
45
29
  options = {
46
30
  :page => @current_page,
47
31
  :per_page => MerbAdmin[:per_page],
48
32
  }.merge(options)
49
- @page_count, @objects = @model.paginated(options)
33
+ @page_count, @objects = @abstract_model.model.paginated(options)
34
+ options.delete(:page)
35
+ options.delete(:per_page)
36
+ options.delete(:offset)
37
+ options.delete(:limit)
50
38
  end
51
- options.delete(:page)
52
- options.delete(:per_page)
53
- options.delete(:offset)
54
- options.delete(:limit)
55
- @record_count = @model.count(options)
39
+
40
+ @record_count = @abstract_model.count(options)
56
41
  render(:layout => "list")
57
42
  end
58
43
 
59
44
  def new
60
- @object = @model.new
45
+ @object = @abstract_model.new
61
46
  render(:layout => "form")
62
47
  end
63
48
 
@@ -66,34 +51,44 @@ class MerbAdmin::Main < MerbAdmin::Application
66
51
  end
67
52
 
68
53
  def create
69
- object = eval("params[:#{@model_name.snake_case}]") || {}
70
- @object = @model.new(object)
71
- if @object.save
54
+ object = params[@abstract_model.singular_name] || {}
55
+ # Delete fields that are blank
56
+ object.each do |key, value|
57
+ object.delete(key) if value.blank?
58
+ end
59
+ associations = @abstract_model.has_many_associations.map{|association| [association, (params[:associations] || {}).delete(association[:name])]}
60
+ @object = @abstract_model.new(object)
61
+ if @object.save && associations.each{|association, ids| update_has_many_association(association, ids)}
72
62
  if params[:_continue]
73
- redirect(slice_url(:admin_edit, :model_name => @model_name.snake_case, :id => @object.id), :message => {:notice => "#{@model_name} was successfully created"})
63
+ redirect(slice_url(:admin_edit, :model_name => @abstract_model.singular_name, :id => @object.id), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully created"})
74
64
  elsif params[:_add_another]
75
- redirect(slice_url(:admin_new, :model_name => @model_name.snake_case), :message => {:notice => "#{@model_name} was successfully created"})
65
+ redirect(slice_url(:admin_new, :model_name => @abstract_model.singular_name), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully created"})
76
66
  else
77
- redirect(slice_url(:admin_list, :model_name => @model_name.snake_case), :message => {:notice => "#{@model_name} was successfully created"})
67
+ redirect(slice_url(:admin_list, :model_name => @abstract_model.singular_name), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully created"})
78
68
  end
79
69
  else
80
- message[:error] = "#{@model_name} failed to be created"
70
+ message[:error] = "#{@abstract_model.pretty_name.capitalize} failed to be created"
81
71
  render(:new, :layout => "form")
82
72
  end
83
73
  end
84
74
 
85
75
  def update
86
- object = eval("params[:#{@model_name.snake_case}]") || {}
87
- if @object.update_attributes(object)
76
+ object = params[@abstract_model.singular_name] || {}
77
+ # Delete fields that are blank
78
+ object.each do |key, value|
79
+ object.delete(key) if value.blank?
80
+ end
81
+ associations = @abstract_model.has_many_associations.map{|association| [association, (params[:associations] || {}).delete(association[:name])]}
82
+ if @object.update_attributes(object) && associations.each{|association, ids| update_has_many_association(association, ids)}
88
83
  if params[:_continue]
89
- redirect(slice_url(:admin_edit, :model_name => @model_name.snake_case, :id => @object.id), :message => {:notice => "#{@model_name} was successfully updated"})
84
+ redirect(slice_url(:admin_edit, :model_name => @abstract_model.singular_name, :id => @object.id), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully updated"})
90
85
  elsif params[:_add_another]
91
- redirect(slice_url(:admin_new, :model_name => @model_name.snake_case), :message => {:notice => "#{@model_name} was successfully updated"})
86
+ redirect(slice_url(:admin_new, :model_name => @abstract_model.singular_name), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully updated"})
92
87
  else
93
- redirect(slice_url(:admin_list, :model_name => @model_name.snake_case), :message => {:notice => "#{@model_name} was successfully updated"})
88
+ redirect(slice_url(:admin_list, :model_name => @abstract_model.singular_name), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully updated"})
94
89
  end
95
90
  else
96
- message[:error] = "#{@model_name} failed to be updated"
91
+ message[:error] = "#{@abstract_model.pretty_name.capitalize} failed to be updated"
97
92
  render(:edit, :layout => "form")
98
93
  end
99
94
  end
@@ -104,7 +99,7 @@ class MerbAdmin::Main < MerbAdmin::Application
104
99
 
105
100
  def destroy
106
101
  if @object.destroy
107
- redirect(slice_url(:admin_list, :model_name => @model_name.snake_case), :message => {:notice => "#{@model_name} was successfully destroyed"})
102
+ redirect(slice_url(:admin_list, :model_name => @abstract_model.singular_name), :message => {:notice => "#{@abstract_model.pretty_name.capitalize} was successfully destroyed"})
108
103
  else
109
104
  raise BadRequest
110
105
  end
@@ -113,28 +108,67 @@ class MerbAdmin::Main < MerbAdmin::Application
113
108
  private
114
109
 
115
110
  def find_models
116
- @models = DataMapper::Resource.descendants.to_a.sort{|a, b| a.to_s <=> b.to_s}
117
- # remove DataMapperSessionStore because it's included by default
118
- @models -= [Merb::DataMapperSessionStore] if Merb.const_defined?(:DataMapperSessionStore)
111
+ @abstract_models = MerbAdmin::AbstractModel.all
119
112
  end
120
113
 
121
114
  def find_model
122
- @model_name = params[:model_name].camel_case.singularize
123
- begin
124
- @model = eval(@model_name)
125
- rescue StandardError
126
- raise NotFound
127
- end
115
+ model_name = params[:model_name].camel_case
116
+ @abstract_model = MerbAdmin::AbstractModel.new(model_name)
128
117
  find_properties
129
118
  end
130
119
 
131
120
  def find_properties
132
- @properties = @model.properties.to_a
121
+ @properties = @abstract_model.properties
133
122
  end
134
123
 
135
124
  def find_object
136
- @object = @model.get(params[:id])
125
+ @object = @abstract_model.find(params[:id])
137
126
  raise NotFound unless @object
138
127
  end
139
128
 
129
+ def merge_filter(options)
130
+ return unless params[:filter]
131
+ params[:filter].each_pair do |key, value|
132
+ @properties.each do |property|
133
+ next unless property[:name] == key.to_sym
134
+ if property[:type] == :boolean
135
+ options.merge!(key.to_sym => (value == "true"))
136
+ elsif property[:type] == :integer && property[:flag_map]
137
+ options.merge!(key.to_sym => value.to_sym)
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ def merge_query(options)
144
+ return unless params[:query]
145
+ condition_statement = []
146
+ conditions = []
147
+ @properties.each do |property|
148
+ next unless property[:type] == :string
149
+ condition_statement << "#{property[:field]} LIKE ?"
150
+ conditions << "%#{params[:query]}%"
151
+ end
152
+ conditions.unshift(condition_statement.join(" OR "))
153
+ options.merge!(:conditions => conditions) unless conditions == [""]
154
+ end
155
+
156
+ def merge_sort(options)
157
+ return unless params[:sort]
158
+ options.merge!(:order => [params[:sort].to_sym.send(params[:sort_reverse] ? :desc : :asc)])
159
+ end
160
+
161
+ def update_has_many_association(association, ids)
162
+ # Remove all of the associated items
163
+ relationship = @object.send(association[:name])
164
+ @object.clear_association(relationship)
165
+ # Add all of the objects to the relationship
166
+ conditions = {association[:parent_key].first => ids}
167
+ model = MerbAdmin::AbstractModel.new(association[:child_model])
168
+ for object in model.find_all(conditions)
169
+ relationship << object
170
+ end
171
+ @object.save
172
+ end
173
+
140
174
  end
@@ -2,6 +2,9 @@ require 'builder'
2
2
  module Merb
3
3
  module MerbAdmin
4
4
  module MainHelper
5
+ def object_title(object)
6
+ object.try(:name) || object.try(:title) || "#{object.class.to_s} ##{object.id}"
7
+ end
5
8
 
6
9
  # Given a page count and the current page, we generate a set of pagination
7
10
  # links.
@@ -1,6 +1,6 @@
1
1
  <%
2
2
  slice_name = "MerbAdmin" + (MerbAdmin[:app_name].blank? ? "" : " for #{MerbAdmin[:app_name]}")
3
- page_name = action_name.capitalize + " " + @model_name.snake_case.gsub('_', ' ')
3
+ page_name = action_name.capitalize + " " + @abstract_model.pretty_name
4
4
  %>
5
5
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
6
6
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
@@ -35,7 +35,7 @@
35
35
  </div>
36
36
  <div class="breadcrumbs">
37
37
  <%= link_to("Home", url(:admin_dashboard)) %> &rsaquo;
38
- <%= link_to(@model_name.pluralize, url(:admin_list, :model_name => @model_name.snake_case)) %> &rsaquo;
38
+ <%= link_to(@abstract_model.plural_name.to_s.capitalize, url(:admin_list, :model_name => @abstract_model.singular_name)) %> &rsaquo;
39
39
  <%= page_name %>
40
40
  </div>
41
41
  <%= partial('layout/message', :message => message) unless message.blank? -%>
@@ -1,6 +1,6 @@
1
1
  <%
2
2
  slice_name = "MerbAdmin" + (MerbAdmin[:app_name].blank? ? "" : " for #{MerbAdmin[:app_name]}")
3
- page_name = "Select " + @model_name.snake_case.gsub('_', ' ') + " to edit"
3
+ page_name = "Select " + @abstract_model.pretty_name + " to edit"
4
4
  %>
5
5
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
6
6
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
@@ -30,7 +30,7 @@
30
30
  </div>
31
31
  <div class="breadcrumbs">
32
32
  <%= link_to("Home", url(:admin_dashboard)) %> &rsaquo;
33
- <%=h @model_name.pluralize %>
33
+ <%= @abstract_model.plural_name.to_s.capitalize %>
34
34
  </div>
35
35
  <%= partial('layout/message', :message => message) unless message.blank? -%>
36
36
  <div id="content" class="flex">
@@ -1,6 +1,6 @@
1
1
  <div>
2
- <%= text_field(property.name, :maxlength => property.length, :label => property.field.capitalize.gsub('_', ' ')) %>
2
+ <%= text_field(property[:name], :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
3
3
  <p class="help">
4
- <%= !property.nullable? || property.serial? ? "Required." : "Optional." %>
4
+ <%= !property[:nullable?] || property[:serial?] ? "Required." : "Optional." %>
5
5
  </p>
6
6
  </div>
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <%= check_box(property[:name], :label => property[:pretty_name].capitalize) %>
3
+ </div>
@@ -1,7 +1,7 @@
1
- <% value = eval("@object.#{property.field}") %>
1
+ <% value = @object.send(property[:name]) %>
2
2
  <div>
3
- <%= text_field(property.name, :class => "vDateField", :label => property.field.capitalize.gsub('_', ' '), :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : nil) %>
3
+ <%= text_field(property[:name], :class => "vDateField", :label => property[:pretty_name].capitalize, :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : nil) %>
4
4
  <p class="help">
5
- <%= !property.nullable? ? "Required." : "Optional." %>
5
+ <%= !property[:nullable?] ? "Required." : "Optional." %>
6
6
  </p>
7
7
  </div>
@@ -1,7 +1,7 @@
1
- <% value = eval("@object.#{property.field}") %>
1
+ <% value = @object.send(property[:name]) %>
2
2
  <div>
3
- <%= text_field(property.name, :class => "vDateField", :label => property.field.capitalize.gsub('_', ' '), :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil) %>
3
+ <%= text_field(property[:name], :class => "vDateField", :label => property[:pretty_name].capitalize, :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil) %>
4
4
  <p class="help">
5
- <%= !property.nullable? ? "Required." : "Optional." %>
5
+ <%= !property[:nullable?] ? "Required." : "Optional." %>
6
6
  </p>
7
7
  </div>
@@ -1,6 +1,6 @@
1
1
  <div>
2
- <%= text_field(property.name, :maxlength => property.length, :label => property.field.capitalize.gsub('_', ' ')) %>
2
+ <%= text_field(property[:name], :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
3
3
  <p class="help">
4
- <%= !property.nullable? || property.serial? ? "Required." : "Optional." %>
4
+ <%= !property[:nullable?] || property[:serial?] ? "Required." : "Optional." %>
5
5
  </p>
6
6
  </div>
@@ -0,0 +1,10 @@
1
+ <fieldset class="module aligned">
2
+ <h2><%= association[:pretty_name].capitalize %></h2>
3
+ <div class="form-row">
4
+ <div>
5
+ <%= select(:name => "associations[#{association[:name]}][]", :id => association[:name], :collection => MerbAdmin::AbstractModel.new(association[:child_model]).find_all.map{|o| [o.id, object_title(o)]}.sort_by{|o| o[1]}, :selected => @object.send(association[:name]).map{|o| o.id.to_s}, :label => association[:pretty_name].capitalize, :multiple => true) %>
6
+ <script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("<%= association[:name] %>", "<%= association[:name] %>", 0, "<%= image_path %>"); });</script>
7
+ <p class="help">Hold down "Control", or "Command" on a Mac, to select more than one.</p>
8
+ </div>
9
+ </div>
10
+ </fieldset>
@@ -1,10 +1,10 @@
1
1
  <div>
2
- <% if property.type.respond_to?(:flag_map) #Enum or Flag type %>
3
- <%= select(property.name, :collection => property.type.flag_map.map{|x| [x[1], x[1].to_s.capitalize.gsub('_', ' ')]}.sort{|a, b| a[1] <=> b[1]}, :label => property.field.capitalize.gsub('_', ' ')) %>
2
+ <% if property[:flag_map] # Enum or Flag type %>
3
+ <%= select(property[:name], :collection => property[:flag_map].map{|x| [x[1], x[1].to_s.capitalize.gsub('_', ' ')]}.sort{|a, b| a[1] <=> b[1]}, :label => property[:pretty_name].capitalize) %>
4
4
  <% else %>
5
- <%= text_field(property.name, :maxlength => property.length, :label => property.field.capitalize.gsub('_', ' ')) %>
6
- <% end %>
5
+ <%= text_field(property[:name], :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
7
6
  <p class="help">
8
- <%= !property.nullable? || property.serial? ? "Required." : "Optional." %>
7
+ <%= !property[:nullable?] || property[:serial?] ? "Required." : "Optional." %>
9
8
  </p>
9
+ <% end %>
10
10
  </div>
@@ -1,14 +1,6 @@
1
1
  <div>
2
- <% case property.type.to_s %>
3
- <% when "DataMapper::Types::Text" %>
4
- <%= text_area(property.name, :cols => 80, :label => property.field.capitalize.gsub('_', ' ')) %>
5
- <p class="help">
6
- <%= property.nullable? ? "Required." : "Optional." %>
7
- </p>
8
- <% else %>
9
- <%= text_field(property.name, :size => [50, property.length].min, :maxlength => property.length, :label => property.field.capitalize.gsub('_', ' ')) %>
10
- <p class="help">
11
- <%= !property.nullable? ? "Required." : "Optional." %> <%= property.length %> <%= property.length == 1 ? "character." : "characters or fewer." %>
12
- </p>
13
- <% end %>
2
+ <%= text_field(property[:name], :size => [50, property[:length]].min, :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
3
+ <p class="help">
4
+ <%= !property[:nullable?] ? "Required." : "Optional." %> <%= property[:length] %> <%= property[:length] == 1 ? "character." : "characters or fewer." %>
5
+ </p>
14
6
  </div>
@@ -0,0 +1,6 @@
1
+ <div>
2
+ <%= text_area(property[:name], :cols => 80, :label => property[:pretty_name].capitalize) %>
3
+ <p class="help">
4
+ <%= property[:nullable?] ? "Required." : "Optional." %>
5
+ </p>
6
+ </div>
@@ -1,7 +1,7 @@
1
- <% value = eval("@object.#{property.field}") %>
1
+ <% value = @object.send(property[:name]) %>
2
2
  <div>
3
- <%= text_field(property.name, :class => "vTimeField", :label => property.field.capitalize.gsub('_', ' '), :value => value.respond_to?(:strftime) ? value.strftime("%H:%M:%S") : nil) %>
3
+ <%= text_field(property[:name], :class => "vTimeField", :label => property[:pretty_name].capitalize, :value => value.respond_to?(:strftime) ? value.strftime("%H:%M:%S") : nil) %>
4
4
  <p class="help">
5
- <%= !property.nullable? ? "Required." : "Optional." %>
5
+ <%= !property[:nullable?] ? "Required." : "Optional." %>
6
6
  </p>
7
7
  </div>
@@ -1,10 +1,10 @@
1
- <p>Are you sure you want to delete the <%= @model_name.snake_case.gsub('_', ' ') %>? All of the following related items will be deleted:</p>
1
+ <p>Are you sure you want to delete the <%= @abstract_model.pretty_name %>? All of the following related items will be deleted:</p>
2
2
  <ul>
3
3
  <li>
4
- <%= link_to(@model_name, slice_url(:admin_edit, :model_name => @model_name.snake_case, :id => @object.id))%>
4
+ <%= link_to(@model_name, slice_url(:admin_edit, :model_name => @abstract_model.singular_name, :id => @object.id))%>
5
5
  </li>
6
6
  </ul>
7
- <%= form_for(@object, :action => slice_url(:admin_destroy, :model_name => @model_name.snake_case, :id => @object.id), :method => :delete) do %>
7
+ <%= form_for(@object, :action => slice_url(:admin_destroy, :model_name => @abstract_model.singular_name, :id => @object.id), :method => :delete) do %>
8
8
  <div>
9
9
  <%= submit "Yes, I'm sure" %>
10
10
  </div>
@@ -1,30 +1,33 @@
1
1
  <div id="content-main">
2
2
  <ul class="object-tools">
3
3
  <li>
4
- <%= link_to("View on site", "/#{@model_name.snake_case}/#{@object.id}", :target => "_blank", :class => "viewsitelink") %>
4
+ <%= link_to("View on site", "/#{@abstract_model.singular_name}/#{@object.id}", :target => "_blank", :class => "viewsitelink") %>
5
5
  </li>
6
6
  </ul>
7
- <%= form_for(@object, :action => slice_url(:admin_update, :model_name => @model_name.snake_case, :id => @object.id)) do %>
7
+ <%= form_for(@object, :action => slice_url(:admin_update, :model_name => @abstract_model.singular_name, :id => @object.id)) do %>
8
8
  <div>
9
9
  <fieldset class="module aligned">
10
10
  <% @properties.each do |property| %>
11
- <% next if [:id, :created_at, :created_on, :updated_at, :updated_on].include?(property.name) %>
12
- <div class="<%= @object.errors[property.name] ? "form-row errors" : "form-row"%>">
13
- <% if @object.errors[property.name] %>
11
+ <% next if [:id, :created_at, :created_on, :updated_at, :updated_on].include?(property[:name]) %>
12
+ <div class="<%= @object.errors[property[:name]] ? "form-row errors" : "form-row"%>">
13
+ <% if @object.errors[property[:name]] %>
14
14
  <ul class="errorlist">
15
- <% @object.errors[property.name].each do |error| %>
15
+ <% @object.errors[property[:name]].each do |error| %>
16
16
  <li><%= error %></li>
17
17
  <% end %>
18
18
  </ul>
19
19
  <% end %>
20
- <%= partial(property.primitive.to_s.snake_case, :property => property) -%>
20
+ <%= partial(property[:type].to_s, :property => property) -%>
21
21
  </div>
22
22
  <% end %>
23
23
  </fieldset>
24
+ <% @abstract_model.has_many_associations.each do |association| %>
25
+ <%= partial('has_many', :association => association) %>
26
+ <% end %>
24
27
  <div class="submit-row" >
25
28
  <%= submit "Save", :class => "default", :name => "_save" %>
26
29
  <p class="deletelink-box">
27
- <%= link_to("Delete", slice_url(:admin_delete, :model_name => @model_name.snake_case, :id => @object.id), :class => "deletelink") %>
30
+ <%= link_to("Delete", slice_url(:admin_delete, :model_name => @abstract_model.singular_name, :id => @object.id), :class => "deletelink") %>
28
31
  </p>
29
32
  <%= submit "Save and add another", :name => "_add_another" %>
30
33
  <%= submit "Save and continue editing", :name => "_continue" %>
@@ -4,16 +4,16 @@
4
4
  <caption>
5
5
  <%= link_to("Models", slice_url(:admin_dashboard), :class => "section") %>
6
6
  </caption>
7
- <% @models.map{|m| m.to_s}.each do |model_name| %>
7
+ <% @abstract_models.each do |abstract_model| %>
8
8
  <tr>
9
9
  <th scope="row">
10
- <%= link_to(model_name.pluralize, slice_url(:admin_list, :model_name => model_name.snake_case)) %>
10
+ <%= link_to(abstract_model.plural_name.to_s.capitalize, slice_url(:admin_list, :model_name => abstract_model.singular_name)) %>
11
11
  </th>
12
12
  <td>
13
- <%= link_to("Add", slice_url(:admin_new, :model_name => model_name.snake_case), :class => "addlink") %>
13
+ <%= link_to("Add", slice_url(:admin_new, :model_name => abstract_model.singular_name), :class => "addlink") %>
14
14
  </td>
15
15
  <td>
16
- <%= link_to("Edit", slice_url(:admin_list, :model_name => model_name.snake_case), :class => "changelink") %>
16
+ <%= link_to("Edit", slice_url(:admin_list, :model_name => abstract_model.singular_name), :class => "changelink") %>
17
17
  </td>
18
18
  </tr>
19
19
  <% end %>
@@ -2,7 +2,7 @@
2
2
  <div id="content-main">
3
3
  <ul class="object-tools">
4
4
  <li>
5
- <%= link_to("Add #{@model_name.snake_case.gsub('_', ' ')}", slice_url(:admin_new, :model_name => @model_name.snake_case), :class => "addlink") %>
5
+ <%= link_to("Add #{@abstract_model.pretty_name}", slice_url(:admin_new, :model_name => @abstract_model.singular_name), :class => "addlink") %>
6
6
  </li>
7
7
  </ul>
8
8
  <div class="module filtered" id="changelist">
@@ -13,7 +13,7 @@
13
13
  <input type="text" size="40" name="query" value="" id="searchbar" />
14
14
  <input type="submit" value="Search" />
15
15
  <% if params[:query] || params[:filter] %>
16
- <span class="small quiet"><%= @record_count %> <%= @record_count == 1 ? "result" : "results" %> (<a href="?"><%= @model.count %> total</a>)</span>
16
+ <span class="small quiet"><%= @record_count %> <%= @record_count == 1 ? "result" : "results" %> (<a href="?"><%= @abstract_model.count %> total</a>)</span>
17
17
  <% end %>
18
18
  <% if params[:filter] %>
19
19
  <% params[:filter].each do |name, value| %>
@@ -27,28 +27,28 @@
27
27
  <div id="changelist-filter">
28
28
  <h2>Filter</h2>
29
29
  <% @properties.each do |property| %>
30
- <% if property.primitive.to_s == "TrueClass" %>
31
- <h3>By <%= property.field.gsub('_', ' ') %></h3>
30
+ <% if property[:type] == :boolean %>
31
+ <h3>By <%= property[:pretty_name] %></h3>
32
32
  <ul>
33
- <li class="<%= params[:filter].nil? || params[:filter][property.name].blank? ? "selected" : nil %>">
34
- <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).reject{|key, value| key.to_sym == property.name})) %>">All</a>
33
+ <li class="<%= params[:filter].nil? || params[:filter][property[:name]].blank? ? "selected" : nil %>">
34
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).reject{|key, value| key.to_sym == property[:name]})) %>">All</a>
35
35
  </li>
36
- <li class="<%= params[:filter] && params[:filter][property.name] == "true" ? "selected" : nil %>">
37
- <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property.name => "true"}))) %>">Yes</a>
36
+ <li class="<%= params[:filter] && params[:filter][property[:name]] == "true" ? "selected" : nil %>">
37
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property[:name] => "true"}))) %>">Yes</a>
38
38
  </li>
39
- <li class="<%= params[:filter] && params[:filter][property.name] == "false" ? "selected" : nil %>">
40
- <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property.name => "false"}))) %>">No</a>
39
+ <li class="<%= params[:filter] && params[:filter][property[:name]] == "false" ? "selected" : nil %>">
40
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property[:name] => "false"}))) %>">No</a>
41
41
  </li>
42
42
  </ul>
43
- <% elsif property.primitive.to_s == "Integer" && property.type.respond_to?(:flag_map) %>
44
- <h3>By <%= property.field.gsub('_', ' ') %></h3>
43
+ <% elsif property[:type] == :integer && property[:flag_map] %>
44
+ <h3>By <%= property[:pretty_name] %></h3>
45
45
  <ul>
46
- <li class="<%= params[:filter].nil? || params[:filter][property.name].blank? ? "selected" : nil %>">
47
- <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).reject{|key, value| key.to_sym == property.name})) %>">All</a>
46
+ <li class="<%= params[:filter].nil? || params[:filter][property[:name]].blank? ? "selected" : nil %>">
47
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).reject{|key, value| key.to_sym == property[:name]})) %>">All</a>
48
48
  </li>
49
- <% property.type.flag_map.each do |value, name| %>
50
- <li class="<%= params[:filter] && params[:filter][property.name] == name.to_s ? "selected" : nil %>">
51
- <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property.name => name}))) %>"><%= name.to_s.capitalize.gsub('_', ' ')%></a>
49
+ <% property[:flag_map].each do |value, name| %>
50
+ <li class="<%= params[:filter] && params[:filter][property[:name]] == name.to_s ? "selected" : nil %>">
51
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property[:name] => name}))) %>"><%= name.to_s.capitalize.gsub('_', ' ')%></a>
52
52
  </li>
53
53
  <% end %>
54
54
  </ul>
@@ -59,8 +59,8 @@
59
59
  <thead>
60
60
  <tr>
61
61
  <% @properties.each do |property| %>
62
- <th class="<%= params[:sort] == property.field ? params[:sort_reverse] ? 'sorted descending' : 'sorted ascending' : nil %>">
63
- <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:sort => property.field).reject{|key, value| key.to_sym == :sort_reverse}.merge(params[:sort] == property.field && params[:sort_reverse] != "true" ? {:sort_reverse => "true"} : {})) %>"><%= property.field.capitalize.gsub('_', ' ') %></a>
62
+ <th class="<%= params[:sort] == property[:field] ? params[:sort_reverse] ? 'sorted descending' : 'sorted ascending' : nil %>">
63
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:sort => property[:field]).reject{|key, value| key.to_sym == :sort_reverse}.merge(params[:sort] == property[:field] && params[:sort_reverse] != "true" ? {:sort_reverse => "true"} : {})) %>"><%= property[:pretty_name].capitalize %></a>
64
64
  </th>
65
65
  <% end %>
66
66
  </tr>
@@ -70,35 +70,29 @@
70
70
  <tr class="row<%= index % 2 == 0 ? '1' : '2' %>">
71
71
  <% @properties.each do |property| %>
72
72
  <td>
73
- <a href="<%= slice_url(:admin_edit, :model_name => @model_name.snake_case, :id => eval("object.id")) %>">
74
- <% case property.primitive.to_s %>
75
- <% when "TrueClass" %>
76
- <% if eval("object.#{property.field}") == true %>
73
+ <a href="<%= slice_url(:admin_edit, :model_name => @abstract_model.singular_name, :id => object.id) %>">
74
+ <% case property[:type] %>
75
+ <% when :boolean %>
76
+ <% if object.send(property[:name]) == true %>
77
77
  <img alt="True" src="<%= image_path("icon-yes.gif") %>"/>
78
78
  <% else %>
79
79
  <img alt="False" src="<%= image_path("icon-no.gif") %>"/>
80
80
  <% end %>
81
- <% when "DateTime" %>
82
- <% value = eval("object.#{property.field}") %>
81
+ <% when :date_time %>
82
+ <% value = object.send(property[:name]) %>
83
83
  <%= value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y, %I:%M%p") : nil %>
84
- <% when "Date" %>
85
- <% value = eval("object.#{property.field}") %>
84
+ <% when :date %>
85
+ <% value = object.send(property[:name]) %>
86
86
  <%= value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y") : nil %>
87
- <% when "Time" %>
88
- <% value = eval("object.#{property.field}") %>
87
+ <% when :time %>
88
+ <% value = object.send(property[:name]) %>
89
89
  <%= value.respond_to?(:strftime) ? value.strftime("%I:%M%p") : nil %>
90
- <% when "Integer" %>
91
- <% if property.type.respond_to?(:flag_map) #Enum or Flag type %>
92
- <%= eval("object.#{property.field}").to_s.capitalize.gsub('_', ' ') %>
90
+ <% when :string %>
91
+ <%= object.send(property[:name]).to_s.truncate(50) %>
92
+ <% when :text %>
93
+ <%= object.send(property[:name]).to_s.truncate(50) %>
93
94
  <% else %>
94
- <%= eval("object.#{property.field}") %>
95
- <% end %>
96
- <% when "BigDecimal" %>
97
- <%= eval("object.#{property.field}") %>
98
- <% when "Float" %>
99
- <%= eval("object.#{property.field}") %>
100
- <% when "String" %>
101
- <%= eval("object.#{property.field}").to_s.truncate(50) %>
95
+ <%= object.send(property[:name]) %>
102
96
  <% end %>
103
97
  </a>
104
98
  </td>
@@ -111,7 +105,7 @@
111
105
  <% if @page_count.to_i > 1 %>
112
106
  <%= paginate(@current_page, @page_count, :url => '?' + Merb::Parse.params_to_query_string(params)) %>
113
107
  <% end %>
114
- <%= @record_count %> <%= @record_count == 1 ? @model_name.snake_case.gsub('_', ' ') : @model_name.snake_case.gsub('_', ' ').pluralize %>
108
+ <%= @record_count %> <%= @record_count == 1 ? @abstract_model.pretty_name : @abstract_model.pretty_name.pluralize %>
115
109
  <% if @page_count.to_i == 2 %>
116
110
  <%= link_to("Show all", '?' + Merb::Parse.params_to_query_string(params.merge(:all => true)), :class => "showall") %>
117
111
  <% end %>
@@ -1,21 +1,24 @@
1
1
  <div id="content-main">
2
- <%= form_for(@object, :action => slice_url(:admin_create, :model_name => @model_name.snake_case)) do %>
2
+ <%= form_for(@object, :action => slice_url(:admin_create, :model_name => @abstract_model.singular_name)) do %>
3
3
  <div>
4
4
  <fieldset class="module aligned">
5
5
  <% @properties.each do |property| %>
6
- <% next if [:id, :created_at, :created_on, :updated_at, :updated_on].include?(property.name) %>
7
- <div class="<%= @object.errors[property.name] ? "form-row errors" : "form-row"%>">
8
- <% if @object.errors[property.name] %>
6
+ <% next if [:id, :created_at, :created_on, :updated_at, :updated_on].include?(property[:name]) %>
7
+ <div class="<%= @object.errors[property[:name]] ? "form-row errors" : "form-row"%>">
8
+ <% if @object.errors[property[:name]] %>
9
9
  <ul class="errorlist">
10
- <% @object.errors[property.name].each do |error| %>
10
+ <% @object.errors[property[:name]].each do |error| %>
11
11
  <li><%= error %></li>
12
12
  <% end %>
13
13
  </ul>
14
14
  <% end %>
15
- <%= partial(property.primitive.to_s.snake_case, :property => property) -%>
15
+ <%= partial(property[:type].to_s, :property => property) -%>
16
16
  </div>
17
17
  <% end %>
18
18
  </fieldset>
19
+ <% @abstract_model.has_many_associations.each do |association| %>
20
+ <%= partial('has_many', :association => association) %>
21
+ <% end %>
19
22
  <div class="submit-row" >
20
23
  <%= submit "Save", :class => "default", :name => "_save" %>
21
24
  <%= submit "Save and add another", :name => "_add_another" %>
@@ -0,0 +1,171 @@
1
+ module MerbAdmin
2
+ class AbstractModel
3
+
4
+ # Returns all models for a given Merb app
5
+ def self.all
6
+ return @models if @models
7
+ @models ||= []
8
+ case Merb.orm
9
+ when :datamapper
10
+ DataMapper::Resource.descendants.each do |m|
11
+ # Remove DataMapperSessionStore because it's included by default
12
+ next if m == Merb::DataMapperSessionStore if Merb.const_defined?(:DataMapperSessionStore)
13
+ model = lookup(m.to_s.to_sym)
14
+ @models << new(model) if model
15
+ end
16
+ @models.sort{|a, b| a.to_s <=> b.to_s}
17
+ else
18
+ raise "MerbAdmin does not currently support the #{Merb.orm} ORM"
19
+ end
20
+ end
21
+
22
+ # Given a symbol +model_name+, finds the corresponding model class
23
+ def self.lookup(model_name)
24
+ model = const_get(model_name)
25
+ raise "could not find model #{model_name}" if model.nil?
26
+ return model if model.include?(DataMapper::Resource)
27
+ nil
28
+ end
29
+
30
+ attr_accessor :model
31
+
32
+ def initialize(model)
33
+ model = self.class.lookup(model.camel_case.to_sym) unless model.is_a?(Class)
34
+ @model = model
35
+ self.extend(GenericSupport)
36
+ case Merb.orm
37
+ when :datamapper
38
+ self.extend(DatamapperSupport)
39
+ else
40
+ raise "MerbAdmin does not currently support the #{Merb.orm} ORM"
41
+ end
42
+ end
43
+
44
+ module GenericSupport
45
+ def singular_name
46
+ model.to_s.snake_case.to_sym
47
+ end
48
+
49
+ def plural_name
50
+ model.to_s.snake_case.pluralize.to_sym
51
+ end
52
+
53
+ def pretty_name
54
+ model.to_s.snake_case.gsub('_', ' ')
55
+ end
56
+ end
57
+
58
+ module DatamapperSupport
59
+ def count(options = {})
60
+ model.count(options)
61
+ end
62
+
63
+ def find_all(options = {})
64
+ model.all(options)
65
+ end
66
+
67
+ def find(id)
68
+ model.get(id).extend(InstanceMethods)
69
+ end
70
+
71
+ def new(params = {})
72
+ model.new(params).extend(InstanceMethods)
73
+ end
74
+
75
+ def has_many_associations
76
+ associations.select do |association|
77
+ association[:type] == :has_many
78
+ end
79
+ end
80
+
81
+ def has_one_associations
82
+ associations.select do |association|
83
+ association[:type] == :has_one
84
+ end
85
+ end
86
+
87
+ def belongs_to_associations
88
+ associations.select do |association|
89
+ association[:type] == :belongs_to
90
+ end
91
+ end
92
+
93
+ def associations
94
+ model.relationships.to_a.map do |name, relationship|
95
+ {
96
+ :name => name,
97
+ :pretty_name => name.to_s.gsub('_', ' '),
98
+ :type => association_type_lookup(relationship),
99
+ :parent_model => relationship.parent_model,
100
+ :parent_key => relationship.parent_key.map{|p| p.name},
101
+ :child_model => relationship.child_model,
102
+ :child_key => relationship.child_key.map{|p| p.name},
103
+ :remote_relationship => relationship.options[:remote_relationship_name],
104
+ :near_relationship => relationship.options[:near_relationship_name],
105
+ }
106
+ end
107
+ end
108
+
109
+ def association_names
110
+ associations.map do |association|
111
+ association[:type] == :belongs_to ? association[:parent_name] : association[:child_name]
112
+ end
113
+ end
114
+
115
+ def properties
116
+ model.properties.map do |property|
117
+ {
118
+ :name => property.name,
119
+ :pretty_name => property.field.gsub('_', ' '),
120
+ :nullable? => property.nullable?,
121
+ :serial? => property.serial?,
122
+ :key? => property.key?,
123
+ :field => property.field,
124
+ :length => property.length,
125
+ :type => type_lookup(property),
126
+ :flag_map => property.type.respond_to?(:flag_map) ? property.type.flag_map : nil,
127
+ }
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def association_type_lookup(relationship)
134
+ if self.model == relationship.parent_model
135
+ relationship.options[:max] > 1 ? :has_many : :has_one
136
+ elsif self.model == relationship.child_model
137
+ :belongs_to
138
+ else
139
+ raise "Unknown association type"
140
+ end
141
+ end
142
+
143
+ def type_lookup(property)
144
+ type = {
145
+ DataMapper::Types::Serial => :integer,
146
+ DataMapper::Types::Boolean => :boolean,
147
+ DataMapper::Types::Text => :text,
148
+ DataMapper::Types::ParanoidDateTime => :date_time,
149
+ Integer => :integer,
150
+ Fixnum => :integer,
151
+ Float => :float,
152
+ BigDecimal => :big_decimal,
153
+ TrueClass => :boolean,
154
+ String => :string,
155
+ DateTime => :date_time,
156
+ Date => :date,
157
+ Time => :time,
158
+ }
159
+ type[property.type] || type[property.primitive]
160
+ end
161
+
162
+ module InstanceMethods
163
+ def clear_association(association)
164
+ association.clear
165
+ end
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+ end
data/lib/merb-admin.rb CHANGED
@@ -22,8 +22,8 @@ if defined?(Merb::Plugins)
22
22
  module MerbAdmin
23
23
 
24
24
  # Slice metadata
25
- self.description = "MerbAdmin is a merb slice that uses your DataMapper models to provide an easy-to-use, Django-style interface for content managers."
26
- self.version = "0.2.7"
25
+ self.description = "MerbAdmin is a Merb plugin that provides an easy-to-use interface for managing your data."
26
+ self.version = "0.2.8"
27
27
  self.author = "Erik Michaels-Ober"
28
28
 
29
29
  # Stub classes loaded hook - runs before LoadClasses BootLoader
data/lib/metaid.rb ADDED
@@ -0,0 +1,19 @@
1
+ class Object
2
+ # The hidden singleton lurks behind everyone
3
+ def metaclass; class << self; self; end; end
4
+ def meta_eval &blk; metaclass.instance_eval &blk; end
5
+
6
+ # Adds methods to a metaclass
7
+ def meta_def name, &blk
8
+ meta_eval { define_method name, &blk }
9
+ end
10
+
11
+ # Defines an instance method within a class
12
+ def class_def name, &blk
13
+ class_eval { define_method name, &blk }
14
+ end
15
+
16
+ def try( method )
17
+ self.send( method ) if self.respond_to?( method )
18
+ end
19
+ end
@@ -36,7 +36,7 @@ var SelectFilter = {
36
36
  quickElement('h2', selector_available, interpolate(gettext('Available %s'), [field_name]));
37
37
  var filter_p = quickElement('p', selector_available, '');
38
38
  filter_p.className = 'selector-filter';
39
- quickElement('img', filter_p, '', 'src', admin_media_prefix + 'images/selector-search.gif');
39
+ quickElement('img', filter_p, '', 'src', admin_media_prefix + '/selector-search.gif');
40
40
  filter_p.appendChild(document.createTextNode(' '));
41
41
  var filter_input = quickElement('input', filter_p, '', 'type', 'text');
42
42
  filter_input.id = field_id + '_input';
@@ -58,7 +58,7 @@ var SelectFilter = {
58
58
  quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s'), [field_name]));
59
59
  var selector_filter = quickElement('p', selector_chosen, gettext('Select your choice(s) and click '));
60
60
  selector_filter.className = 'selector-filter';
61
- quickElement('img', selector_filter, '', 'src', admin_media_prefix + (is_stacked ? 'images/selector_stacked-add.gif':'images/selector-add.gif'), 'alt', 'Add');
61
+ quickElement('img', selector_filter, '', 'src', admin_media_prefix + (is_stacked ? '/selector_stacked-add.gif':'/selector-add.gif'), 'alt', 'Add');
62
62
  var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
63
63
  to_box.className = 'filtered';
64
64
  var clear_all = quickElement('a', selector_chosen, gettext('Clear all'), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from");})()');
@@ -8,7 +8,7 @@ class Player
8
8
  property :team_id, Integer, :nullable => false, :index => true
9
9
  property :number, Integer, :nullable => false
10
10
  property :name, String, :length => 100, :nullable => false
11
- property :position, Flag[:pitcher, :catcher, :first, :second, :third, :shortstop, :left, :center, :right]
11
+ property :position, Enum[:pitcher, :catcher, :first, :second, :third, :shortstop, :left, :center, :right]
12
12
  property :sex, Enum[:male, :female]
13
13
  property :batting_average, Float, :default => 0.0, :precision => 4, :scale => 3
14
14
  property :injured, Boolean, :default => false
@@ -24,5 +24,6 @@ Player.fixture {{
24
24
  :team_id => /\d{1,2}/.gen,
25
25
  :number => /\d{1,2}/.gen,
26
26
  :name => "#{/\w{3,10}/.gen.capitalize} #{/\w{5,10}/.gen.capitalize}",
27
+ :position => Player.properties[:position].type.flag_map.values[rand(Player.properties[:position].type.flag_map.length)],
27
28
  :sex => Player.properties[:sex].type.flag_map.values[rand(Player.properties[:sex].type.flag_map.length)],
28
29
  }}
@@ -28,7 +28,7 @@ describe "MerbAdmin" do
28
28
  @response.should be_successful
29
29
  end
30
30
 
31
- it "should should contain \"Site administration\"" do
31
+ it "should contain \"Site administration\"" do
32
32
  @response.body.should contain("Site administration")
33
33
  end
34
34
  end
@@ -43,7 +43,7 @@ describe "MerbAdmin" do
43
43
  end
44
44
 
45
45
  it "should contain \"Select model to edit\"" do
46
- @response.body.should contain("Select #{"player".gsub('_', ' ')} to edit")
46
+ @response.body.should contain("Select player to edit")
47
47
  end
48
48
  end
49
49
 
@@ -119,27 +119,7 @@ describe "MerbAdmin" do
119
119
  end
120
120
  end
121
121
 
122
- describe "list with enum filter" do
123
- before(:each) do
124
- Player.gen(:name => "Jackie Robinson", :sex => :male)
125
- Player.gen(:name => "Dottie Hinson", :sex => :female)
126
- @response = request(url(:admin_list, :model_name => "player"), :params => {:filter => {:sex => :male}})
127
- end
128
-
129
- it "should respond sucessfully" do
130
- @response.should be_successful
131
- end
132
-
133
- it "should contain matching results" do
134
- @response.body.should contain("Jackie Robinson")
135
- end
136
-
137
- it "should not contain non-matching results" do
138
- @response.body.should_not contain("Dottie Hinson")
139
- end
140
- end
141
-
142
- describe "list with 2 players", :given => "two players exist" do
122
+ describe "list with 2 objects", :given => "two players exist" do
143
123
  before(:each) do
144
124
  MerbAdmin[:paginate] = true
145
125
  MerbAdmin[:per_page] = 1
@@ -151,72 +131,68 @@ describe "MerbAdmin" do
151
131
  end
152
132
 
153
133
  it "should contain \"2 results\"" do
154
- @response.body.should contain("2 #{"player".gsub('_', ' ').pluralize}")
134
+ @response.body.should contain("2 players")
155
135
  end
156
136
  end
157
137
 
158
- describe "list with 2 players, show all", :given => "two players exist" do
138
+ describe "list with 20 objects", :given => "twenty players exist" do
159
139
  before(:each) do
160
140
  MerbAdmin[:paginate] = true
161
141
  MerbAdmin[:per_page] = 1
162
- @response = request(url(:admin_list, :model_name => "player"), :params => {:all => true})
142
+ @response = request(url(:admin_list, :model_name => "player"))
163
143
  end
164
144
 
165
145
  it "should respond sucessfully" do
166
146
  @response.should be_successful
167
147
  end
168
148
 
169
- it "should contain \"2 results\"" do
170
- @response.body.should contain("2 #{"player".gsub('_', ' ').pluralize}")
149
+ it "should contain \"20 results\"" do
150
+ @response.body.should contain("20 players")
171
151
  end
172
152
  end
173
153
 
174
- describe "list with 20 players", :given => "twenty players exist" do
154
+ describe "list with 20 objects, page 8", :given => "twenty players exist" do
175
155
  before(:each) do
176
156
  MerbAdmin[:paginate] = true
177
157
  MerbAdmin[:per_page] = 1
178
- @response = request(url(:admin_list, :model_name => "player"))
158
+ @response = request(url(:admin_list, :model_name => "player"), :params => {:page => 8})
179
159
  end
180
160
 
181
161
  it "should respond sucessfully" do
182
162
  @response.should be_successful
183
163
  end
184
164
 
185
- it "should contain \"20 results\"" do
186
- @response.body.should contain("20 #{"player".gsub('_', ' ').pluralize}")
165
+ it "should paginate correctly" do
166
+ @response.body.should contain(/1 2[^0-9]*5 6 7 8 9 10 11[^0-9]*19 20/)
187
167
  end
188
168
  end
189
169
 
190
- describe "list with 20 players, page 8", :given => "twenty players exist" do
170
+ describe "list with 20 objects, page 17", :given => "twenty players exist" do
191
171
  before(:each) do
192
172
  MerbAdmin[:paginate] = true
193
173
  MerbAdmin[:per_page] = 1
194
- @response = request(url(:admin_list, :model_name => "player"), :params => {:page => 8})
174
+ @response = request(url(:admin_list, :model_name => "player"), :params => {:page => 17})
195
175
  end
196
176
 
197
177
  it "should respond sucessfully" do
198
178
  @response.should be_successful
199
179
  end
200
180
 
201
- it "should contain \"20 results\"" do
202
- @response.body.should contain("20 #{"player".gsub('_', ' ').pluralize}")
181
+ it "should paginate correctly" do
182
+ @response.body.should contain(/1 2[^0-9]*12 13 14 15 16 17 18 19 20/)
203
183
  end
204
184
  end
205
185
 
206
- describe "list with 20 players, page 17", :given => "twenty players exist" do
186
+ describe "list show all", :given => "two players exist" do
207
187
  before(:each) do
208
188
  MerbAdmin[:paginate] = true
209
189
  MerbAdmin[:per_page] = 1
210
- @response = request(url(:admin_list, :model_name => "player"), :params => {:page => 17})
190
+ @response = request(url(:admin_list, :model_name => "player"), :params => {:all => true})
211
191
  end
212
192
 
213
193
  it "should respond sucessfully" do
214
194
  @response.should be_successful
215
195
  end
216
-
217
- it "should contain \"20 results\"" do
218
- @response.body.should contain("20 #{"player".gsub('_', ' ').pluralize}")
219
- end
220
196
  end
221
197
 
222
198
  describe "new" do
@@ -229,7 +205,7 @@ describe "MerbAdmin" do
229
205
  end
230
206
 
231
207
  it "should contain \"New model\"" do
232
- @response.body.should contain("New #{"player".gsub('_', ' ')}")
208
+ @response.body.should contain("New player")
233
209
  end
234
210
  end
235
211
 
@@ -243,7 +219,7 @@ describe "MerbAdmin" do
243
219
  end
244
220
 
245
221
  it "should contain \"Edit model\"" do
246
- @response.body.should contain("Edit #{"player".gsub('_', ' ')}")
222
+ @response.body.should contain("Edit player")
247
223
  end
248
224
  end
249
225
 
@@ -259,7 +235,7 @@ describe "MerbAdmin" do
259
235
 
260
236
  describe "create" do
261
237
  before(:each) do
262
- @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}})
238
+ @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :position => :second, :sex => :male}})
263
239
  end
264
240
 
265
241
  it "should redirect to list" do
@@ -271,19 +247,9 @@ describe "MerbAdmin" do
271
247
  end
272
248
  end
273
249
 
274
- describe "create with invalid object" do
275
- before(:each) do
276
- @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {}})
277
- end
278
-
279
- it "should have an error message" do
280
- @response.body.should contain("Player failed to be created")
281
- end
282
- end
283
-
284
250
  describe "create and edit" do
285
251
  before(:each) do
286
- @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}, :_continue => true})
252
+ @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :position => :second, :sex => :male}, :_continue => true})
287
253
  end
288
254
 
289
255
  it "should redirect to edit" do
@@ -297,7 +263,7 @@ describe "MerbAdmin" do
297
263
 
298
264
  describe "create and add another" do
299
265
  before(:each) do
300
- @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}, :_add_another => true})
266
+ @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :position => :second, :sex => :male}, :_add_another => true})
301
267
  end
302
268
 
303
269
  it "should redirect to new" do
@@ -309,37 +275,27 @@ describe "MerbAdmin" do
309
275
  end
310
276
  end
311
277
 
312
- describe "update", :given => "an object exists" do
278
+ describe "create with invalid object" do
313
279
  before(:each) do
314
- @response = request(url(:admin_update, :model_name => "player", :id => @player.id), :method => "put", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}})
315
- end
316
-
317
- it "should redirect to list" do
318
- @response.should redirect_to(url(:admin_list, :model_name => "player"))
280
+ @response = request(url(:admin_create, :model_name => "player"), :method => "post", :params => {:player => {}})
319
281
  end
320
282
 
321
- it "should update an object that already exists" do
322
- Player.first(:id => @player.id).name.should eql("Jackie Robinson")
283
+ it "should contain an error message" do
284
+ @response.body.should contain("Player failed to be created")
323
285
  end
324
286
  end
325
287
 
326
- describe "update with invalid object", :given => "an object exists" do
288
+ describe "update", :given => "an object exists" do
327
289
  before(:each) do
328
- @response = request(url(:admin_update, :model_name => "player", :id => @player.id), :method => "put", :params => {:player => {:number => nil}})
329
- end
330
-
331
- it "should have an error message" do
332
- @response.body.should contain("Player failed to be updated")
290
+ @response = request(url(:admin_update, :model_name => "player", :id => @player.id), :method => "put", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}})
333
291
  end
334
- end
335
292
 
336
- describe "update with missing object" do
337
- before(:each) do
338
- @response = request(url(:admin_update, :model_name => "player", :id => 1), :method => "put", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}})
293
+ it "should redirect to list" do
294
+ @response.should redirect_to(url(:admin_list, :model_name => "player"))
339
295
  end
340
296
 
341
- it "should raise NotFound" do
342
- @response.status.should == 404
297
+ it "should update an object that already exists" do
298
+ Player.first(:id => @player.id).name.should eql("Jackie Robinson")
343
299
  end
344
300
  end
345
301
 
@@ -371,6 +327,26 @@ describe "MerbAdmin" do
371
327
  end
372
328
  end
373
329
 
330
+ describe "update with missing object" do
331
+ before(:each) do
332
+ @response = request(url(:admin_update, :model_name => "player", :id => 1), :method => "put", :params => {:player => {:name => "Jackie Robinson", :number => 42, :team_id => 1, :sex => :male}})
333
+ end
334
+
335
+ it "should raise NotFound" do
336
+ @response.status.should == 404
337
+ end
338
+ end
339
+
340
+ describe "update with invalid object", :given => "an object exists" do
341
+ before(:each) do
342
+ @response = request(url(:admin_update, :model_name => "player", :id => @player.id), :method => "put", :params => {:player => {:number => "a"}})
343
+ end
344
+
345
+ it "should contain an error message" do
346
+ @response.body.should contain("Player failed to be updated")
347
+ end
348
+ end
349
+
374
350
  describe "delete", :given => "an object exists" do
375
351
  before(:each) do
376
352
  @response = request(url(:admin_delete, :model_name => "player", :id => @player.id))
@@ -381,7 +357,7 @@ describe "MerbAdmin" do
381
357
  end
382
358
 
383
359
  it "should contain \"Delete model\"" do
384
- @response.body.should contain("Delete #{"player".gsub('_', ' ')}")
360
+ @response.body.should contain("Delete player")
385
361
  end
386
362
  end
387
363
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sferik-merb-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Michaels-Ober
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-22 00:00:00 -07:00
12
+ date: 2009-08-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -55,11 +55,13 @@ files:
55
55
  - LICENSE
56
56
  - README.markdown
57
57
  - Rakefile
58
+ - lib/abstract_model.rb
58
59
  - lib/merb-admin
59
60
  - lib/merb-admin/merbtasks.rb
60
61
  - lib/merb-admin/slicetasks.rb
61
62
  - lib/merb-admin/spectasks.rb
62
63
  - lib/merb-admin.rb
64
+ - lib/metaid.rb
63
65
  - spec/controllers
64
66
  - spec/controllers/main_spec.rb
65
67
  - spec/fixtures
@@ -85,13 +87,15 @@ files:
85
87
  - app/views/layout/list.html.erb
86
88
  - app/views/main
87
89
  - app/views/main/_big_decimal.html.erb
90
+ - app/views/main/_boolean.html.erb
88
91
  - app/views/main/_date.html.erb
89
92
  - app/views/main/_date_time.html.erb
90
93
  - app/views/main/_float.html.erb
94
+ - app/views/main/_has_many.html.erb
91
95
  - app/views/main/_integer.html.erb
92
96
  - app/views/main/_string.html.erb
97
+ - app/views/main/_text.html.erb
93
98
  - app/views/main/_time.html.erb
94
- - app/views/main/_true_class.html.erb
95
99
  - app/views/main/delete.html.erb
96
100
  - app/views/main/edit.html.erb
97
101
  - app/views/main/index.html.erb
@@ -1,3 +0,0 @@
1
- <div>
2
- <%= check_box(property.name, :label => property.field.capitalize.gsub('_', ' ')) %>
3
- </div>