diff --git a/.gitignore b/.gitignore index 728011c..b8d01f0 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ spec/dummy/.sass-cache test_apps/**/db/migrate/**/*.rb test_apps/**/db/* test_apps/**/spec/**/*.rb +test_apps/**/app/upmin/**/*.rb diff --git a/Rakefile b/Rakefile index b6041c9..9bb7684 100644 --- a/Rakefile +++ b/Rakefile @@ -13,6 +13,17 @@ end task :default => "spec:all" +def update_files + # Drop and reload spec files + sh "rm -rf spec/" + sh "cp -R ../../spec spec" + sh "cp ../../.rspec .rspec" + + # Drop and reload Upmin::Model files + sh "rm -rf app/upmin/" + sh "cp -R ../../test_app_upmin app/upmin" +end + namespace :spec do # Full bundle install & test. %w(active_record_32 active_record_40 active_record_41 active_record_42 will_paginate).each do |gemfile| @@ -33,10 +44,7 @@ namespace :spec do sh "RAILS_ENV=test bundle exec rake db:drop db:create db:migrate --quiet" - # Drop and reload spec files - sh "rm -rf spec/" - sh "cp -R ../../spec spec" - sh "cp ../../.rspec .rspec" + update_files # Run tests sh "bundle exec rake" @@ -50,10 +58,7 @@ namespace :spec do Dir.chdir("test_apps/#{gemfile}") puts "Re-testing in #{`pwd`}. Bundle install and migration updates will NOT happen!" - # Drop and reload spec files - sh "rm -rf spec/" - sh "cp -R ../../spec spec" - sh "cp ../../.rspec .rspec" + update_files # Run tests sh "bundle exec rake" diff --git a/app/assets/javascripts/upmin/application.js b/app/assets/javascripts/upmin/application.js index d2e1ed3..41d5b67 100644 --- a/app/assets/javascripts/upmin/application.js +++ b/app/assets/javascripts/upmin/application.js @@ -13,7 +13,6 @@ //= require jquery //= require jquery_ujs //= require ./jquery-clockpicker -//= require ./moment //= require ./pikaday //= require ./helpers //= require_tree . diff --git a/app/assets/stylesheets/upmin/base.css.scss b/app/assets/stylesheets/upmin/base.css.scss index 081164c..6c4bf92 100644 --- a/app/assets/stylesheets/upmin/base.css.scss +++ b/app/assets/stylesheets/upmin/base.css.scss @@ -53,6 +53,7 @@ body { } + .wizard span {padding: 12px 12px 10px 12px; margin-right:5px; background:#efefef; position:relative; display:inline-block; } .wizard span:before {width:0px; height:0px; border-top: 20px inset transparent; border-bottom: 20px inset transparent; border-left: 20px solid #fff; position: absolute; content: ""; top: 0; left: 0;} .wizard span:after {width:0px; height:0px; border-top: 20px inset transparent; border-bottom: 20px inset transparent; border-left: 20px solid #efefef; position: absolute; content: ""; top: 0; right: -20px; z-index:2;} diff --git a/app/assets/stylesheets/upmin/instances.css.scss b/app/assets/stylesheets/upmin/instances.css.scss index e87ad53..1c5a451 100644 --- a/app/assets/stylesheets/upmin/instances.css.scss +++ b/app/assets/stylesheets/upmin/instances.css.scss @@ -1,8 +1,14 @@ @import "colors"; +.search-result-link { + .upmin-model { + margin-bottom: 10px; + } +} + .upmin-model { padding: 20px; - margin: 10px 0px 10px 0; + margin: 10px 0px 250px 0; border: 1px solid #eee; border-left-width: 5px; border-radius: 3px; @@ -65,6 +71,10 @@ padding: 9px; margin-bottom: 0; min-height: 40px; + + &.action-well { + margin-bottom: 32px; + } } a.active-tag-link { diff --git a/app/controllers/upmin/models_controller.rb b/app/controllers/upmin/models_controller.rb index ca07999..c8a34c7 100644 --- a/app/controllers/upmin/models_controller.rb +++ b/app/controllers/upmin/models_controller.rb @@ -6,8 +6,9 @@ class ModelsController < ApplicationController before_filter :set_model, only: [:show, :update, :action] before_filter :set_page, only: [:search] + before_filter :set_query, only: [:search] - before_filter :set_method, only: [:action] + before_filter :set_action, only: [:action] before_filter :set_arguments, only: [:action] def dashboard @@ -25,34 +26,28 @@ def new # POST /:model_name def create @model = @klass.new - instance = @model.instance + raw_model = @model.model - args = params[@klass.name.underscore] - transforms = args.delete(:transforms) || {} + args = params[@klass.underscore_name] args.each do |key, value| - # TODO(jon): Figure out a better way to do transforms. - # This could cause issues and is exploitable, but it - # should be fine for now since this is only on admin pages - if transforms[key] and not value.blank? - value = transform(transforms, key, value) - end + # TODO(jon): Figure out a way to do transforms. # TODO(jon): Remove duplicate code between update and create if args["#{key}_is_nil"] == "1" - instance.send("#{key}=", nil) + raw_model.send("#{key}=", nil) else if key.ends_with?("_is_nil") # Skip this, since we use the non _is_nil arg. else - instance.send("#{key}=", value) + raw_model.send("#{key}=", value) end end end - if instance.save - flash[:notice] = "#{@klass.humanized_name(:singular)} created successfully with id=#{instance.id}." - redirect_to(upmin_model_path(@model.path_hash)) + if raw_model.save + flash[:notice] = "#{@klass.humanized_name(:singular)} created successfully with id=#{raw_model.id}." + redirect_to(@model.path) else flash.now[:alert] = "#{@klass.humanized_name(:singular)} was NOT created." render(:new) @@ -62,33 +57,26 @@ def create # PUT /:model_name/:id def update - instance = @model.instance - updates = params[@klass.name.underscore] - transforms = updates.delete(:transforms) || {} - updates.each do |key, value| - # TODO(jon): Figure out a better way to do transforms. - # This could cause issues and is exploitable, but it - # should be fine for now since this is only on admin pages - if transforms[key] and not value.blank? - value = transform(transforms, key, value) - end + raw_model = @model.model + updates = params[@klass.underscore_name] + updates.each do |key, value| # TODO(jon): Remove duplicate code between update and create if updates["#{key}_is_nil"] == "1" - instance.send("#{key}=", nil) + raw_model.send("#{key}=", nil) else if key.ends_with?("_is_nil") # Skip this, since we use the non _is_nil arg. else - instance.send("#{key}=", value) + raw_model.send("#{key}=", value) end end end - if instance.save + if raw_model.save flash[:notice] = "#{@klass.humanized_name(:singular)} updated successfully." - redirect_to(upmin_model_path(@model.path_hash)) + redirect_to(@model.path) else flash.now[:alert] = "#{@klass.humanized_name(:singular)} was NOT updated." render(:show) @@ -96,15 +84,15 @@ def update end def search - @q = @klass.ransack(params[:q]) - @results = Upmin::Paginator.paginate(@q.result(distinct: true), @page, 30) + # @q = @klass.ransack(params[:q]) + # @results = Upmin::Paginator.paginate(@q.result(distinct: true), @page, 30) end def action - # begin - response = @model.perform_action(params[:method], @arguments) - flash[:notice] = "Action successfully performed with a response of: #{response}" - redirect_to(upmin_model_path(@model.path_hash)) + @response = @action.perform(@arguments) + + flash[:notice] = "Action successfully performed with a response of: #{@response}" + redirect_to(@model.path) # rescue Exception => e # flash.now[:alert] = "Action failed with the error message: #{e.message}" # render(:show) @@ -112,22 +100,27 @@ def action end private - - def set_klass - @klass = Upmin::Klass.find(params[:klass]) - raise "Invalid klass name" if @klass.nil? + def set_query + @query = Upmin::Query.new(@klass, params[:q], page: @page, per_page: 30) end def set_model - @model = @klass.find(params[:id]) + @model = @klass.new(id: params[:id]) end - def set_method - @method = params[:method].to_sym + def set_klass + @klass = Upmin::Model.find_class(params[:klass]) + end + + def set_action + action_name = params[:method].to_sym + @action = @model.actions.select{ |action| action.name == action_name }.first + + raise Upmin::InvalidAction.new(params[:method]) unless @action end def set_arguments - arguments = params[@method] || {} + arguments = params[@action.name] || {} @arguments = {} arguments.each do |k, v| unless k.ends_with?("_is_nil") @@ -136,6 +129,7 @@ def set_arguments end end end + @arguments = ActiveSupport::HashWithIndifferentAccess.new(@arguments) end def set_page diff --git a/app/helpers/upmin/instances_helper.rb b/app/helpers/upmin/instances_helper.rb deleted file mode 100644 index e07c7bd..0000000 --- a/app/helpers/upmin/instances_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Upmin - module InstancesHelper - - def models_path - return "#{root_path}models/" - end - - def instance_path(instance) - return "#{models_path}#{instance.class.name}/#{instance.id}" - end - - end -end diff --git a/app/views/layouts/upmin/_navbar.html.haml b/app/views/layouts/upmin/_navbar.html.haml index a9f56c9..1c86532 100644 --- a/app/views/layouts/upmin/_navbar.html.haml +++ b/app/views/layouts/upmin/_navbar.html.haml @@ -9,7 +9,7 @@ %span.icon-bar #navbar-main.navbar-collapse.collapse %ul.nav.navbar-nav - - Upmin::Klass.all.each do |m| + - Upmin::Model.all.each do |m| %li - %a{href: upmin_search_path(klass: m.name)} - = m.name.pluralize + %a{href: m.search_path} + = m.humanized_name(:plural) diff --git a/app/views/upmin/models/new.html.haml b/app/views/upmin/models/new.html.haml index ef37cd9..7ee9738 100644 --- a/app/views/upmin/models/new.html.haml +++ b/app/views/upmin/models/new.html.haml @@ -9,13 +9,13 @@ %button.close{"data-dismiss" => "alert", type: "button"} %span{"aria-hidden" => "true"} × = alert - - if @model.instance.errors.any? + - if @model.errors.any? %ul - - @model.instance.errors.each do |field, error| + - @model.errors.each do |field, error| %li %b = field = error .row .col-sm-12 - = up_model(@model.instance) + = up_render(@model) diff --git a/app/views/upmin/models/search.html.haml b/app/views/upmin/models/search.html.haml index c32213a..6618eea 100644 --- a/app/views/upmin/models/search.html.haml +++ b/app/views/upmin/models/search.html.haml @@ -2,16 +2,16 @@ .row .col-md-8 -# TODO(jon): Add pagination w/ search results - = up_search_results(@q, @results) + = up_render(@query) %br - = up_paginate(@results) + = up_paginate(@query.paginated_results) %br %br .col-md-4 -# TODO(jon): Implement up_search_box - = up_search_box(@klass) + = up_render(@klass) .new-button-wrapper - %a.btn.btn-block.btn-success{href: upmin_new_model_path(klass: @klass.name)} + %a.btn.btn-block.btn-success{href: upmin_new_model_path(klass: @klass.model_class_name)} Create a new = @klass.humanized_name(:singular) %br diff --git a/app/views/upmin/models/show.html.haml b/app/views/upmin/models/show.html.haml index ef37cd9..7ee9738 100644 --- a/app/views/upmin/models/show.html.haml +++ b/app/views/upmin/models/show.html.haml @@ -9,13 +9,13 @@ %button.close{"data-dismiss" => "alert", type: "button"} %span{"aria-hidden" => "true"} × = alert - - if @model.instance.errors.any? + - if @model.errors.any? %ul - - @model.instance.errors.each do |field, error| + - @model.errors.each do |field, error| %li %b = field = error .row .col-sm-12 - = up_model(@model.instance) + = up_render(@model) diff --git a/app/views/upmin/partials/actions/_action.html.haml b/app/views/upmin/partials/actions/_action.html.haml index 0a81baa..23e7aee 100644 --- a/app/views/upmin/partials/actions/_action.html.haml +++ b/app/views/upmin/partials/actions/_action.html.haml @@ -1,24 +1,9 @@ +%h4{style: "color: #333;"} + = action.title .well.action-well - = form_tag(upmin_action_path(upmin_model.path_hash.merge(method: action_name))) do - - upmin_model.action_parameters(action_name).each do |param_type, param_name| - - - next if param_type == :block # Skip blocks - - .form-group - = label(action_name, param_name, param_name.to_s.capitalize.gsub("_", " ")) - - - if param_type == :opt - .input-group - = text_field(action_name, param_name, class: "form-control") - .input-group-addon.nilable-addon - .form-group - %label{for: "#{action_name}_#{param_name}_is_nil"} - Do Not Provide - = check_box(action_name, "#{param_name}_is_nil", class: "boolean") - %small - * Optional - - else - = text_field(action_name, param_name, class: "form-control") - + = form_tag(action.path, class: action.name) do + - action.parameters.each do |parameter| + -# = Upmin::Railties::RenderHelpers.parameter_partials(parameter) + = up_render(parameter) = submit_tag("Submit", class: "btn btn-primary") diff --git a/app/views/upmin/partials/associations/_associations.html.haml b/app/views/upmin/partials/associations/_associations.html.haml index e1cc342..b1dc93b 100644 --- a/app/views/upmin/partials/associations/_associations.html.haml +++ b/app/views/upmin/partials/associations/_associations.html.haml @@ -1,14 +1,12 @@ -- if associations.any? - %p - - associations.each do |u| - - nested_upmin_model = Upmin::Model.new(u) - %a.active-tag-link{href: upmin_model_path(nested_upmin_model.path_hash)} - %span.label{class: nested_upmin_model.color} - = nested_upmin_model.title +%h5 + = association.title -- else +- if association.empty? %p.well None - - +- else + - association.upmin_values.each do |m| + %a.active-tag-link{href: m.path} + %span.label{class: m.color} + = m.title diff --git a/app/views/upmin/partials/attributes/_boolean.html.haml b/app/views/upmin/partials/attributes/_boolean.html.haml index feb47cf..90441e9 100644 --- a/app/views/upmin/partials/attributes/_boolean.html.haml +++ b/app/views/upmin/partials/attributes/_boolean.html.haml @@ -1,8 +1,10 @@ -- boolean ||= "false" +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name -- if editable && f = form_builder - = f.check_box(attr_name, value: boolean, class: "boolean") + - if attribute.editable? && f = form_builder + = f.check_box(attribute.name, value: boolean, class: "boolean") -- else - %p.well - = f.check_box(attr_name, {value: boolean, class: "boolean", disabled: "disabled"}) + - else + %p.well + = attribute.value diff --git a/app/views/upmin/partials/attributes/_datetime.html.haml b/app/views/upmin/partials/attributes/_datetime.html.haml index 1c5e173..0af4994 100644 --- a/app/views/upmin/partials/attributes/_datetime.html.haml +++ b/app/views/upmin/partials/attributes/_datetime.html.haml @@ -1,36 +1,38 @@ -- datetime ||= nil -- datetime_str = datetime.utc.iso8601 if datetime -- if editable && f = form_builder - .row.datetime-attribute{class: form_id} - = f.hidden_field(attr_name, value: datetime_str) - - # TODO(jon): Figure out a better way to do transforms. This works for now though. - = f.hidden_field("transforms[#{attr_name}]", value: "DateTime#parse") - - .col-xs-12.col-md-4 - .input-group.pikadate - %input.form-control{type: :text, value: datetime_str, id: "#{form_id}-date"} - %span.input-group-addon - %span.glyphicon.glyphicon-calendar - - .col-xs-12.col-md-4 - .input-group.clockpicker{"data-align" => "top", "data-autoclose" => "true", "data-placement" => "right"} - %input.form-control{:type => "text", :value => datetime_str, id: "#{form_id}-time"} - %span.input-group-addon - %span.glyphicon.glyphicon-time - - .col-xs-12.col-md-4 - %small - Date & Time are shown in UTC. If not time is provided, the current time in UTC will be used. - - - - content_for(:javascript) do - :javascript - $(document).ready(function() { - window.Upmin.Attributes.DateTime("#{form_id}"); - }); - - -- else - %p.well - -# TODO(jon): Make this show an uneditable date and time field. - = datetime +- iso8601 = attribute.value.nil? ? nil : attribute.value.utc.iso8601 + +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name + + - if attribute.editable? && f = form_builder + .row.datetime-attribute{class: attribute.form_id} + = f.hidden_field(attribute.name, value: iso8601) + + .col-xs-12.col-md-4 + .input-group.pikadate + %input.form-control{type: :text, value: iso8601, id: "#{attribute.form_id}-date"} + %span.input-group-addon + %span.glyphicon.glyphicon-calendar + + .col-xs-12.col-md-4 + .input-group.clockpicker{"data-align" => "top", "data-autoclose" => "true", "data-placement" => "right"} + %input.form-control{type: "text", value: iso8601, id: "#{attribute.form_id}-time"} + %span.input-group-addon + %span.glyphicon.glyphicon-time + + .col-xs-12.col-md-4 + %small + Date & Time are shown in UTC. If no time is provided, the current time in UTC will be used. + + + - content_for(:javascript) do + :javascript + $(document).ready(function() { + window.Upmin.Attributes.DateTime("#{attribute.form_id}"); + }); + + + - else + %p.well + -# TODO(jon): Make this show an uneditable date and time field. + = iso8601 diff --git a/app/views/upmin/partials/attributes/_decimal.html.haml b/app/views/upmin/partials/attributes/_decimal.html.haml index 2d9fd52..d574c08 100644 --- a/app/views/upmin/partials/attributes/_decimal.html.haml +++ b/app/views/upmin/partials/attributes/_decimal.html.haml @@ -1,7 +1,10 @@ -- decimal ||= nil -- if editable && f = form_builder - = f.text_field(attr_name, value: decimal, class: "form-control") +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name -- else - %p.well - = decimal + - if attribute.editable? && f = form_builder + = f.text_field(attribute.name, value: attribute.value, class: "form-control") + + - else + %p.well + = attribute.value diff --git a/app/views/upmin/partials/attributes/_float.html.haml b/app/views/upmin/partials/attributes/_float.html.haml index 117c99e..9660a34 100644 --- a/app/views/upmin/partials/attributes/_float.html.haml +++ b/app/views/upmin/partials/attributes/_float.html.haml @@ -1,7 +1,11 @@ -- float ||= nil -- if editable && f = form_builder - = f.text_field(attr_name, value: float, class: "form-control") -- else - %p.well - = float +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name + + - if attribute.editable? && f = form_builder + = f.text_field(attribute.name, value: attribute.value, class: "form-control") + + - else + %p.well + = attribute.value diff --git a/app/views/upmin/partials/attributes/_integer.html.haml b/app/views/upmin/partials/attributes/_integer.html.haml index 035339a..825d6a3 100644 --- a/app/views/upmin/partials/attributes/_integer.html.haml +++ b/app/views/upmin/partials/attributes/_integer.html.haml @@ -1,7 +1,11 @@ -- integer ||= nil -- if editable && f = form_builder - = f.number_field(attr_name, value: integer, class: "form-control") -- else - %p.well - = integer +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name + + - if attribute.editable? && f = form_builder + = f.number_field(attribute.name, value: attribute.value, class: "form-control") + + - else + %p.well + = attribute.value diff --git a/app/views/upmin/partials/attributes/_nilable.html.haml b/app/views/upmin/partials/attributes/_nilable.html.haml deleted file mode 100644 index d46984a..0000000 --- a/app/views/upmin/partials/attributes/_nilable.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- if editable && f = form_builder - -# TODO(jon): Find a better place for code like this. Kinda sucks to have it in the view. - - is_nil = upmin_model.attribute(attr_name).nil? && !upmin_model.instance.new_record? - .input-group - = up_attribute(upmin_model.instance, attr_name, locals: { form_builder: form_builder }) - .input-group-addon.nilable-addon - .form-group - %label{for: "#{upmin_model.klass.name.underscore}_#{attr_name}_is_nil"} - Make This Nil - = check_box(upmin_model.klass.name.underscore, "#{attr_name}_is_nil", class: "boolean", checked: is_nil, value: is_nil) - -- else - %p.well - = nilable diff --git a/app/views/upmin/partials/attributes/_progress_bar.html.haml b/app/views/upmin/partials/attributes/_progress_bar.html.haml index 855256c..bbf55b4 100644 --- a/app/views/upmin/partials/attributes/_progress_bar.html.haml +++ b/app/views/upmin/partials/attributes/_progress_bar.html.haml @@ -1,3 +1,4 @@ +-# TODO(jon): Turn this into a widget - state ||= nil - states ||= [] %ol.progbar{data: { "progbar-steps" => states.length }} diff --git a/app/views/upmin/partials/attributes/_string.html.haml b/app/views/upmin/partials/attributes/_string.html.haml index 025b317..fece22b 100644 --- a/app/views/upmin/partials/attributes/_string.html.haml +++ b/app/views/upmin/partials/attributes/_string.html.haml @@ -1,7 +1,18 @@ -- string ||= nil -- if editable && f = form_builder - = f.text_field(attr_name, value: string, class: "form-control") +- is_nil = model.new_record? ? false : attribute.value.nil? -- else - %p.well - = string +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name + + - if attribute.editable? && f = form_builder + .input-group + = f.text_field(attribute.name, value: attribute.value, class: "form-control") + .input-group-addon.nilable-addon + .form-group + %label{for: "#{attribute.form_id}_is_nil"} + Make this Nil + = check_box(model.underscore_name, "#{attribute.name}_is_nil", class: "boolean", checked: is_nil, value: is_nil) + + - else + %p.well + = attribute.value diff --git a/app/views/upmin/partials/attributes/_text.html.haml b/app/views/upmin/partials/attributes/_text.html.haml index d0ed7fe..d2d7e28 100644 --- a/app/views/upmin/partials/attributes/_text.html.haml +++ b/app/views/upmin/partials/attributes/_text.html.haml @@ -1,7 +1,9 @@ -- text ||= nil -- if editable && f = form_builder - = f.text_area(attr_name, value: text, rows: 4, class: "form-control") +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name + - if attribute.editable? && f = form_builder + = f.text_area(attribute.name, value: attribute.value, rows: 4, class: "form-control") -- else - %p.well - = text + - else + %p.well + = attribute.value diff --git a/app/views/upmin/partials/attributes/_unknown.html.haml b/app/views/upmin/partials/attributes/_unknown.html.haml index efb20fc..a0c4e6a 100644 --- a/app/views/upmin/partials/attributes/_unknown.html.haml +++ b/app/views/upmin/partials/attributes/_unknown.html.haml @@ -1,3 +1,5 @@ -- unknown ||= nil -%p.well - = unknown +.form-group{class: attribute.errors? ? "has-error" : ""} + %label{for: attribute.form_id} + = attribute.label_name + %p.well + = attribute.value diff --git a/app/views/upmin/partials/models/_model.html.haml b/app/views/upmin/partials/models/_model.html.haml index fab8fd2..80f3502 100644 --- a/app/views/upmin/partials/models/_model.html.haml +++ b/app/views/upmin/partials/models/_model.html.haml @@ -1,72 +1,44 @@ --# The following are all available to you here: --# unknown - This is the model passed into the up_model method. --# This attribute is always present in any partial rendered --# by Upmin, and will always match the name of the partial --# *UNLESS* nil, true, or false are passed in. --# upmin_model - This is an Upmin::Model instantiated with unknown --# and is always present. --# --# In general, the upmin_model makes it significantly easier to --# render a view, so I suggest using it. It is way simpler than --# trying to find all associations for a model on your own. --# --# Just an FYI: upmin_model.instance == unknown - -.upmin-model{class: upmin_model.color} - -# Display the model title as "Model # ID" +.upmin-model{class: model.color} %h3 - = upmin_model.title + = model.title %br %br %h3{style: "color: #333;"} Attributes %hr - -# Create a form to wrap the attributes in. - -# TODO(jon): Update the URL with a decent helper? - = form_for(upmin_model.instance, url: upmin_model_path(upmin_model.path_hash), html: { method: :put }) do |f| - - -# Render each attribute - - upmin_model.klass.attributes.each do |attribute| - - any_errors = model.errors[attribute].any? - .form-group{class: any_errors ? "has-error" : ""} - -# = f.label(attribute.to_s) # Not using this because it drops _id and this isn't always desirable - - %label{for: upmin_model.attribute_form_id(attribute)} - = upmin_model.attribute_label_name(attribute) - - if upmin_model.attribute_type(attribute) == :string - = up_attribute(upmin_model.instance, attribute, locals: { form_builder: f }, as: :nilable) - - else - = up_attribute(upmin_model.instance, attribute, locals: { form_builder: f }) + .attributes + -# Yes this is meant to be model.model - this is the raw rails model instance. + = form_for(model.model, url: model.path, html: { method: :put }) do |f| - = f.submit("Save", class: "btn btn-primary") + -# Render each attribute + - model.attributes.each do |attribute| + = up_render(attribute, locals: { form_builder: f }) + = f.submit("Save", class: "btn btn-primary") - - if upmin_model.klass.associations.any? + - if model.associations.any? %br %br %br %h3{style: "color: #333;"} Associations %hr - - upmin_model.klass.associations.each do |association| - %h5 - = association.to_s.humanize - = up_association(upmin_model.instance, association, limit: 5) + .associations + - model.associations.each do |association| + = up_render(association) - - if upmin_model.klass.actions.any? + + - if model.actions.any? %br %br %br %h3{style: "color: #333;"} Actions %hr - - upmin_model.klass.actions.each do |action| - %h4{style: "color: #333;"} - = action.to_s.capitalize.humanize - = up_action(upmin_model.instance, action) - - + .actions + - model.actions.each do |action| + = up_render(action) diff --git a/app/views/upmin/partials/models/_new_model.html.haml b/app/views/upmin/partials/models/_new_model.html.haml index 42c7a42..31a32a0 100644 --- a/app/views/upmin/partials/models/_new_model.html.haml +++ b/app/views/upmin/partials/models/_new_model.html.haml @@ -1,44 +1,22 @@ --# The following are all available to you here: --# unknown - This is the model passed into the up_model method. --# This attribute is always present in any partial rendered --# by Upmin, and will always match the name of the partial --# *UNLESS* nil, true, or false are passed in. --# upmin_model - This is an Upmin::Model instantiated with unknown --# and is always present. --# --# In general, the upmin_model makes it significantly easier to --# render a view, so I suggest using it. It is way simpler than --# trying to find all associations for a model on your own. --# --# Just an FYI: upmin_model.instance == unknown -.upmin-model{class: upmin_model.color} - -# Display the model title as "Model # ID" +.upmin-model{class: model.color} %h3 Create a - = upmin_model.klass.humanized_name(:singular) + = model.humanized_name(:singular) %br %br %h3{style: "color: #333;"} Attributes %hr - -# Create a form to wrap the attributes in. - -# TODO(jon): Update the URL with a decent helper? - = form_for(upmin_model.instance, url: upmin_create_model_path(klass: @klass.name), html: { method: :post }) do |f| - -# Render each attribute - - upmin_model.klass.attributes.each do |attribute| - - any_errors = new_model.errors[attribute].any? - .form-group{class: any_errors ? "has-error" : ""} - -# = f.label(attribute.to_s) # Not using this because it drops _id and this isn't always desirable + .attributes + -# Yes this is meant to be model.model - this is the raw rails model instance. + = form_for(model.model, url: model.create_path, html: { method: :post }) do |f| - %label{for: upmin_model.attribute_form_id(attribute)} - = upmin_model.attribute_label_name(attribute) - - if upmin_model.attribute_type(attribute) == :string - = up_attribute(upmin_model.instance, attribute, locals: { form_builder: f }, as: :nilable) - - else - = up_attribute(upmin_model.instance, attribute, locals: { form_builder: f }) + -# Render each attribute + - model.attributes.each do |attribute| + = up_render(attribute, locals: { form_builder: f }) - = f.submit("Create", class: "btn btn-primary") + = f.submit("Create", class: "btn btn-primary") diff --git a/app/views/upmin/partials/parameters/_block_parameter.haml b/app/views/upmin/partials/parameters/_block_parameter.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/upmin/partials/parameters/_opt_parameter.html.haml b/app/views/upmin/partials/parameters/_opt_parameter.html.haml new file mode 100644 index 0000000..560ccae --- /dev/null +++ b/app/views/upmin/partials/parameters/_opt_parameter.html.haml @@ -0,0 +1,14 @@ +.form-group + %label{for: parameter.form_id} + = parameter.label_name + + .input-group + = text_field(action.name, parameter.name, class: "form-control") + .input-group-addon.nilable-addon + .form-group + %label{for: parameter.nil_form_id} + Do Not Provide + = check_box(parameter.action.name, "#{parameter.name}_is_nil", class: "boolean") + + %small + * Optional diff --git a/app/views/upmin/partials/parameters/_req_parameter.html.haml b/app/views/upmin/partials/parameters/_req_parameter.html.haml new file mode 100644 index 0000000..db7068e --- /dev/null +++ b/app/views/upmin/partials/parameters/_req_parameter.html.haml @@ -0,0 +1,4 @@ +.form-group + %label{for: parameter.form_id} + = parameter.label_name + = text_field(action.name, parameter.name, class: "form-control") diff --git a/app/views/upmin/partials/search_boxes/_ransack_search_box.html.haml b/app/views/upmin/partials/search_boxes/_ransack_search_box.html.haml index 115ea72..a0886a3 100644 --- a/app/views/upmin/partials/search_boxes/_ransack_search_box.html.haml +++ b/app/views/upmin/partials/search_boxes/_ransack_search_box.html.haml @@ -3,11 +3,10 @@ = klass.humanized_name -# Upmin default search uses the ransack gem, see: https://github.com/activerecord-hackery/ransack -= form_tag(upmin_search_path(klass: klass.name), method: :get) do += form_tag(klass.search_path, method: :get) do - klass.attributes.each do |attr_name| - type = klass.attribute_type(attr_name) - -# TODO(jon): Make sure these retain their data on a search -# TODO(jon): Break these into partials possibly? - if type == :string .form-group @@ -34,4 +33,4 @@ = date_field(:q, "#{attr_name}_lteq", class: "form-control") = submit_tag("Search", class: "btn btn-primary btn-block") - = link_to("Clear All", upmin_search_path(klass: klass.name), class: "btn btn-default btn-block") + = link_to("Clear All", klass.search_path, class: "btn btn-default btn-block") diff --git a/app/views/upmin/partials/search_results/_result.html.haml b/app/views/upmin/partials/search_results/_result.html.haml deleted file mode 100644 index 78d5f43..0000000 --- a/app/views/upmin/partials/search_results/_result.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -%a.search-result-link{href: upmin_model_path(upmin_model.path_hash)} - .upmin-model{class: upmin_model.color} - %dl.dl-horizontal - - @klass.attributes.each do |attribute| - %dt - = upmin_model.attribute_label_name(attribute) - %dd - = upmin_model.attribute(attribute) diff --git a/app/views/upmin/partials/search_results/_results.html.haml b/app/views/upmin/partials/search_results/_results.html.haml index bb12f03..20fd48a 100644 --- a/app/views/upmin/partials/search_results/_results.html.haml +++ b/app/views/upmin/partials/search_results/_results.html.haml @@ -1,2 +1,9 @@ -- results.each do |result| - = up_search_result(result) +- query.upmin_results.each do |result| + %a.search-result-link{href: result.path} + .upmin-model{class: result.color} + %dl.dl-horizontal + - result.attributes.each do |attribute| + %dt + = attribute.label_name + %dd + = attribute.value diff --git a/lib/upmin/action.rb b/lib/upmin/action.rb new file mode 100644 index 0000000..475eab7 --- /dev/null +++ b/lib/upmin/action.rb @@ -0,0 +1,50 @@ +module Upmin + class Action + include Upmin::AutomaticDelegation + + attr_reader :model + attr_reader :name + + def initialize(model, action_name, options = {}) + @model = model + @name = action_name.to_sym + end + + def title + return name.to_s.humanize + end + + def path + return upmin_action_path(klass: model.model_class_name, id: model.id, method: name) + end + + def parameters + return @parameters if defined?(@parameters) + @parameters = [] + model.method(name).parameters.each do |param_type, param_name| + @parameters << Upmin::Parameter.new(self, param_name, type: param_type) + end + return @parameters + end + alias_method :arguments, :parameters + + def perform(arguments) + array = [] + parameters.each do |parameter| + if parameter.type == :req + unless arguments[parameter.name] + raise Upmin::MissingArgument.new(parameter.name) + end + array << arguments[parameter.name] + elsif parameter.type == :opt + array << arguments[parameter.name] if arguments[parameter.name] + else # :block - skip it + end + end + return model.send(name, *array) + end + + private + + end +end diff --git a/lib/upmin/admin.rb b/lib/upmin/admin.rb index 59591fc..64574d9 100644 --- a/lib/upmin/admin.rb +++ b/lib/upmin/admin.rb @@ -1,10 +1,17 @@ require "upmin" require "upmin/engine" -require "upmin/klass" +require "upmin/automatic_delegation" +require "upmin/errors" +require "upmin/paginator" + require "upmin/model" +require "upmin/attribute" +require "upmin/association" +require "upmin/action" +require "upmin/parameter" +require "upmin/query" -require "upmin/paginator" # Monkey patch code into rails require "upmin/railties/active_record" diff --git a/lib/upmin/association.rb b/lib/upmin/association.rb new file mode 100644 index 0000000..53ae5e1 --- /dev/null +++ b/lib/upmin/association.rb @@ -0,0 +1,91 @@ +module Upmin + class Association + attr_reader :model + attr_reader :name + + def initialize(model, assoc_name, options = {}) + @model = model + @name = assoc_name.to_sym + end + + def value + # TODO(jon): Add some way to handle exceptions. + return model.send(name) + end + + def title + return name.to_s.humanize + end + + def upmin_values(options = {}) + options[:limit] ||= 5 + if reflection.collection? + vals = [value.limit(options[:limit])].flatten + else + vals = [value] + end + + return vals.map{ |m| m.upmin_model } + end + + def type + return @type if defined?(@type) + + if reflection + @type = reflection.foreign_type.to_s.gsub(/_type$/, "") + if collection? + @type = @type.pluralize.to_sym + else + @type = @type.to_sym + end + else + @type = :unknown + end + + if @type == :unknown + @type = infer_type_from_value + end + + return @type + end + + def collection? + if reflection + return reflection.collection? + elsif value + return value.responds_to?(:each) + else + return false + end + end + + def empty? + return ![value].flatten.any? + end + + + private + + def reflection + return @reflection if defined?(@reflection) + @reflection = model.model_class.reflect_on_all_associations.select do |r| + r.name == name + end.first + return @reflection + end + + def infer_type_from_value + if first = [value].flatten.first + type = first.class.name.underscore + if collection? + return type.pluralize.to_sym + else + return type.to_sym + end + else + return :unknown + end + end + + end +end diff --git a/lib/upmin/attribute.rb b/lib/upmin/attribute.rb new file mode 100644 index 0000000..a6cb8ef --- /dev/null +++ b/lib/upmin/attribute.rb @@ -0,0 +1,83 @@ +module Upmin + class Attribute + attr_reader :model + attr_reader :name + + def initialize(model, attr_name, options = {}) + @model = model + @name = attr_name.to_sym + end + + def value + # TODO(jon): Add some way to handle exceptions. + return model.send(name) + end + + def type + # TODO(jon): Add a way to override with widgets? + return @type if defined?(@type) + + # Try to get it from the model_class + @type = model.class.attribute_type(name) + + # If we still don't know the type, try to infer it from the value + if @type == :unknown + @type = infer_type_from_value + end + + return @type + end + + def editable? + case name.to_sym + when :id + return false + when :created_at + return false + when :updated_at + return false + else + # TODO(jon): Add a way to declare which attributes are editable and which are not later. + return model.respond_to?("#{name}=") + end + end + + def errors? + return model.errors[name].any? + end + + def label_name + return name.to_s.gsub(/_/, " ").capitalize + end + + def form_id + return "#{model.underscore_name}_#{name}" + end + + def nilable_id + return "#{form_id}_is_nil" + end + + + private + + def infer_type_from_value + class_sym = value.class.to_s.underscore.to_sym + if class_sym == :false_class || class_sym == :true_class + return :boolean + elsif class_sym == :nil_class + return :unknown + elsif class_sym == :fixnum + return :integer + elsif class_sym == :big_decimal + return :decimal + elsif class_sym == :"active_support/time_with_zone" + return :datetime + else + # This should prevent any classes from being skipped, but we may not have an exhaustive list yet. + return class_sym + end + end + + end +end diff --git a/lib/upmin/automatic_delegation.rb b/lib/upmin/automatic_delegation.rb new file mode 100644 index 0000000..86cb3e0 --- /dev/null +++ b/lib/upmin/automatic_delegation.rb @@ -0,0 +1,72 @@ +module Upmin + module AutomaticDelegation + extend ActiveSupport::Concern + + # Delegates missing instance methods to the source model. + def method_missing(method, *args, &block) + if delegatable?(method) + self.class.delegate(method, to: :model) + send(method, *args, &block) + else + return super + end + end + + def delegatable?(method) + return model.respond_to?(method) + end + + def delegated?(method) + return self.class.delegated?(method) + end + + def respond_to?(method) + super || delegatable?(method) + end + + def method(method_name) + if delegated?(method_name) + return model.method(method_name) + else + return super(method_name) + end + rescue NameError => e + if delegatable?(method_name) + self.class.delegate(method_name, to: :model) + return method(method_name) + else + super(method_name) + end + end + + module ClassMethods + # Proxies missing class methods to the source class. + def method_missing(method, *args, &block) + return super unless delegatable?(method) + + model_class.send(method, *args, &block) + end + + def delegatable?(method) + model_class? && model_class.respond_to?(method) + end + + def delegate(method, *args) + @delegated ||= [] + @delegated << method.to_sym + super(method, *args) + end + + def delegated?(method) + @delegated ||= [] + return @delegated.include?(method.to_sym) + end + + # Avoids reloading the model class when ActiveSupport clears autoloaded + # dependencies in development mode. + def before_remove_const + end + end + + end +end diff --git a/lib/upmin/engine.rb b/lib/upmin/engine.rb index a705703..ec62661 100644 --- a/lib/upmin/engine.rb +++ b/lib/upmin/engine.rb @@ -3,5 +3,7 @@ module Upmin class Engine < ::Rails::Engine isolate_namespace Upmin + + config.autoload_paths << "#{::Rails.root}/app/upmin/models" end end diff --git a/lib/upmin/errors.rb b/lib/upmin/errors.rb new file mode 100644 index 0000000..66bdd17 --- /dev/null +++ b/lib/upmin/errors.rb @@ -0,0 +1,37 @@ +module Upmin + class InvalidAction < ArgumentError + def initialize(action) + super("Invalid action: #{action}") + end + end + + class MissingArgument < ArgumentError + def initialize(arg) + super("Missing argument: #{arg}") + end + end + + class ArgumentError < ArgumentError + def initialize(arg) + super("Invalid argument: #{arg}") + end + end + + class MissingPartial < ::StandardError + def initialize(data) + super("Could not find a matching partial with the following data: #{data.as_json}") + end + end + + class UninferrableAdminError < NameError + def initialize(klass) + super("Could not infer an Admin class for #{klass}.") + end + end + + class UninferrableSourceError < NameError + def initialize(klass) + super("Could not infer a source for #{klass}.") + end + end +end diff --git a/lib/upmin/klass.rb b/lib/upmin/klass.rb deleted file mode 100644 index cad5f94..0000000 --- a/lib/upmin/klass.rb +++ /dev/null @@ -1,170 +0,0 @@ -module Upmin - class Klass - - attr_accessor :model - attr_accessor :color - - def initialize(model, options = {}) - self.model = model - - if options[:color] - self.color = options[:color] - end - end - - def new(*args) - m = model.new(*args) - return Upmin::Model.new(m) - end - - # Exposing a model method, but wrapping the result in - # an Upmin::Model - def find(*args) - return Upmin::Model.new(model.find(*args)) - end - - def ransack(*args) - return model.ransack(*args) - end - - - # Returns all of the upmin attributes for the ActiveRecord model - # referenced by this Klass object. - def attributes - return model.upmin_attributes - end - - # Returns the type for an attribute by checking the columns - # hash. If no match can be found, :unknown is returned. - # NOTE - the Upmin::Model version of this will look at the - # actual contents of the attr_name if :unknown is returned, - # so this version is more accurate if you can use it. - def attribute_type(attr_name) - if connection_adapter = model.columns_hash[attr_name.to_s] - return connection_adapter.type - else - return :unknown - end - end - - - # Returns all of the upmin actions for the ActiveRecord model - # referenced by this Klass object. - def actions - return model.upmin_actions - end - - # Returns all associations that are not used in through associations - # eg - an Order's products, but not an order's product_orders that link the two. - def associations - return @associations if defined?(@associations) - - all = [] - ignored = [] - model.reflect_on_all_associations.each do |reflection| - all << reflection.name.to_sym - - # We need to remove the ignored later because we don't know the order they come in. - if reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection) - ignored << reflection.options[:through] - end - end - - return @associations = all - ignored - end - - # Tries to find an association type based on the reflection - def association_type(assoc_name) - reflection = reflections.select { |r| r.name == assoc_name.to_sym }.first - - if reflection - return reflection.foreign_type.to_s.gsub(/_type$/, "").pluralize.to_sym - else - return :unknown - end - end - - def plural_associations - return model.reflect_on_all_associations - .select{ |r| r.collection? } - .map{ |r| r.name.to_sym } - end - - def reflections - return model.reflect_on_all_associations - end - - - - ## Methods for prettying up things to display them in views etc. - - # Returns the class name, split at camelCase, - # with the last word pluralized if it is plural. - def humanized_name(type = :plural) - names = model.name.split(/(?=[A-Z])/) - if type == :plural - names[names.length-1] = names.last.pluralize - end - return names.join(" ") - end - - # Returns the class name, capitalized as it would be with User.name or OrderShipment.name - "User", or "OrderShipment" - def name - return model.name - end - - def path_hash - return { - klass: klass.name - } - end - - - - ## Class Methods - - # Takes a Rails ActiveRecord or the name of one and returns an - # Upmin::Klass instance of the model. - def Klass.find(model) - return all.select{|k| k.name == model.to_s}.first - end - - # Returns an array of all Klass instances - def Klass.all - return @all if defined?(@all) - all = [] - - models.each_with_index do |model, i| - klass = Klass.new(model, color: colors[i % colors.length]) - all << klass - end - - return @all = all - end - - def Klass.colors - return [ - :light_blue, - :blue_green, - :red, - :yellow, - :orange, - :purple, - :dark_blue, - :dark_red, - :green - ] - end - - def Klass.models - # If Rails - ::Rails.application.eager_load! - rails_models = ::ActiveRecord::Base.descendants.select do |m| - m.to_s != "ActiveRecord::SchemaMigration" - end - - return rails_models - end - - end -end diff --git a/lib/upmin/model.rb b/lib/upmin/model.rb index f5e7307..220a909 100644 --- a/lib/upmin/model.rb +++ b/lib/upmin/model.rb @@ -1,137 +1,288 @@ module Upmin class Model + include Upmin::Engine.routes.url_helpers + include Upmin::AutomaticDelegation - attr_accessor :instance - attr_accessor :klass + attr_reader :model + alias_method :object, :model # For delegation - def initialize(instance, options = {}) - self.instance = instance - self.klass = Upmin::Klass.find(instance.class.name) + def initialize(model = nil, options = {}) + if model.is_a?(Hash) + unless model.has_key?(:id) + raise ":id or model instance is required." + end + @model = self.model_class.find(model[:id]) + elsif model.nil? + @model = self.model_class.new + else + @model = model + end + end + + def path + if new_record? + return upmin_new_model_path(klass: model_class_name) + else + return upmin_model_path(klass: model_class_name, id: id) + end + end + + def create_path + return upmin_create_model_path(klass: model_class_name) end - ## Methods for rendering in views def title - return "#{klass.humanized_name(:singular)} # #{instance.id}" + return "#{humanized_name(:singular)} # #{id}" + end + + def attributes + return @attributes if defined?(@attributes) + @attributes = [] + self.class.attributes.each do |attr_name| + @attributes << Upmin::Attribute.new(self, attr_name) + end + return @attributes + end + + def associations + return @associations if defined?(@associations) + @associations = [] + self.class.associations.each do |assoc_name| + @associations << Upmin::Association.new(self, assoc_name) + end + return @associations + end + + def actions + return @actions if defined?(@actions) + @actions = [] + self.class.actions.each do |action_name| + @actions << Upmin::Action.new(self, action_name) + end + return @actions end + + + ########################################################### + ### Delegated instance methods ### + ########################################################### + + # TODO(jon): Figure out why delegations here weren't working in 3.2 tests + # delegate(:color, to: :class) def color - return klass.color - end - - def path_hash - return { - klass: klass.name, - id: instance.id - } - end - - def new_record? - return instance.new_record? - end - - ## Methods for getting attributes, associations, etc and anything relevant to them. - - - # Returns the type of an attribute. If it is nil and we can't - # figure it out from the db columns we just fall back to - # :unknown - def attribute_type(attr_name) - type = klass.attribute_type(attr_name) - - if type == :unknown - # See if we can deduce it by looking at the data - data = attribute(attr_name) - class_sym = data.class.to_s.underscore.to_sym - if class_sym == :false_class || class_sym == :true_class - type = :boolean - elsif class_sym == :nil_class - type = :unknown - elsif class_sym == :fixnum - type = :integer - elsif class_sym == :big_decimal - type = :decimal - elsif class_sym == :"active_support/time_with_zone" - type = :datetime - else - # This should prevent any classes from being skipped, but we may not have an exhaustive list yet. - type = class_sym + return self.class.color + end + # delegate(:humanized_name, to: :class) + def humanized_name(type = :plural) + return self.class.humanized_name(type) + end + # delegate(:underscore_name, to: :class) + def underscore_name + return self.class.underscore_name + end + # delegate(:model_class, to: :class) + def model_class + return self.class.model_class + end + # delegate(:model_class_name, to: :class) + def model_class_name + return self.class.model_class_name + end + + + + + ########################################################### + ### Class methods ### + ########################################################### + + def Model.associations + return @associations if defined?(@associations) + + all = [] + ignored = [] + model_class.reflect_on_all_associations.each do |reflection| + all << reflection.name.to_sym + + # We need to remove the ignored later because we don't know the order they come in. + if reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection) + ignored << reflection.options[:through] end end - return type + return @associations = all - ignored + end + + def Model.find_class(model) + return find_or_create_class(model.to_s) + end + + def Model.find_or_create_class(model_name) + ::Rails.application.eager_load! + return "Admin#{model_name}".constantize + rescue NameError + eval("class ::Admin#{model_name} < Upmin::Model; end") + return "Admin#{model_name}".constantize + end + + # Returns all admin models. + def Model.all + return @all if defined?(@all) + @all = [] + all_models.each do |m| + @all << find_or_create_class(m.name) + end + return @all + end + + def Model.all_models + # If Rails + ::Rails.application.eager_load! + rails_models = ::ActiveRecord::Base.descendants.select do |m| + m.to_s != "ActiveRecord::SchemaMigration" + end + + return rails_models end - # Returns whether or not the attr_name is an attribute that can be edited. - def attribute_editable?(attr_name) - attr_name = attr_name.to_sym - return false if attr_name == :id - return false if attr_name == :created_at - return false if attr_name == :updated_at - # TODO(jon): Add a way to declare which attributes are editable and which are not later. - return instance.respond_to?("#{attr_name}=") + def Model.model_class + @model_class ||= inferred_model_class end - # Returns the value of the attr_name method - def attribute(attr_name) - attr_name = attr_name.to_sym - # TODO(jon): Add some way to handle exceptions. Probably a custom error that we display. - return instance.send(attr_name) + def Model.model_class? + return model_class + rescue Upmin::UninferrableSourceError + return false end - def attribute_form_id(attr_name) - return "#{klass.name.underscore}_#{attr_name}" + def Model.model_class + return @model_class ||= inferred_model_class end - def attribute_label_name(attr_name) - return attr_name.to_s.gsub(/_/, " ").capitalize + def Model.inferred_model_class + name = model_class_name + return name.constantize + rescue NameError => error + raise if name && !error.missing_name?(name) + raise Upmin::UninferrableSourceError.new(self) end + def Model.model_class_name + raise NameError if name.nil? || name.demodulize !~ /Admin.+$/ + return name.demodulize[5..-1] + end - # Returns the type of an association. If we can't figure it - # out we fall back to :unknown - def association_type(assoc_name) - type = klass.association_type(assoc_name) - if type == :unknown && data = association(assoc_name).first - type = data.class.name.underscore + def Model.humanized_name(type = :plural) + names = model_class_name.split(/(?=[A-Z])/) + if type == :plural + names[names.length-1] = names.last.pluralize end - return type + return names.join(" ") end - def association(assoc_name, options = {}) - association = instance.send(assoc_name) - if association.respond_to?(:each) - # We have a collection, at least we hope we do. - if options[:limit] && association.respond_to?(:limit) - association = association.limit(5) - end + def Model.underscore_name(type = :singular) + if type == :singular + return model_class_name.underscore + else + return model_class_name.pluralize.underscore end - return association end - def action_parameters(action) - instance.method(action).parameters + def Model.search_path + return Upmin::Engine.routes.url_helpers.upmin_search_path(klass: model_class_name) + end + + def Model.color + return @color if defined?(@color) + @color = Model.next_color + return @color + end + + def Model.next_color + @color_index ||= 0 + next_color = colors[@color_index] + @color_index = (@color_index + 1) % colors.length + return next_color end - def perform_action(action, arguments) - unless klass.actions.include?(action.to_sym) - raise "Invalid action: #{action}" + def Model.colors(*colors) + @colors = colors if colors.any? + @colors ||= [ + :light_blue, + :blue_green, + :red, + :yellow, + :orange, + :purple, + :dark_blue, + :dark_red, + :green + ] + return @colors + end + + + + ########################################################### + ### Customization methods for Admin classes ### + ########################################################### + + # Add a single attribute to upmin attributes. + # If this is called before upmin_attributes + # the attributes will not include any defaults + # attributes. + def Model.attribute(attribute = nil) + @extra_attrs = [] unless defined?(@extra_attrs) + @extra_attrs << attribute.to_sym if attribute + end + + # Sets the attributes to the provided attributes # if any are any provided. + # If no attributes are provided then the + # attributes are set to the default attributes of + # the model class. + def Model.attributes(*attributes) + @extra_attrs = [] unless defined?(@extra_attrs) + + if attributes.any? + @attributes = attributes.map{|a| a.to_sym} end - params = action_parameters(action) - params_array = [] - params.each do |param_type, param_name| - if param_type == :req - raise "Missing argument: #{param_name}" unless arguments[param_name] - params_array << arguments[param_name] - elsif param_type == :opt - params_array << arguments[param_name] if arguments[param_name] - else # :block or ?? - next - end + @attributes ||= model_class.attribute_names.map{|a| a.to_sym} + return (@attributes + @extra_attrs).uniq + end + + def Model.attribute_type(attribute) + if adapter = model_class.columns_hash[attribute.to_s] + return adapter.type + else + return :unknown end - return instance.send(action, *params_array) end + # Add a single action to upmin actions. If this is called + # before upmin_actions the actions will not include any defaults + # actions. + def Model.action(action) + @actions ||= [] + + action = action.to_sym + @actions << action unless @actions.include?(action) + end + + # Sets the upmin_actions to the provided actions if any are + # provided. + # If no actions are provided, and upmin_actions hasn't been defined, + # then the upmin_actions are set to the default actions. + # Returns the upmin_actions + def Model.actions(*actions) + if actions.any? + # set the actions + @actions = actions.map{|a| a.to_sym} + end + @actions ||= [] + return @actions + end end end diff --git a/lib/upmin/parameter.rb b/lib/upmin/parameter.rb new file mode 100644 index 0000000..c5d4dc9 --- /dev/null +++ b/lib/upmin/parameter.rb @@ -0,0 +1,43 @@ +module Upmin + class Parameter + attr_reader :action + attr_reader :name + + def initialize(action, parameter_name, options = {}) + @action = action + @name = parameter_name.to_sym + @type = options[:type] if options[:type] + end + + def model + return action.model + end + + def title + return name.to_s.humanize + end + + def label_name + name.to_s.capitalize.gsub("_", " ") + end + + def type + return @type if defined?(@type) + @type = action.model.method(action.name).parameters.select do |param_type, param_name| + param_name == name + end.first.first || :req + return @type + end + + def form_id + return "#{action.name}_#{name}" + end + + def nil_form_id + return "#{form_id}_is_nil" + end + + private + + end +end diff --git a/lib/upmin/query.rb b/lib/upmin/query.rb new file mode 100644 index 0000000..2a7f16d --- /dev/null +++ b/lib/upmin/query.rb @@ -0,0 +1,42 @@ +module Upmin + class Query + + attr_reader :klass + attr_reader :search_options + attr_reader :page + attr_reader :per_page + + delegate(:underscore_name, to: :klass) + + def initialize(klass, search_options = {}, options = {}) + @klass = klass + @search_options = search_options + @page = options[:page] + @per_page = options[:per_page] + end + + def results + return klass.ransack(search_options).result(distinct: true) + end + + def paginated_results + return @paginated_results if defined?(@paginated_results) + if page && per_page + pr = Upmin::Paginator.paginate(results, page, per_page) + else + pr = Upmin::Paginator.paginate(results) + end + @paginated_results = pr + return @paginated_results + end + + def upmin_results + return @upmin_results if defined?(@upmin_results) + @upmin_results = paginated_results.map{ |r| r.upmin_model } + return @upmin_results + end + + private + + end +end diff --git a/lib/upmin/railtie.rb b/lib/upmin/railtie.rb index 2edae23..0a69495 100644 --- a/lib/upmin/railtie.rb +++ b/lib/upmin/railtie.rb @@ -1,3 +1,5 @@ +require 'rails/railtie' + module Upmin require 'rails' class Railtie < Rails::Railtie diff --git a/lib/upmin/railties/active_record.rb b/lib/upmin/railties/active_record.rb index eeaa987..5d8580d 100644 --- a/lib/upmin/railties/active_record.rb +++ b/lib/upmin/railties/active_record.rb @@ -4,60 +4,15 @@ module Upmin::Railties module ActiveRecord extend ::ActiveSupport::Concern + def upmin_model + klass = Upmin::Model.find_class(self.class) + return klass.new(self) + end + included do end module ClassMethods - - # Add a single attribute to upmin attributes. If this is called - # before upmin_attributes the attributes will not include any defaults - # attributes. - def upmin_attribute(attribute = nil) - @upmin_extra_attrs = [] unless defined?(@upmin_extra_attrs) - @upmin_extra_attrs << attribute.to_sym if attribute - end - - # Sets the upmin_attributes to the provided attributes if any are - # provided. - # If no attributes are provided, and upmin_attributes hasn't been defined, - # then the upmin_attributes are set to the default attributes. - # Returns the upmin_attributes - def upmin_attributes(*attributes) - @upmin_extra_attrs = [] unless defined?(@upmin_extra_attrs) - - if attributes.any? - @upmin_attributes = attributes.map{|a| a.to_sym} - end - - @upmin_attributes ||= attribute_names.map{|a| a.to_sym} - return (@upmin_attributes + @upmin_extra_attrs).uniq - end - - - # Add a single action to upmin actions. If this is called - # before upmin_actions the actions will not include any defaults - # actions. - def upmin_action(action) - @upmin_actions ||= [] - - action = action.to_sym - @upmin_actions << action unless @upmin_actions.include?(action) - end - - # Sets the upmin_actions to the provided actions if any are - # provided. - # If no actions are provided, and upmin_actions hasn't been defined, - # then the upmin_actions are set to the default actions. - # Returns the upmin_actions - def upmin_actions(*actions) - if actions.any? - # set the actions - @upmin_actions = actions.map{|a| a.to_sym} - end - @upmin_actions ||= [] - return @upmin_actions - end - end end end diff --git a/lib/upmin/railties/render.rb b/lib/upmin/railties/render.rb index 7996e31..51e72a1 100644 --- a/lib/upmin/railties/render.rb +++ b/lib/upmin/railties/render.rb @@ -2,105 +2,41 @@ module Upmin::Railties module Render - def up_model(model, options = {}) - options[:locals] ||= {} - - upmin_model = Upmin::Model.new(model) - options[:locals][:upmin_model] ||= upmin_model - - partials = RenderHelpers.model_partials(upmin_model, options) - return up_render(model, partials, options, :up_model) - end - - def up_attribute(model, attr_name, options = {}) - options[:locals] ||= {} - options[:locals][:model] ||= model - options[:locals][:attr_name] = attr_name - - upmin_model = Upmin::Model.new(model) - options[:locals][:upmin_model] ||= upmin_model - - options[:locals][:form_id] ||= upmin_model.attribute_form_id(attr_name) - # Only fill this in if it was never set so the user can override this. - if options[:locals][:editable].nil? - options[:locals][:editable] = upmin_model.attribute_editable?(attr_name) + # Render method that is used by upmin-admin. Tries to render partials in order, passing data in as the :object, along with options. + def up_render(data, options = {}) + if data.is_a?(Upmin::Model) + options = RenderHelpers.model_options(data, options) + partials = RenderHelpers.model_partials(data, options) + + elsif data.is_a?(Upmin::Attribute) + options = RenderHelpers.attribute_options(data, options) + partials = RenderHelpers.attribute_partials(data, options) + + elsif data.is_a?(Upmin::Association) + options = RenderHelpers.association_options(data, options) + partials = RenderHelpers.association_partials(data, options) + + elsif data.is_a?(Upmin::Action) + options = RenderHelpers.action_options(data, options) + partials = RenderHelpers.action_partials(data, options) + + elsif data.is_a?(Upmin::Parameter) + options = RenderHelpers.parameter_options(data, options) + partials = RenderHelpers.parameter_partials(data, options) + + elsif data.is_a?(Upmin::Query) + options = RenderHelpers.search_results_options(data, options) + partials = RenderHelpers.search_results_partials(data, options) + + elsif Upmin::Model.all.include?(data) + # Probably rendering a search box + options = RenderHelpers.search_box_options(data, options) + partials = RenderHelpers.search_box_partials(data, options) + + else + raise Upmin::ArgumentError.new(data) end - - partials = RenderHelpers.attribute_partials(upmin_model, attr_name, options) - - data = upmin_model.attribute(attr_name) - return up_render(data, partials, options, :up_attribute) - end - - def up_association(model, assoc_name, options = {}) - options[:locals] ||= {} - options[:locals][:model] ||= model - options[:locals][:assoc_name] = assoc_name - - upmin_model = Upmin::Model.new(model) - options[:locals][:upmin_model] ||= upmin_model - - partials = RenderHelpers.association_partials(upmin_model, assoc_name, options) - - data = upmin_model.association(assoc_name, options) - return up_render([data].flatten, partials, options, :up_association) - end - - def up_action(model, action_name, options = {}) - options[:locals] ||= {} - options[:locals][:model] ||= model - options[:locals][:action_name] = action_name - - upmin_model = Upmin::Model.new(model) - options[:locals][:upmin_model] ||= upmin_model - - partials = RenderHelpers.action_partials(upmin_model, action_name, options) - - data = upmin_model.action_parameters(action_name) - return up_render(data, partials, options, :up_action) - end - - def up_search_results(ransack_search, ransack_results, options = {}) - options[:locals] ||= {} - options[:locals][:klass] ||= Upmin::Klass.find(ransack_search.klass) - options[:locals][:ransack_search] ||= ransack_search - options[:locals][:ransack_results] ||= ransack_results - - partials = RenderHelpers.search_results_partials(ransack_search, options) - - return up_render(ransack_results, partials, options, :up_search_results) - end - - def up_search_result(model, options = {}) - options[:locals] ||= {} - - upmin_model = Upmin::Model.new(model) - options[:locals][:upmin_model] ||= upmin_model - - partials = RenderHelpers.search_result_partials(upmin_model, options) - - return up_render(model, partials, options, :up_search_result) - end - - def up_search_box(klass, options = {}) - options[:locals] ||= {} - - klass = Upmin::Klass.find(klass) unless klass.is_a?(Upmin::Klass) - if klass.nil? - raise "Invalid klass provided in `up_search_box`" - end - - options[:locals][:klass] = klass - - partials = RenderHelpers.search_box_partials(klass, options) - - return up_render(klass, partials, options, :up_search_box) - end - - - # Generic render method that is used by all of the up_ methods. Tries to render the partials in order, passing data in as the :object, along with options. - def up_render(data, partials, options = {}, calling_method = nil) # Use options as the render hash, and set :object as the data being used for rendering. options[:object] = data @@ -113,7 +49,7 @@ def up_render(data, partials, options = {}, calling_method = nil) end # If we get here we tried all of the partials and nothing matched. This *shouldn't* be possible but might happen if partials are deleted. - raise "Failed to find a matching partial while trying to render `#{calling_method}` with the following data: #{data.inspect}" + raise Upmin::MissingPartial.new(data) end end diff --git a/lib/upmin/railties/render_helpers.rb b/lib/upmin/railties/render_helpers.rb index 37168f4..ffc5122 100644 --- a/lib/upmin/railties/render_helpers.rb +++ b/lib/upmin/railties/render_helpers.rb @@ -2,21 +2,33 @@ module Upmin::Railties module RenderHelpers - def RenderHelpers.model_partials(upmin_model, options) + def RenderHelpers.model_partials(model, options = {}) partials = [] # Add "new_" in front of any partial for the partial for new view. # # # model - prefix = upmin_model.new_record? ? "new_" : "" + prefix = model.new_record? ? "new_" : "" partials << build_model_path(options[:as]) if options[:as] - partials << build_model_path(upmin_model.klass.name.underscore, prefix) + partials << build_model_path(model.underscore_name, prefix) partials << build_model_path(:model, prefix) return partials end - def RenderHelpers.attribute_partials(upmin_model, attr_name, options) + def RenderHelpers.model_options(model, options = {}) + options[:locals] ||= {} + options[:locals][:model] ||= model + return options + end + + def RenderHelpers.build_model_path(partial, prefix = "") + return build_path("models", "#{prefix}#{partial}") + end + + + + def RenderHelpers.attribute_partials(attribute, options = {}) partials = [] # # _, eg: user_name @@ -24,85 +36,148 @@ def RenderHelpers.attribute_partials(upmin_model, attr_name, options) # , eg: string # unknown - model_name = upmin_model.klass.name.underscore - attr_type = upmin_model.attribute_type(attr_name) + model_name = attribute.model.underscore_name + attr_type = attribute.type partials << build_attribute_path(options[:as]) if options[:as] - partials << build_attribute_path("#{model_name}_#{attr_name}") + partials << build_attribute_path("#{model_name}_#{attribute.name}") partials << build_attribute_path("#{model_name}_#{attr_type}") partials << build_attribute_path(attr_type) partials << build_attribute_path(:unknown) return partials end - # def RenderHelpers.action_partials(upmin_model, action, options) - # partials = [] - # partials << build_action_path(:unknown) - # return partials - # end + def RenderHelpers.attribute_options(attribute, options = {}) + options[:locals] ||= {} + options[:locals][:model] ||= attribute.model + options[:locals][:attribute] = attribute + return options + end + + def RenderHelpers.build_attribute_path(partial) + return build_path("attributes", partial) + end + # NOTE: assoc_type is sketchy at best. It tries to determine it, but in some cases it has to be guessed at, so if you have polymorphic associations it will choose the data type of the first association it finds - eg if user.things returns [Order, Product, Review] it will use the type of "order" - def RenderHelpers.association_partials(upmin_model, assoc_name, options) + def RenderHelpers.association_partials(association, options = {}) partials = [] # # _, eg: user_recent_orders # _, eg: user_orders # , eg: orders # associations - model_name = upmin_model.klass.name.underscore - assoc_type = upmin_model.association_type(assoc_name) + model_name = association.model.underscore_name + assoc_type = association.type partials << build_association_path(options[:as]) if options[:as] - partials << build_association_path("#{model_name}_#{assoc_name}") + partials << build_association_path("#{model_name}_#{association.name}") partials << build_association_path("#{model_name}_#{assoc_type}") partials << build_association_path(assoc_type) partials << build_association_path(:associations) return partials end - def RenderHelpers.action_partials(upmin_model, action_name, options) + def RenderHelpers.association_options(association, options = {}) + options[:locals] ||= {} + options[:locals][:model] ||= association.model + options[:locals][:association] = association + return options + end + + def RenderHelpers.build_association_path(partial) + return build_path("associations", partial) + end + + + + def RenderHelpers.action_partials(action, options = {}) partials = [] # # _, eg: order_refund # , eg: refund # action - model_name = upmin_model.klass.name.underscore + model_name = action.model.underscore_name partials << build_action_path(options[:as]) if options[:as] - partials << build_action_path("#{model_name}_#{action_name}") - partials << build_action_path(action_name) + partials << build_action_path("#{model_name}_#{action.name}") + partials << build_action_path(action.name) partials << build_action_path(:action) return partials end - def RenderHelpers.search_results_partials(ransack_search, options) + def RenderHelpers.action_options(action, options = {}) + options[:locals] ||= {} + options[:locals][:model] ||= action.model + options[:locals][:action] = action + return options + end + + def RenderHelpers.build_action_path(partial) + return build_path("actions", partial) + end + + + + def RenderHelpers.parameter_partials(parameter, options = {}) partials = [] # - # , eg: orders - # results - model_name_plural = ransack_search.klass.name.underscore.pluralize - - partials << build_search_result_path(options[:as]) if options[:as] - partials << build_search_result_path(model_name_plural) - partials << build_search_result_path(:results) + # __, eg: order_refund_amount + # _, eg: refund_amount + # , eg: amount + # _parameter, eg: opt_parameter and req_parameter + model_name = parameter.model.underscore_name + action_name = parameter.action.name + + partials << build_parameter_path(options[:as]) if options[:as] + partials << build_parameter_path("#{model_name}_#{action_name}_#{parameter.name}") + partials << build_parameter_path("#{action_name}_#{parameter.name}") + partials << build_parameter_path(parameter.name) + partials << build_parameter_path("#{parameter.type}_parameter") return partials end - def RenderHelpers.search_result_partials(upmin_model, options) + def RenderHelpers.parameter_options(parameter, options = {}) + options[:locals] ||= {} + options[:locals][:model] ||= parameter.model + options[:locals][:action] ||= parameter.action + options[:locals][:parameter] = parameter + return options + end + + def RenderHelpers.build_parameter_path(partial) + return build_path("parameters", partial) + end + + + + def RenderHelpers.search_results_partials(query, options = {}) partials = [] # - # , eg: order + # , eg: orders # results - model_name = upmin_model.klass.name.underscore + model_name_plural = query.underscore_name(:plural) - partials << build_search_result_path(options[:as]) if options[:as] - partials << build_search_result_path(model_name) - partials << build_search_result_path(:result) + partials << build_search_results_path(options[:as]) if options[:as] + partials << build_search_results_path(model_name_plural) + partials << build_search_results_path(:results) return partials end - def RenderHelpers.search_box_partials(klass, options) + def RenderHelpers.search_results_options(query, options = {}) + options[:locals] ||= {} + options[:locals][:query] = query + return options + end + + def RenderHelpers.build_search_results_path(partial) + return build_path("search_results", partial) + end + + + + def RenderHelpers.search_box_partials(klass, options = {}) partials = [] # # ransack_search_box @@ -112,30 +187,21 @@ def RenderHelpers.search_box_partials(klass, options) return partials end - - def RenderHelpers.build_model_path(partial_name, prefix = "") - return "#{root_path}/models/#{prefix}#{partial_name}" + def RenderHelpers.search_box_options(klass, options = {}) + options[:locals] ||= {} + options[:locals][:klass] = klass + return options end - def RenderHelpers.build_attribute_path(partial_name) - return "#{root_path}/attributes/#{partial_name}" + def RenderHelpers.build_search_box_path(partial) + return build_path("search_boxes", partial) end - def RenderHelpers.build_action_path(partial_name) - partial_name = partial_name.to_s.gsub(/[!?]/, "") - return "#{root_path}/actions/#{partial_name}" - end - def RenderHelpers.build_association_path(partial_name) - return "#{root_path}/associations/#{partial_name}" - end - - def RenderHelpers.build_search_result_path(partial_name) - return "#{root_path}/search_results/#{partial_name}" - end - def RenderHelpers.build_search_box_path(partial_name) - return "#{root_path}/search_boxes/#{partial_name}" + def RenderHelpers.build_path(folder, partial) + partial = partial.to_s.gsub(/[!?]/, "") + "#{root_path}/#{folder}/#{partial}" end def RenderHelpers.root_path diff --git a/lib/upmin/version.rb b/lib/upmin/version.rb index 7c86ceb..529026c 100644 --- a/lib/upmin/version.rb +++ b/lib/upmin/version.rb @@ -1,3 +1,3 @@ module Upmin - VERSION = "0.0.39" + VERSION = "0.0.39dev10000094" end diff --git a/spec/features/action_spec.rb b/spec/features/action_spec.rb index ac74ed1..f6fbafc 100644 --- a/spec/features/action_spec.rb +++ b/spec/features/action_spec.rb @@ -6,63 +6,56 @@ # Setup BG Stuff end - scenario("with a valid user") do - visit("/upmin/m/User/new") - - expected_user = build(:user) - - fill_in("user_name", with: expected_user.name) - fill_in("user_email", with: expected_user.email) - fill_in("user_stripe_card_id", with: expected_user.stripe_card_id) - - expect { click_button("Create") }.to(change(User, :count).by(1)) + scenario("with an existing method") do + user = User.first + visit("/upmin/m/User/i/#{user.id}") + + within(".actions") do + expect(page).to(have_selector("h4", text: "Issue coupon")) + within("form.issue_coupon") do + fill_in("issue_coupon_percent", with: "20") + click_button("Submit") + end + end - expect(page).to(have_selector("input#user_name[value='#{expected_user.name}']")) - expect(page).to(have_selector("input#user_email[value='#{expected_user.email}']")) - expect(page).to(have_selector("input#user_stripe_card_id[value='#{expected_user.stripe_card_id}']")) + within(".alert.alert-info") do + expect(page).to(have_content("CPN_FJALDKF01Z1")) + end end - scenario("with an invalid user") do - visit("/upmin/m/User/new") - - invalid_user = build(:user, email: "invalid") + scenario("without an optional parameter") do + user = User.first + visit("/upmin/m/User/i/#{user.id}") - fill_in("user_name", with: invalid_user.name) - fill_in("user_email", with: invalid_user.email) - fill_in("user_stripe_card_id", with: invalid_user.stripe_card_id) - - expect { click_button("Create") }.not_to(change(User, :count)) - - within(".alert.alert-danger") do - expect(page).to(have_content("User was NOT created.")) + within(".actions") do + expect(page).to(have_selector("h4", text: "Issue coupon")) + within("form.issue_coupon") do + check("issue_coupon_percent_is_nil") + click_button("Submit") + end end - within(".field_with_errors") do - expect(page).to(have_selector("input#user_email")) + within(".alert.alert-info") do + expect(page).to(have_content("CPN_FJALDKF01Z1")) end - - # Make sure the inputs have the values typed in. - expect(page).to(have_selector("input#user_name[value='#{invalid_user.name}']")) - expect(page).to(have_selector("input#user_email[value='#{invalid_user.email}']")) - expect(page).to(have_selector("input#user_stripe_card_id[value='#{invalid_user.stripe_card_id}']")) end - scenario("with a nil attribute") do - product = build(:product) - visit("/upmin/m/Product/new") + scenario("with an admin only method") do + product = Product.first + visit("/upmin/m/Product/i/#{product.id}") - check("product_name_is_nil") - fill_in("product_short_desc", with: product.short_desc) - fill_in("product_manufacturer", with: product.manufacturer) + new_price = "10.15" - expect { click_button("Create") }.to(change(Product, :count).by(1)) - - box = find("#product_name_is_nil") - expect(box).to(be_checked) + within(".actions") do + expect(page).to(have_selector("h4", text: "Update price")) + within("form.update_price") do + fill_in("update_price_price", with: new_price) + click_button("Submit") + end + end - created = Product.last - expect(created.name).to(be_nil) - expect(created.short_desc).to(eq(product.short_desc)) - expect(created.manufacturer).to(eq(product.manufacturer)) + product.reload + expect(product.price.to_s[0..4]).to(eq(new_price)) end + end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 15c6e1a..ee3da3f 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -73,5 +73,4 @@ expect(page).to(have_content(expected_user.name)) end - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 757dbb2..9a9cbbf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,7 +9,7 @@ if defined?(ActiveRecord) - require File.expand_path('../../../../seed/seeder', __FILE__) + require File.expand_path('../../../../test_seeders/seeder', __FILE__) end @@ -48,4 +48,5 @@ # end config.include(FactoryGirl::Syntax::Methods) + config.include(ActionView::Helpers::NumberHelper, type: :view) end diff --git a/test_app_upmin/models/admin_product.rb b/test_app_upmin/models/admin_product.rb new file mode 100644 index 0000000..719c0ee --- /dev/null +++ b/test_app_upmin/models/admin_product.rb @@ -0,0 +1,11 @@ +class AdminProduct < Upmin::Model + attributes :name, :short_desc, :price, :manufacturer, :free_shipping + + action :update_price + + def update_price(price) + model.price = price + model.save! + end + +end diff --git a/test_app_upmin/models/admin_shipment.rb b/test_app_upmin/models/admin_shipment.rb new file mode 100644 index 0000000..26b7416 --- /dev/null +++ b/test_app_upmin/models/admin_shipment.rb @@ -0,0 +1,11 @@ +class AdminShipment < Upmin::Model + + attribute :status + actions :update_shipment + + def status + return "TestStatus" + # return Upmin::Widget::ProgressBar.new(model.status, model.tracking_states) + end + +end diff --git a/test_app_upmin/models/admin_user.rb b/test_app_upmin/models/admin_user.rb new file mode 100644 index 0000000..e5455bd --- /dev/null +++ b/test_app_upmin/models/admin_user.rb @@ -0,0 +1,5 @@ +class AdminUser < Upmin::Model + + action :issue_coupon + +end diff --git a/seed/names.json b/test_seeders/names.json similarity index 100% rename from seed/names.json rename to test_seeders/names.json diff --git a/seed/order_seeder.rb b/test_seeders/order_seeder.rb similarity index 100% rename from seed/order_seeder.rb rename to test_seeders/order_seeder.rb diff --git a/seed/product_seeder.rb b/test_seeders/product_seeder.rb similarity index 100% rename from seed/product_seeder.rb rename to test_seeders/product_seeder.rb diff --git a/seed/products.json b/test_seeders/products.json similarity index 100% rename from seed/products.json rename to test_seeders/products.json diff --git a/seed/seeder.rb b/test_seeders/seeder.rb similarity index 100% rename from seed/seeder.rb rename to test_seeders/seeder.rb diff --git a/seed/user_seeder.rb b/test_seeders/user_seeder.rb similarity index 100% rename from seed/user_seeder.rb rename to test_seeders/user_seeder.rb