merb-admin 0.4.3

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.
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