merb-admin 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/LICENSE +20 -0
  2. data/README.markdown +58 -0
  3. data/Rakefile +64 -0
  4. data/app/controllers/application.rb +6 -0
  5. data/app/controllers/main.rb +145 -0
  6. data/app/helpers/application_helper.rb +64 -0
  7. data/app/helpers/main_helper.rb +122 -0
  8. data/app/views/layout/_message.html.erb +10 -0
  9. data/app/views/layout/dashboard.html.erb +34 -0
  10. data/app/views/layout/form.html.erb +48 -0
  11. data/app/views/layout/list.html.erb +42 -0
  12. data/app/views/main/_belongs_to.html.erb +29 -0
  13. data/app/views/main/_big_decimal.html.erb +12 -0
  14. data/app/views/main/_boolean.html.erb +7 -0
  15. data/app/views/main/_date.html.erb +12 -0
  16. data/app/views/main/_datetime.html.erb +12 -0
  17. data/app/views/main/_float.html.erb +12 -0
  18. data/app/views/main/_has_many.html.erb +16 -0
  19. data/app/views/main/_has_one.html.erb +30 -0
  20. data/app/views/main/_integer.html.erb +18 -0
  21. data/app/views/main/_properties.html.erb +18 -0
  22. data/app/views/main/_string.html.erb +12 -0
  23. data/app/views/main/_text.html.erb +11 -0
  24. data/app/views/main/_time.html.erb +12 -0
  25. data/app/views/main/_timestamp.html.erb +12 -0
  26. data/app/views/main/delete.html.erb +12 -0
  27. data/app/views/main/edit.html.erb +25 -0
  28. data/app/views/main/index.html.erb +22 -0
  29. data/app/views/main/list.html.erb +72 -0
  30. data/app/views/main/new.html.erb +22 -0
  31. data/lib/abstract_model.rb +67 -0
  32. data/lib/activerecord_support.rb +169 -0
  33. data/lib/datamapper_support.rb +157 -0
  34. data/lib/generic_support.rb +17 -0
  35. data/lib/merb-admin/merbtasks.rb +103 -0
  36. data/lib/merb-admin/slicetasks.rb +20 -0
  37. data/lib/merb-admin/spectasks.rb +53 -0
  38. data/lib/merb-admin.rb +99 -0
  39. data/public/images/arrow-down.gif +0 -0
  40. data/public/images/arrow-up.gif +0 -0
  41. data/public/images/changelist-bg.gif +0 -0
  42. data/public/images/changelist-bg_rtl.gif +0 -0
  43. data/public/images/chooser-bg.gif +0 -0
  44. data/public/images/chooser_stacked-bg.gif +0 -0
  45. data/public/images/default-bg-reverse.gif +0 -0
  46. data/public/images/default-bg.gif +0 -0
  47. data/public/images/deleted-overlay.gif +0 -0
  48. data/public/images/icon-no.gif +0 -0
  49. data/public/images/icon-unknown.gif +0 -0
  50. data/public/images/icon-yes.gif +0 -0
  51. data/public/images/icon_addlink.gif +0 -0
  52. data/public/images/icon_alert.gif +0 -0
  53. data/public/images/icon_calendar.gif +0 -0
  54. data/public/images/icon_changelink.gif +0 -0
  55. data/public/images/icon_clock.gif +0 -0
  56. data/public/images/icon_deletelink.gif +0 -0
  57. data/public/images/icon_error.gif +0 -0
  58. data/public/images/icon_searchbox.png +0 -0
  59. data/public/images/icon_success.gif +0 -0
  60. data/public/images/inline-delete-8bit.png +0 -0
  61. data/public/images/inline-delete.png +0 -0
  62. data/public/images/inline-restore-8bit.png +0 -0
  63. data/public/images/inline-restore.png +0 -0
  64. data/public/images/inline-splitter-bg.gif +0 -0
  65. data/public/images/nav-bg-grabber.gif +0 -0
  66. data/public/images/nav-bg-reverse.gif +0 -0
  67. data/public/images/nav-bg.gif +0 -0
  68. data/public/images/selector-add.gif +0 -0
  69. data/public/images/selector-addall.gif +0 -0
  70. data/public/images/selector-remove.gif +0 -0
  71. data/public/images/selector-removeall.gif +0 -0
  72. data/public/images/selector-search.gif +0 -0
  73. data/public/images/selector_stacked-add.gif +0 -0
  74. data/public/images/selector_stacked-remove.gif +0 -0
  75. data/public/images/tool-left.gif +0 -0
  76. data/public/images/tool-left_over.gif +0 -0
  77. data/public/images/tool-right.gif +0 -0
  78. data/public/images/tool-right_over.gif +0 -0
  79. data/public/images/tooltag-add.gif +0 -0
  80. data/public/images/tooltag-add_over.gif +0 -0
  81. data/public/images/tooltag-arrowright.gif +0 -0
  82. data/public/images/tooltag-arrowright_over.gif +0 -0
  83. data/public/javascripts/CollapsedFieldsets.js +85 -0
  84. data/public/javascripts/DateTimeShortcuts.js +255 -0
  85. data/public/javascripts/RelatedObjectLookups.js +96 -0
  86. data/public/javascripts/SelectBox.js +111 -0
  87. data/public/javascripts/SelectFilter2.js +113 -0
  88. data/public/javascripts/actions.js +39 -0
  89. data/public/javascripts/calendar.js +143 -0
  90. data/public/javascripts/core.js +176 -0
  91. data/public/javascripts/dateparse.js +233 -0
  92. data/public/javascripts/getElementsBySelector.js +167 -0
  93. data/public/javascripts/i18n.js +33 -0
  94. data/public/javascripts/master.js +0 -0
  95. data/public/javascripts/ordering.js +137 -0
  96. data/public/javascripts/timeparse.js +94 -0
  97. data/public/javascripts/urlify.js +140 -0
  98. data/public/stylesheets/base.css +746 -0
  99. data/public/stylesheets/changelists.css +269 -0
  100. data/public/stylesheets/dashboard.css +24 -0
  101. data/public/stylesheets/forms.css +327 -0
  102. data/public/stylesheets/global.css +142 -0
  103. data/public/stylesheets/ie.css +51 -0
  104. data/public/stylesheets/layout.css +29 -0
  105. data/public/stylesheets/login.css +54 -0
  106. data/public/stylesheets/master.css +2 -0
  107. data/public/stylesheets/null.css +1 -0
  108. data/public/stylesheets/patch-iewin.css +8 -0
  109. data/public/stylesheets/rtl.css +206 -0
  110. data/public/stylesheets/widgets.css +506 -0
  111. data/schema/migrations/001_create_divisions_migration.rb +15 -0
  112. data/schema/migrations/002_create_drafts_migration.rb +21 -0
  113. data/schema/migrations/003_create_leagues_migration.rb +13 -0
  114. data/schema/migrations/004_create_players_migration.rb +23 -0
  115. data/schema/migrations/005_create_teams_migration.rb +18 -0
  116. data/spec/controllers/main_spec.rb +25 -0
  117. data/spec/models/activerecord/division.rb +8 -0
  118. data/spec/models/activerecord/draft.rb +16 -0
  119. data/spec/models/activerecord/league.rb +6 -0
  120. data/spec/models/activerecord/player.rb +10 -0
  121. data/spec/models/activerecord/team.rb +11 -0
  122. data/spec/models/datamapper/division.rb +12 -0
  123. data/spec/models/datamapper/draft.rb +18 -0
  124. data/spec/models/datamapper/league.rb +11 -0
  125. data/spec/models/datamapper/player.rb +21 -0
  126. data/spec/models/datamapper/team.rb +15 -0
  127. data/spec/requests/main_spec.rb +475 -0
  128. data/spec/spec_helper.rb +112 -0
  129. metadata +209 -0
@@ -0,0 +1,7 @@
1
+ <%
2
+ name = property[:name]
3
+ label = property[:pretty_name]
4
+ %>
5
+ <div>
6
+ <%= check_box(name, :label => label) %>
7
+ </div>
@@ -0,0 +1,12 @@
1
+ <%
2
+ name = property[:name]
3
+ value = @object.send(name)
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?]
6
+ %>
7
+ <div>
8
+ <%= text_field(name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : nil, :label => label) %>
9
+ <p class="help">
10
+ <%= required ? "Required." : "Optional." %>
11
+ </p>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <%
2
+ name = property[:name]
3
+ value = @object.send(name)
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?]
6
+ %>
7
+ <div>
8
+ <%= text_field(name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil, :label => label) %>
9
+ <p class="help">
10
+ <%= required ? "Required." : "Optional." %>
11
+ </p>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <%
2
+ name = property[:name]
3
+ length = property[:length]
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?] || property[:serial?]
6
+ %>
7
+ <div>
8
+ <%= text_field(name, :maxlength => length, :label => label) %>
9
+ <p class="help">
10
+ <%= required ? "Required." : "Optional." %>
11
+ </p>
12
+ </div>
@@ -0,0 +1,16 @@
1
+ <%
2
+ name = association[:name]
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)
5
+ label = association[:pretty_name]
6
+ %>
7
+ <fieldset class="module aligned">
8
+ <h2><%= label %></h2>
9
+ <div class="form-row">
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>
13
+ <p class="help">Hold down "Control", or "Command" on a Mac, to select more than one.</p>
14
+ </div>
15
+ </div>
16
+ </fieldset>
@@ -0,0 +1,30 @@
1
+ <%
2
+ child_key = association[:child_key].first
3
+ name = association[:name]
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])
6
+ label = association[:pretty_name]
7
+ required = false
8
+ @properties.each do |property|
9
+ next unless property[:name] == child_key
10
+ required = true unless property[:nullable?]
11
+ end
12
+ %>
13
+ <fieldset class="module aligned">
14
+ <h2><%= label %></h2>
15
+ <div class="<%= @object.errors[child_key] ? "form-row errors" : "form-row" %>">
16
+ <% if @object.errors[child_key] %>
17
+ <ul class="errorlist">
18
+ <% @object.errors[child_key].each do |error| %>
19
+ <li><%= error %></li>
20
+ <% end %>
21
+ </ul>
22
+ <% end %>
23
+ <div>
24
+ <%= select(:name => "associations[#{name}][]", :id => name, :collection => collection, :include_blank => true, :selected => selected ? selected.id.to_s : nil, :label => label) %>
25
+ <p class="help">
26
+ <%= required ? "Required." : "Optional." %>
27
+ </p>
28
+ </div>
29
+ </div>
30
+ </fieldset>
@@ -0,0 +1,18 @@
1
+ <%
2
+ flag_map = property[:flag_map]
3
+ name = property[:name]
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?] || property[:serial?]
6
+ %>
7
+ <div>
8
+ <% if flag_map # Enum or Flag type %>
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) %>
11
+ <% else %>
12
+ <% length = property[:length] %>
13
+ <%= text_field(name, :maxlength => length, :label => label) %>
14
+ <p class="help">
15
+ <%= required ? "Required." : "Optional." %>
16
+ </p>
17
+ <% end %>
18
+ </div>
@@ -0,0 +1,18 @@
1
+ <fieldset class="module aligned">
2
+ <% belongs_to_keys = @abstract_model.belongs_to_associations.map{|b| b[:child_key].first} %>
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] %>
9
+ <ul class="errorlist">
10
+ <% @object.errors[name].each do |error| %>
11
+ <li><%= error %></li>
12
+ <% end %>
13
+ </ul>
14
+ <% end %>
15
+ <%= partial(property[:type].to_s, :property => property) -%>
16
+ </div>
17
+ <% end %>
18
+ </fieldset>
@@ -0,0 +1,12 @@
1
+ <%
2
+ name = property[:name]
3
+ length = property[:length]
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?]
6
+ %>
7
+ <div>
8
+ <%= text_field(name, :size => [50, length].min, :maxlength => length, :label => label) %>
9
+ <p class="help">
10
+ <%= required ? "Required." : "Optional." %> <%= length %> <%= length == 1 ? "character." : "characters or fewer." %>
11
+ </p>
12
+ </div>
@@ -0,0 +1,11 @@
1
+ <%
2
+ name = property[:name]
3
+ label = property[:pretty_name]
4
+ required = property[:nullable?]
5
+ %>
6
+ <div>
7
+ <%= text_area(name, :cols => 80, :label => label) %>
8
+ <p class="help">
9
+ <%= required ? "Required." : "Optional." %>
10
+ </p>
11
+ </div>
@@ -0,0 +1,12 @@
1
+ <%
2
+ name = property[:name]
3
+ value = @object.send(name)
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?]
6
+ %>
7
+ <div>
8
+ <%= text_field(name, :class => "vTimeField", :value => value.respond_to?(:strftime) ? value.strftime("%H:%M:%S") : nil, :label => label) %>
9
+ <p class="help">
10
+ <%= required ? "Required." : "Optional." %>
11
+ </p>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <%
2
+ name = property[:name]
3
+ value = @object.send(name)
4
+ label = property[:pretty_name]
5
+ required = !property[:nullable?]
6
+ %>
7
+ <div>
8
+ <%= text_field(name, :class => "vDateField", :value => value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S") : nil, :label => label) %>
9
+ <p class="help">
10
+ <%= required ? "Required." : "Optional." %>
11
+ </p>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <p>Are you sure you want to delete the <%= @abstract_model.pretty_name.downcase %>? All of the following related items will be deleted:</p>
2
+ <ul>
3
+ <li>
4
+ <%= link_to(@model_name, url(:admin_edit, :model_name => @abstract_model.singular_name, :id => @object.id))%>
5
+ </li>
6
+ </ul>
7
+ <%= form_for(@object, :action => url(:admin_destroy, :model_name => @abstract_model.singular_name, :id => @object.id), :method => :delete) do %>
8
+ <div>
9
+ <%= submit "Yes, I'm sure" %>
10
+ </div>
11
+ <% end =%>
12
+
@@ -0,0 +1,25 @@
1
+ <div id="content-main">
2
+ <%= form_for(@object, :action => url(:admin_update, :model_name => @abstract_model.singular_name, :id => @object.id)) do %>
3
+ <div>
4
+ <%= partial('properties', :properties => @properties) -%>
5
+ <% @abstract_model.belongs_to_associations.each do |association| %>
6
+ <%= partial('belongs_to', :association => association) -%>
7
+ <% end %>
8
+ <% @abstract_model.has_one_associations.each do |association| %>
9
+ <%= partial('has_one', :association => association) -%>
10
+ <% end %>
11
+ <% @abstract_model.has_many_associations.each do |association| %>
12
+ <%= partial('has_many', :association => association) -%>
13
+ <% end %>
14
+ <div class="submit-row" >
15
+ <%= submit "Save", :class => "default", :name => "_save" %>
16
+ <p class="deletelink-box">
17
+ <%= link_to("Delete", url(:admin_delete, :model_name => @abstract_model.singular_name, :id => @object.id), :class => "deletelink") %>
18
+ </p>
19
+ <%= submit "Save and add another", :name => "_add_another" %>
20
+ <%= submit "Save and continue editing", :name => "_continue" %>
21
+ </div>
22
+ </div>
23
+ <% end =%>
24
+
25
+ </div>
@@ -0,0 +1,22 @@
1
+ <div id="content-main">
2
+ <div class="module">
3
+ <table summary="Models available in the application.">
4
+ <caption>
5
+ <%= link_to("Models", url(:admin_dashboard), :class => "section") %>
6
+ </caption>
7
+ <% @abstract_models.each do |abstract_model| %>
8
+ <tr>
9
+ <th scope="row">
10
+ <%= link_to(abstract_model.plural_name.to_s.capitalize, url(:admin_list, :model_name => abstract_model.singular_name)) %>
11
+ </th>
12
+ <td>
13
+ <%= link_to("Add", url(:admin_new, :model_name => abstract_model.singular_name), :class => "addlink") %>
14
+ </td>
15
+ <td>
16
+ <%= link_to("Edit", url(:admin_list, :model_name => abstract_model.singular_name), :class => "changelink") %>
17
+ </td>
18
+ </tr>
19
+ <% end %>
20
+ </table>
21
+ </div>
22
+ </div>
@@ -0,0 +1,72 @@
1
+ <%
2
+ params = request.params.except(:action, :controller, :model_name)
3
+ %>
4
+ <div id="content-main">
5
+ <ul class="object-tools">
6
+ <li>
7
+ <%= link_to("Add #{@abstract_model.pretty_name.downcase}", url(:admin_new, :model_name => @abstract_model.singular_name), :class => "addlink") %>
8
+ </li>
9
+ </ul>
10
+ <div class="module" id="changelist">
11
+ <table cellspacing="0">
12
+ <thead>
13
+ <tr>
14
+ <% @properties.each do |property| %>
15
+ <% name = property[:name] %>
16
+ <% pretty_name = property[:pretty_name] %>
17
+ <th>
18
+ <%= pretty_name %>
19
+ </th>
20
+ <% end %>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% @objects.each_with_index do |object, index| %>
25
+ <tr class="row<%= index % 2 == 0 ? '1' : '2' %>">
26
+ <% @properties.each do |property| %>
27
+ <% type = property[:type] %>
28
+ <% name = property[:name] %>
29
+ <td>
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 %>
54
+ </a>
55
+ </td>
56
+ <% end %>
57
+ </tr>
58
+ <% end %>
59
+ </tbody>
60
+ </table>
61
+ <p class="paginator">
62
+ <% if @page_count.to_i > 1 %>
63
+ <%= paginate(@current_page, @page_count, :url => '?' + Merb::Parse.params_to_query_string(params)) %>
64
+ <% end %>
65
+ <%= @record_count %> <%= @record_count == 1 ? @abstract_model.pretty_name.downcase : @abstract_model.pretty_name.downcase.pluralize %>
66
+ <% if @page_count.to_i == 2 %>
67
+ <%= link_to("Show all", '?' + Merb::Parse.params_to_query_string(params.merge(:all => true)), :class => "showall") %>
68
+ <% end %>
69
+ </p>
70
+ </div>
71
+ </div>
72
+ <br class="clear" />
@@ -0,0 +1,22 @@
1
+ <div id="content-main">
2
+ <%= form_for(@object, :action => url(:admin_create, :model_name => @abstract_model.singular_name)) do %>
3
+ <div>
4
+ <%= partial('properties', :properties => @properties) -%>
5
+ <% @abstract_model.belongs_to_associations.each do |association| %>
6
+ <%= partial('belongs_to', :association => association) -%>
7
+ <% end %>
8
+ <% @abstract_model.has_one_associations.each do |association| %>
9
+ <%= partial('has_one', :association => association) -%>
10
+ <% end %>
11
+ <% @abstract_model.has_many_associations.each do |association| %>
12
+ <%= partial('has_many', :association => association) -%>
13
+ <% end %>
14
+ <div class="submit-row" >
15
+ <%= submit "Save", :class => "default", :name => "_save" %>
16
+ <%= submit "Save and add another", :name => "_add_another" %>
17
+ <%= submit "Save and continue editing", :name => "_continue" %>
18
+ </div>
19
+ </div>
20
+ <% end =%>
21
+
22
+ </div>
@@ -0,0 +1,67 @@
1
+ require 'generic_support'
2
+ require 'activerecord_support'
3
+ require 'datamapper_support'
4
+
5
+ module MerbAdmin
6
+ class AbstractModel
7
+ # Returns all models for a given Merb app
8
+ def self.all
9
+ return @models if @models
10
+ @models ||= []
11
+ case Merb.orm
12
+ when :activerecord
13
+ Dir.glob(Merb.dir_for(:model) / Merb.glob_for(:model)).each do |filename|
14
+ # FIXME: This heuristic for finding ActiveRecord models is too strict
15
+ File.read(filename).scan(/^class ([\w\d_\-:]+) < ActiveRecord::Base$/).flatten.each do |m|
16
+ model = lookup(m.to_s.to_sym)
17
+ @models << new(model) if model
18
+ end
19
+ end
20
+ @models.sort!{|a, b| a.model.to_s <=> b.model.to_s}
21
+ when :datamapper
22
+ DataMapper::Resource.descendants.each do |m|
23
+ # Remove DataMapperSessionStore because it's included by default
24
+ next if m == Merb::DataMapperSessionStore if Merb.const_defined?(:DataMapperSessionStore)
25
+ model = lookup(m.to_s.to_sym)
26
+ @models << new(model) if model
27
+ end
28
+ @models.sort!{|a, b| a.model.to_s <=> b.model.to_s}
29
+ else
30
+ raise "MerbAdmin does not support the #{Merb.orm} ORM"
31
+ end
32
+ end
33
+
34
+ # Given a symbol +model_name+, finds the corresponding model class
35
+ def self.lookup(model_name)
36
+ begin
37
+ model = const_get(model_name)
38
+ rescue NameError
39
+ raise "MerbAdmin could not find model #{model_name}"
40
+ end
41
+
42
+ case Merb.orm
43
+ when :activerecord
44
+ return model if model.superclass == ActiveRecord::Base
45
+ when :datamapper
46
+ return model if model.include?(DataMapper::Resource)
47
+ end
48
+ nil
49
+ end
50
+
51
+ attr_accessor :model
52
+
53
+ def initialize(model)
54
+ model = self.class.lookup(model.to_s.camel_case) unless model.is_a?(Class)
55
+ @model = model
56
+ self.extend(GenericSupport)
57
+ case Merb.orm
58
+ when :activerecord
59
+ self.extend(ActiverecordSupport)
60
+ when :datamapper
61
+ self.extend(DatamapperSupport)
62
+ else
63
+ raise "MerbAdmin does not support the #{Merb.orm} ORM"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,169 @@
1
+ module MerbAdmin
2
+ class AbstractModel
3
+ module ActiverecordSupport
4
+ def count(options = {})
5
+ model.count(options)
6
+ end
7
+
8
+ def get(id)
9
+ model.find(id).extend(InstanceMethods)
10
+ rescue
11
+ nil
12
+ end
13
+
14
+ def first(options = {})
15
+ model.first(options).extend(InstanceMethods)
16
+ end
17
+
18
+ def all(options = {})
19
+ model.all(options)
20
+ end
21
+
22
+ def all_in(ids, options = {})
23
+ options[:conditions] = ["id IN (?)", ids]
24
+ model.all(options)
25
+ end
26
+
27
+ def paginated(options = {})
28
+ page = options.delete(:page) || 1
29
+ per_page = options.delete(:per_page) || MerbAdmin[:per_page]
30
+
31
+ page_count = (count(options).to_f / per_page).ceil
32
+
33
+ options.merge!({
34
+ :limit => per_page,
35
+ :offset => (page - 1) * per_page
36
+ })
37
+
38
+ [page_count, all(options)]
39
+ end
40
+
41
+ def create(params = {})
42
+ model.create(params)
43
+ end
44
+
45
+ def new(params = {})
46
+ model.new(params).extend(InstanceMethods)
47
+ end
48
+
49
+ def destroy_all!
50
+ model.all.each do |object|
51
+ object.destroy
52
+ end
53
+ end
54
+
55
+ def has_many_associations
56
+ associations.select do |association|
57
+ association[:type] == :has_many
58
+ end
59
+ end
60
+
61
+ def has_one_associations
62
+ associations.select do |association|
63
+ association[:type] == :has_one
64
+ end
65
+ end
66
+
67
+ def belongs_to_associations
68
+ associations.select do |association|
69
+ association[:type] == :belongs_to
70
+ end
71
+ end
72
+
73
+ def associations
74
+ model.reflect_on_all_associations.map do |association|
75
+ {
76
+ :name => association.name,
77
+ :pretty_name => association.name.to_s.gsub('_', ' ').capitalize,
78
+ :type => association.macro,
79
+ :parent_model => association_parent_model_lookup(association),
80
+ :parent_key => association_parent_key_lookup(association),
81
+ :child_model => association_child_model_lookup(association),
82
+ :child_key => association_child_key_lookup(association),
83
+ }
84
+ end
85
+ end
86
+
87
+ def properties
88
+ model.columns.map do |property|
89
+ {
90
+ :name => property.name.to_sym,
91
+ :pretty_name => property.human_name,
92
+ :type => property.type,
93
+ :length => property.limit,
94
+ :nullable? => property.null,
95
+ :serial? => property.primary,
96
+ :key? => property.primary,
97
+ :flag_map => property.type.respond_to?(:flag_map) ? property.type.flag_map : nil,
98
+ }
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def association_parent_model_lookup(association)
105
+ case association.macro
106
+ when :belongs_to
107
+ association.klass
108
+ when :has_one, :has_many
109
+ association.active_record
110
+ else
111
+ raise "Unknown association type"
112
+ end
113
+ end
114
+
115
+ def association_parent_key_lookup(association)
116
+ [:id]
117
+ end
118
+
119
+ def association_child_model_lookup(association)
120
+ case association.macro
121
+ when :belongs_to
122
+ association.active_record
123
+ when :has_one, :has_many
124
+ association.klass
125
+ else
126
+ raise "Unknown association type"
127
+ end
128
+ end
129
+
130
+ def association_child_key_lookup(association)
131
+ case association.macro
132
+ when :belongs_to
133
+ ["#{association.class_name.snake_case}_id".to_sym]
134
+ when :has_one, :has_many
135
+ [association.primary_key_name.to_sym]
136
+ else
137
+ raise "Unknown association type"
138
+ end
139
+ end
140
+
141
+ module InstanceMethods
142
+ def id
143
+ super
144
+ end
145
+
146
+ def save
147
+ super
148
+ end
149
+
150
+ def destroy
151
+ super
152
+ end
153
+
154
+ def update_attributes(attributes)
155
+ super
156
+ end
157
+
158
+ def errors
159
+ super
160
+ end
161
+
162
+ def clear_association(association)
163
+ association.clear
164
+ end
165
+ end
166
+
167
+ end
168
+ end
169
+ end