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 +8 -3
- data/Rakefile +1 -1
- data/app/controllers/main.rb +91 -57
- data/app/helpers/main_helper.rb +3 -0
- data/app/views/layout/form.html.erb +2 -2
- data/app/views/layout/list.html.erb +2 -2
- data/app/views/main/_big_decimal.html.erb +2 -2
- data/app/views/main/_boolean.html.erb +3 -0
- data/app/views/main/_date.html.erb +3 -3
- data/app/views/main/_date_time.html.erb +3 -3
- data/app/views/main/_float.html.erb +2 -2
- data/app/views/main/_has_many.html.erb +10 -0
- data/app/views/main/_integer.html.erb +5 -5
- data/app/views/main/_string.html.erb +4 -12
- data/app/views/main/_text.html.erb +6 -0
- data/app/views/main/_time.html.erb +3 -3
- data/app/views/main/delete.html.erb +3 -3
- data/app/views/main/edit.html.erb +11 -8
- data/app/views/main/index.html.erb +4 -4
- data/app/views/main/list.html.erb +35 -41
- data/app/views/main/new.html.erb +9 -6
- data/lib/abstract_model.rb +171 -0
- data/lib/merb-admin.rb +2 -2
- data/lib/metaid.rb +19 -0
- data/public/javascripts/SelectFilter2.js +2 -2
- data/spec/fixtures/player_fixture.rb +2 -1
- data/spec/requests/main_spec.rb +54 -78
- metadata +7 -3
- data/app/views/main/_true_class.html.erb +0 -3
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.
|
18
|
-
dependency "dm-is-paginated", "0.0.1" #
|
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
|
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.
|
15
|
+
GEM_VERSION = "0.2.8"
|
16
16
|
|
17
17
|
spec = Gem::Specification.new do |s|
|
18
18
|
s.rubyforge_project = "merb"
|
data/app/controllers/main.rb
CHANGED
@@ -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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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 = @
|
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
|
-
|
52
|
-
|
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 = @
|
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 =
|
70
|
-
|
71
|
-
|
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 => @
|
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 => @
|
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 => @
|
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] = "#{@
|
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 =
|
87
|
-
|
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 => @
|
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 => @
|
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 => @
|
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] = "#{@
|
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 => @
|
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
|
-
@
|
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
|
-
|
123
|
-
|
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 = @
|
121
|
+
@properties = @abstract_model.properties
|
133
122
|
end
|
134
123
|
|
135
124
|
def find_object
|
136
|
-
@object = @
|
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
|
data/app/helpers/main_helper.rb
CHANGED
@@ -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 + " " + @
|
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)) %> ›
|
38
|
-
<%= link_to(@
|
38
|
+
<%= link_to(@abstract_model.plural_name.to_s.capitalize, url(:admin_list, :model_name => @abstract_model.singular_name)) %> ›
|
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 " + @
|
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)) %> ›
|
33
|
-
<%=
|
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
|
2
|
+
<%= text_field(property[:name], :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
|
3
3
|
<p class="help">
|
4
|
-
<%= !property
|
4
|
+
<%= !property[:nullable?] || property[:serial?] ? "Required." : "Optional." %>
|
5
5
|
</p>
|
6
6
|
</div>
|
@@ -1,7 +1,7 @@
|
|
1
|
-
<% value =
|
1
|
+
<% value = @object.send(property[:name]) %>
|
2
2
|
<div>
|
3
|
-
<%= text_field(property
|
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
|
5
|
+
<%= !property[:nullable?] ? "Required." : "Optional." %>
|
6
6
|
</p>
|
7
7
|
</div>
|
@@ -1,7 +1,7 @@
|
|
1
|
-
<% value =
|
1
|
+
<% value = @object.send(property[:name]) %>
|
2
2
|
<div>
|
3
|
-
<%= text_field(property
|
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
|
5
|
+
<%= !property[:nullable?] ? "Required." : "Optional." %>
|
6
6
|
</p>
|
7
7
|
</div>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<div>
|
2
|
-
<%= text_field(property
|
2
|
+
<%= text_field(property[:name], :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
|
3
3
|
<p class="help">
|
4
|
-
<%= !property
|
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
|
3
|
-
<%= select(property
|
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
|
6
|
-
<% end %>
|
5
|
+
<%= text_field(property[:name], :maxlength => property[:length], :label => property[:pretty_name].capitalize) %>
|
7
6
|
<p class="help">
|
8
|
-
<%= !property
|
7
|
+
<%= !property[:nullable?] || property[:serial?] ? "Required." : "Optional." %>
|
9
8
|
</p>
|
9
|
+
<% end %>
|
10
10
|
</div>
|
@@ -1,14 +1,6 @@
|
|
1
1
|
<div>
|
2
|
-
|
3
|
-
|
4
|
-
<%=
|
5
|
-
|
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>
|
@@ -1,7 +1,7 @@
|
|
1
|
-
<% value =
|
1
|
+
<% value = @object.send(property[:name]) %>
|
2
2
|
<div>
|
3
|
-
<%= text_field(property
|
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
|
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 <%= @
|
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 => @
|
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 => @
|
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", "/#{@
|
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 => @
|
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
|
-
|
12
|
-
<div class="<%= @object.errors[property
|
13
|
-
<% if @object.errors[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]] %>
|
14
14
|
<ul class="errorlist">
|
15
|
-
<% @object.errors[property
|
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.
|
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 => @
|
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
|
-
<% @
|
7
|
+
<% @abstract_models.each do |abstract_model| %>
|
8
8
|
<tr>
|
9
9
|
<th scope="row">
|
10
|
-
<%= link_to(
|
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 =>
|
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 =>
|
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 #{@
|
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="?"><%= @
|
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
|
31
|
-
<h3>By <%= property
|
30
|
+
<% if property[:type] == :boolean %>
|
31
|
+
<h3>By <%= property[:pretty_name] %></h3>
|
32
32
|
<ul>
|
33
|
-
<li class="<%= params[:filter].nil? || params[:filter][property
|
34
|
-
<a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).reject{|key, value| key.to_sym == property
|
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
|
37
|
-
<a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property
|
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
|
40
|
-
<a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property
|
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
|
44
|
-
<h3>By <%= property
|
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
|
47
|
-
<a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).reject{|key, value| key.to_sym == property
|
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
|
50
|
-
<li class="<%= params[:filter] && params[:filter][property
|
51
|
-
<a href="?<%= Merb::Parse.params_to_query_string(params.merge(:filter => (params[:filter] || {}).merge({property
|
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
|
63
|
-
<a href="?<%= Merb::Parse.params_to_query_string(params.merge(:sort => 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[: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 => @
|
74
|
-
<% case property
|
75
|
-
<% when
|
76
|
-
<% if
|
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
|
82
|
-
|
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
|
85
|
-
|
84
|
+
<% when :date %>
|
85
|
+
<% value = object.send(property[:name]) %>
|
86
86
|
<%= value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y") : nil %>
|
87
|
-
<% when
|
88
|
-
|
87
|
+
<% when :time %>
|
88
|
+
<% value = object.send(property[:name]) %>
|
89
89
|
<%= value.respond_to?(:strftime) ? value.strftime("%I:%M%p") : nil %>
|
90
|
-
<% when
|
91
|
-
|
92
|
-
|
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
|
-
<%=
|
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 ? @
|
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 %>
|
data/app/views/main/new.html.erb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
<div id="content-main">
|
2
|
-
<%= form_for(@object, :action => slice_url(:admin_create, :model_name => @
|
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
|
7
|
-
<div class="<%= @object.errors[property
|
8
|
-
<% if @object.errors[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]] %>
|
9
9
|
<ul class="errorlist">
|
10
|
-
<% @object.errors[property
|
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.
|
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
|
26
|
-
self.version = "0.2.
|
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 + '
|
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 ? '
|
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,
|
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
|
}}
|
data/spec/requests/main_spec.rb
CHANGED
@@ -28,7 +28,7 @@ describe "MerbAdmin" do
|
|
28
28
|
@response.should be_successful
|
29
29
|
end
|
30
30
|
|
31
|
-
it "should
|
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
|
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
|
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
|
134
|
+
@response.body.should contain("2 players")
|
155
135
|
end
|
156
136
|
end
|
157
137
|
|
158
|
-
describe "list with
|
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")
|
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 \"
|
170
|
-
@response.body.should contain("
|
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
|
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
|
186
|
-
@response.body.should contain(
|
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
|
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 =>
|
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
|
202
|
-
@response.body.should contain(
|
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
|
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 => {:
|
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
|
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
|
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 "
|
278
|
+
describe "create with invalid object" do
|
313
279
|
before(:each) do
|
314
|
-
@response = request(url(:
|
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
|
322
|
-
|
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
|
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 =>
|
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
|
-
|
337
|
-
|
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
|
342
|
-
@
|
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
|
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.
|
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-
|
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
|