sferik-merb-admin 0.2.7 → 0.2.8

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