merb-admin 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -8,13 +8,13 @@ It currently offers the features listed [here](http://sferik.tadalist.com/lists/
8
8
 
9
9
  At the command prompt, type:
10
10
 
11
- sudo gem install sferik-merb-admin -s http://gems.github.com
11
+ sudo gem install merb-admin -s http://gemcutter.org
12
12
 
13
13
  ## Install it
14
14
 
15
15
  In your app, add the following dependency to `config/dependencies.rb`:
16
16
 
17
- dependency "sferik-merb-admin", "0.4.3", :require_as => "merb-admin"
17
+ dependency "merb-admin", "0.4.4"
18
18
 
19
19
  Add the following route to `config/router.rb`:
20
20
 
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ AUTHOR = "Erik Michaels-Ober"
9
9
  EMAIL = "sferik@gmail.com"
10
10
  HOMEPAGE = "http://twitter.com/sferik"
11
11
  SUMMARY = "MerbAdmin is a Merb plugin that provides an easy-to-use interface for managing your data."
12
- GEM_VERSION = "0.4.3"
12
+ GEM_VERSION = "0.4.4"
13
13
 
14
14
  spec = Gem::Specification.new do |s|
15
15
  s.rubyforge_project = "merb"
@@ -13,6 +13,8 @@ class MerbAdmin::Main < MerbAdmin::Application
13
13
 
14
14
  def list
15
15
  options = {}
16
+ merge_sort!(options)
17
+ merge_sort_reverse!(options)
16
18
 
17
19
  if params[:all]
18
20
  options = {
@@ -123,6 +125,18 @@ class MerbAdmin::Main < MerbAdmin::Application
123
125
  raise NotFound unless @object
124
126
  end
125
127
 
128
+ def merge_sort!(options)
129
+ return unless params[:sort]
130
+ sort = params[:sort] || "id"
131
+ options.merge!(:sort => sort)
132
+ end
133
+
134
+ def merge_sort_reverse!(options)
135
+ return unless params[:sort_reverse]
136
+ reverse = params[:sort_reverse] == "true"
137
+ options.merge!(:sort_reverse => reverse)
138
+ end
139
+
126
140
  def update_has_one_association(association, id)
127
141
  model = MerbAdmin::AbstractModel.new(association[:child_model])
128
142
  if object = model.get(id)
@@ -3,7 +3,9 @@ module Merb
3
3
  module MerbAdmin
4
4
  module MainHelper
5
5
  def object_title(object)
6
- if object.respond_to?(:name)
6
+ if object.nil?
7
+ nil
8
+ elsif object.respond_to?(:name)
7
9
  object.name
8
10
  elsif object.respond_to?(:title)
9
11
  object.title
@@ -12,6 +14,41 @@ module Merb
12
14
  end
13
15
  end
14
16
 
17
+ def object_property(object, property)
18
+ property_type = property[:type]
19
+ property_name = property[:name]
20
+ case property_type
21
+ when :boolean
22
+ if object.send(property_name) == true
23
+ Builder::XmlMarkup.new.img(:src => image_path("icon-yes.gif"), :alt => "True")
24
+ else
25
+ Builder::XmlMarkup.new.img(:src => image_path("icon-on.gif"), :alt => "False")
26
+ end
27
+ when :date_time
28
+ value = object.send(property_name)
29
+ value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y, %I:%M%p") : nil
30
+ when :date
31
+ value = object.send(property_name)
32
+ value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y") : nil
33
+ when :time
34
+ value = object.send(property_name)
35
+ value.respond_to?(:strftime) ? value.strftime("%I:%M%p") : nil
36
+ when :string
37
+ object.send(property_name).to_s.truncate(50)
38
+ when :text
39
+ object.send(property_name).to_s.truncate(50)
40
+ when :integer
41
+ association = @abstract_model.belongs_to_associations.select{|a| a[:child_key].first == property_name}.first
42
+ if association
43
+ object_title(object.send(association[:name]))
44
+ else
45
+ object.send(property_name)
46
+ end
47
+ else
48
+ object.send(property_name)
49
+ end
50
+ end
51
+
15
52
  # Given a page count and the current page, we generate a set of pagination
16
53
  # links.
17
54
  #
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
2
+ property_name = property[:name]
3
3
  length = property[:length]
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?] || property[:serial?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :maxlength => length, :label => label) %>
8
+ <%= text_field(property_name, :maxlength => length, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %>
11
11
  </p>
@@ -1,7 +1,7 @@
1
1
  <%
2
- name = property[:name]
2
+ property_name = property[:name]
3
3
  label = property[:pretty_name]
4
4
  %>
5
5
  <div>
6
- <%= check_box(name, :label => label) %>
6
+ <%= check_box(property_name, :label => label) %>
7
7
  </div>
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
3
- value = @object.send(name)
2
+ property_name = property[:name]
3
+ value = @object.send(property_name)
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : nil, :label => label) %>
8
+ <%= text_field(property_name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : nil, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %>
11
11
  </p>
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
3
- value = @object.send(name)
2
+ property_name = property[:name]
3
+ value = @object.send(property_name)
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil, :label => label) %>
8
+ <%= text_field(property_name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %>
11
11
  </p>
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
2
+ property_name = property[:name]
3
3
  length = property[:length]
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?] || property[:serial?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :maxlength => length, :label => label) %>
8
+ <%= text_field(property_name, :maxlength => length, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %>
11
11
  </p>
@@ -1,15 +1,15 @@
1
1
  <%
2
- name = association[:name]
2
+ association_name = association[:name]
3
3
  collection = MerbAdmin::AbstractModel.new(association[:child_model]).all.map{|o| [o.id, object_title(o)]}.sort_by{|o| o[1]}
4
- selected = @object.send(name)
4
+ selected = @object.send(association_name)
5
5
  label = association[:pretty_name]
6
6
  %>
7
7
  <fieldset class="module aligned">
8
8
  <h2><%= label %></h2>
9
9
  <div class="form-row">
10
10
  <div>
11
- <%= select(:name => "associations[#{name}][]", :id => name, :collection => collection, :selected => selected.map{|o| o.id.to_s}, :label => label, :multiple => true) %>
12
- <script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("<%= name %>", "<%= name %>", 0, "<%= image_path %>"); });</script>
11
+ <%= select(:name => "associations[#{association_name}][]", :id => association_name, :collection => collection, :selected => selected.map{|o| o.id.to_s}, :label => label, :multiple => true) %>
12
+ <script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("<%= association_name %>", "<%= association_name %>", 0, "<%= image_path %>"); });</script>
13
13
  <p class="help">Hold down "Control", or "Command" on a Mac, to select more than one.</p>
14
14
  </div>
15
15
  </div>
@@ -1,8 +1,8 @@
1
1
  <%
2
2
  child_key = association[:child_key].first
3
- name = association[:name]
3
+ association_name = association[:name]
4
4
  collection = MerbAdmin::AbstractModel.new(association[:child_model]).all.map{|o| [o.id, object_title(o)]}.sort_by{|o| o[1]}
5
- selected = @object.send(association[:name])
5
+ selected = @object.send(association_name)
6
6
  label = association[:pretty_name]
7
7
  required = false
8
8
  @properties.each do |property|
@@ -21,7 +21,7 @@
21
21
  </ul>
22
22
  <% end %>
23
23
  <div>
24
- <%= select(:name => "associations[#{name}][]", :id => name, :collection => collection, :include_blank => true, :selected => selected ? selected.id.to_s : nil, :label => label) %>
24
+ <%= select(:name => "associations[#{association_name}][]", :id => association_name, :collection => collection, :include_blank => true, :selected => selected ? selected.id.to_s : nil, :label => label) %>
25
25
  <p class="help">
26
26
  <%= required ? "Required." : "Optional." %>
27
27
  </p>
@@ -1,16 +1,16 @@
1
1
  <%
2
2
  flag_map = property[:flag_map]
3
- name = property[:name]
3
+ property_name = property[:name]
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?] || property[:serial?]
6
6
  %>
7
7
  <div>
8
8
  <% if flag_map # Enum or Flag type %>
9
9
  <% collection = flag_map.map{|x| [x[1], x[1].to_s.capitalize.gsub('_', ' ')]}.sort{|a, b| a[1] <=> b[1]} %>
10
- <%= select(name, :collection => collection, :label => label) %>
10
+ <%= select(property_name, :collection => collection, :label => label) %>
11
11
  <% else %>
12
12
  <% length = property[:length] %>
13
- <%= text_field(name, :maxlength => length, :label => label) %>
13
+ <%= text_field(property_name, :maxlength => length, :label => label) %>
14
14
  <p class="help">
15
15
  <%= required ? "Required." : "Optional." %>
16
16
  </p>
@@ -1,18 +1,19 @@
1
1
  <fieldset class="module aligned">
2
2
  <% belongs_to_keys = @abstract_model.belongs_to_associations.map{|b| b[:child_key].first} %>
3
3
  <% properties.each do |property| %>
4
- <% name = property[:name] %>
5
- <% next if [:id, :created_at, :created_on, :deleted_at, :updated_at, :updated_on, :deleted_on].include?(name) %>
6
- <% next if belongs_to_keys.include?(name) %>
7
- <div class="<%= @object.errors[name] ? "form-row errors" : "form-row" %>">
8
- <% if @object.errors[name] %>
4
+ <% property_name = property[:name] %>
5
+ <% property_type = property[:type] %>
6
+ <% next if [:id, :created_at, :created_on, :deleted_at, :updated_at, :updated_on, :deleted_on].include?(property_name) %>
7
+ <% next if belongs_to_keys.include?(property_name) %>
8
+ <div class="<%= @object.errors[property_name] ? "form-row errors" : "form-row" %>">
9
+ <% if @object.errors[property_name] %>
9
10
  <ul class="errorlist">
10
- <% @object.errors[name].each do |error| %>
11
+ <% @object.errors[property_name].each do |error| %>
11
12
  <li><%= error %></li>
12
13
  <% end %>
13
14
  </ul>
14
15
  <% end %>
15
- <%= partial(property[:type].to_s, :property => property) -%>
16
+ <%= partial(property_type.to_s, :property => property) -%>
16
17
  </div>
17
18
  <% end %>
18
19
  </fieldset>
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
2
+ property_name = property[:name]
3
3
  length = property[:length]
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :size => [50, length].min, :maxlength => length, :label => label) %>
8
+ <%= text_field(property_name, :size => [50, length].min, :maxlength => length, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %> <%= length %> <%= length == 1 ? "character." : "characters or fewer." %>
11
11
  </p>
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
3
- value = @object.send(name)
2
+ property_name = property[:name]
3
+ value = @object.send(property_name)
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :class => "vTimeField", :value => value.respond_to?(:strftime) ? value.strftime("%H:%M:%S") : nil, :label => label) %>
8
+ <%= text_field(property_name, :class => "vTimeField", :value => value.respond_to?(:strftime) ? value.strftime("%H:%M:%S") : nil, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %>
11
11
  </p>
@@ -1,11 +1,11 @@
1
1
  <%
2
- name = property[:name]
3
- value = @object.send(name)
2
+ property_name = property[:name]
3
+ value = @object.send(property_name)
4
4
  label = property[:pretty_name]
5
5
  required = !property[:nullable?]
6
6
  %>
7
7
  <div>
8
- <%= text_field(name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil, :label => label) %>
8
+ <%= text_field(property_name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil, :label => label) %>
9
9
  <p class="help">
10
10
  <%= required ? "Required." : "Optional." %>
11
11
  </p>
@@ -1,5 +1,7 @@
1
1
  <%
2
2
  params = request.params.except(:action, :controller, :model_name)
3
+ sort = params[:sort]
4
+ sort_reverse = params[:sort_reverse]
3
5
  %>
4
6
  <div id="content-main">
5
7
  <ul class="object-tools">
@@ -12,10 +14,10 @@
12
14
  <thead>
13
15
  <tr>
14
16
  <% @properties.each do |property| %>
15
- <% name = property[:name] %>
16
- <% pretty_name = property[:pretty_name] %>
17
- <th>
18
- <%= pretty_name %>
17
+ <% property_name = property[:name] %>
18
+ <% property_pretty_name = property[:pretty_name] %>
19
+ <th class="<%= sort == property_name.to_s ? sort_reverse ? 'sorted descending' : 'sorted ascending' : nil %>">
20
+ <a href="?<%= Merb::Parse.params_to_query_string(params.merge(:sort => property_name).reject{|key, value| key.to_sym == :sort_reverse}.merge(sort == property_name.to_s && sort_reverse != "true" ? {:sort_reverse => "true"} : {})) %>"><%= property_pretty_name %></a>
19
21
  </th>
20
22
  <% end %>
21
23
  </tr>
@@ -24,33 +26,9 @@
24
26
  <% @objects.each_with_index do |object, index| %>
25
27
  <tr class="row<%= index % 2 == 0 ? '1' : '2' %>">
26
28
  <% @properties.each do |property| %>
27
- <% type = property[:type] %>
28
- <% name = property[:name] %>
29
29
  <td>
30
30
  <a href="<%= url(:admin_edit, :model_name => @abstract_model.singular_name, :id => object.id) %>">
31
- <% case type %>
32
- <% when :boolean %>
33
- <% if object.send(name) == true %>
34
- <img alt="True" src="<%= image_path("icon-yes.gif") %>"/>
35
- <% else %>
36
- <img alt="False" src="<%= image_path("icon-no.gif") %>"/>
37
- <% end %>
38
- <% when :date_time %>
39
- <% value = object.send(name) %>
40
- <%= value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y, %I:%M%p") : nil %>
41
- <% when :date %>
42
- <% value = object.send(name) %>
43
- <%= value.respond_to?(:strftime) ? value.strftime("%b. %d, %Y") : nil %>
44
- <% when :time %>
45
- <% value = object.send(name) %>
46
- <%= value.respond_to?(:strftime) ? value.strftime("%I:%M%p") : nil %>
47
- <% when :string %>
48
- <%= object.send(name).to_s.truncate(50) %>
49
- <% when :text %>
50
- <%= object.send(name).to_s.truncate(50) %>
51
- <% else %>
52
- <%= object.send(name) %>
53
- <% end %>
31
+ <%= object_property(object, property) %>
54
32
  </a>
55
33
  </td>
56
34
  <% end %>
@@ -1,21 +1,24 @@
1
1
  module MerbAdmin
2
2
  class AbstractModel
3
3
  module ActiverecordSupport
4
- def count(options = {})
5
- model.count(options)
6
- end
7
-
8
4
  def get(id)
9
5
  model.find(id).extend(InstanceMethods)
10
- rescue
6
+ rescue ActiveRecord::RecordNotFound
11
7
  nil
12
8
  end
13
9
 
10
+ def count(options = {})
11
+ merge_order!(options)
12
+ model.count(options)
13
+ end
14
+
14
15
  def first(options = {})
16
+ merge_order!(options)
15
17
  model.first(options).extend(InstanceMethods)
16
18
  end
17
19
 
18
20
  def all(options = {})
21
+ merge_order!(options)
19
22
  model.all(options)
20
23
  end
21
24
 
@@ -93,7 +96,6 @@ module MerbAdmin
93
96
  :length => property.limit,
94
97
  :nullable? => property.null,
95
98
  :serial? => property.primary,
96
- :key? => property.primary,
97
99
  :flag_map => property.type.respond_to?(:flag_map) ? property.type.flag_map : nil,
98
100
  }
99
101
  end
@@ -101,6 +103,12 @@ module MerbAdmin
101
103
 
102
104
  private
103
105
 
106
+ def merge_order!(options)
107
+ @sort ||= options.delete(:sort) || "id"
108
+ @sort_order ||= options.delete(:sort_reverse) ? "desc" : "asc"
109
+ options.merge!(:order => ["#{@sort} #{@sort_order}"])
110
+ end
111
+
104
112
  def association_parent_model_lookup(association)
105
113
  case association.macro
106
114
  when :belongs_to
@@ -1,19 +1,22 @@
1
1
  module MerbAdmin
2
2
  class AbstractModel
3
3
  module DatamapperSupport
4
- def count(options = {})
5
- model.count(options)
6
- end
7
-
8
4
  def get(id)
9
5
  model.get(id).extend(InstanceMethods)
10
6
  end
11
7
 
8
+ def count(options = {})
9
+ merge_order!(options)
10
+ model.count(options)
11
+ end
12
+
12
13
  def first(options = {})
14
+ merge_order!(options)
13
15
  model.first(options).extend(InstanceMethods)
14
16
  end
15
17
 
16
18
  def all(options = {})
19
+ merge_order!(options)
17
20
  model.all(options)
18
21
  end
19
22
 
@@ -70,7 +73,7 @@ module MerbAdmin
70
73
  model.relationships.to_a.map do |name, association|
71
74
  {
72
75
  :name => name,
73
- :pretty_name => name.to_s.gsub('_', ' ').capitalize,
76
+ :pretty_name => name.to_s.gsub("_", " ").capitalize,
74
77
  :type => association_type_lookup(association),
75
78
  :parent_model => association.parent_model,
76
79
  :parent_key => association.parent_key.map{|r| r.name},
@@ -84,12 +87,11 @@ module MerbAdmin
84
87
  model.properties.map do |property|
85
88
  {
86
89
  :name => property.name,
87
- :pretty_name => property.name.to_s.gsub('_', ' ').capitalize,
90
+ :pretty_name => property.name.to_s.gsub(/_id$/, "").gsub("_", " ").capitalize,
88
91
  :type => type_lookup(property),
89
92
  :length => property.length,
90
93
  :nullable? => property.nullable?,
91
94
  :serial? => property.serial?,
92
- :key? => property.key?,
93
95
  :flag_map => property.type.respond_to?(:flag_map) ? property.type.flag_map : nil,
94
96
  }
95
97
  end
@@ -97,6 +99,12 @@ module MerbAdmin
97
99
 
98
100
  private
99
101
 
102
+ def merge_order!(options)
103
+ @sort ||= options.delete(:sort) || :id
104
+ @sort_order ||= options.delete(:sort_reverse) ? :desc : :asc
105
+ options.merge!(:order => [@sort.to_sym.send(@sort_order)])
106
+ end
107
+
100
108
  def association_type_lookup(association)
101
109
  if self.model == association.parent_model
102
110
  association.options[:max] > 1 ? :has_many : :has_one
data/lib/merb-admin.rb CHANGED
@@ -23,7 +23,7 @@ if defined?(Merb::Plugins)
23
23
 
24
24
  # Slice metadata
25
25
  self.description = "MerbAdmin is a Merb plugin that provides an easy-to-use interface for managing your data."
26
- self.version = "0.4.3"
26
+ self.version = "0.4.4"
27
27
  self.author = "Erik Michaels-Ober"
28
28
 
29
29
  # Stub classes loaded hook - runs before LoadClasses BootLoader
@@ -89,6 +89,38 @@ describe "MerbAdmin" do
89
89
  end
90
90
  end
91
91
 
92
+ describe "list with sort" do
93
+ before(:each) do
94
+ MerbAdmin::AbstractModel.new("Player").create(:team_id => rand(99999), :number => 42, :name => "Jackie Robinson", :sex => :male, :position => :second)
95
+ MerbAdmin::AbstractModel.new("Player").create(:team_id => rand(99999), :number => 32, :name => "Sandy Koufax", :sex => :male, :position => :pitcher)
96
+ @response = request(url(:admin_list, :model_name => "player"), :params => {:sort => "name"})
97
+ end
98
+
99
+ it "should respond sucessfully" do
100
+ @response.should be_successful
101
+ end
102
+
103
+ it "should be sorted correctly" do
104
+ @response.body.should contain(/Jackie Robinson.*Sandy Koufax/m)
105
+ end
106
+ end
107
+
108
+ describe "list with reverse sort" do
109
+ before(:each) do
110
+ MerbAdmin::AbstractModel.new("Player").create(:team_id => rand(99999), :number => 42, :name => "Jackie Robinson", :sex => :male, :position => :second)
111
+ MerbAdmin::AbstractModel.new("Player").create(:team_id => rand(99999), :number => 32, :name => "Sandy Koufax", :sex => :male, :position => :pitcher)
112
+ @response = request(url(:admin_list, :model_name => "player"), :params => {:sort => "name", :sort_reverse => "true"})
113
+ end
114
+
115
+ it "should respond sucessfully" do
116
+ @response.should be_successful
117
+ end
118
+
119
+ it "should be sorted correctly" do
120
+ @response.body.should contain(/Sandy Koufax.*Jackie Robinson/m)
121
+ end
122
+ end
123
+
92
124
  describe "list with 2 objects", :given => "two players exist" do
93
125
  before(:each) do
94
126
  MerbAdmin[:paginate] = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: merb-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
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-09-22 00:00:00 -07:00
12
+ date: 2009-09-29 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency