diff --git a/.gitignore b/.gitignore index 40bcfe6e2..d32b29c45 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ /tmp -config/database.yml \ No newline at end of file +config/database.yml +public/uploads diff --git a/Gemfile b/Gemfile index e23a1da6b..d873a2f94 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,14 @@ gem "devise" gem "bootstrap-sass" +gem "simple_form" + +gem "carrierwave" + +gem "mini_magick" + +gem "font-awesome-rails" + gem "awesome_rails_console" # 讓你的 rails console 變整齊漂亮的 gem group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 364e03c03..7c45ea8ca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,6 +58,11 @@ GEM builder (3.2.2) byebug (5.0.0) columnize (= 0.9.0) + carrierwave (0.10.0) + activemodel (>= 3.2.0) + activesupport (>= 3.2.0) + json (>= 1.7) + mime-types (>= 1.16) coderay (1.1.0) coffee-rails (4.1.0) coffee-script (>= 2.2.0) @@ -77,6 +82,8 @@ GEM warden (~> 1.2.3) erubis (2.7.0) execjs (2.5.2) + font-awesome-rails (4.4.0.0) + railties (>= 3.2, < 5.0) globalid (0.3.5) activesupport (>= 4.1.0) hirb (0.7.3) @@ -98,6 +105,7 @@ GEM mime-types (>= 1.16, < 3) method_source (0.8.2) mime-types (2.6.1) + mini_magick (4.3.3) mini_portile (0.6.2) minitest (5.7.0) multi_json (1.11.2) @@ -158,6 +166,9 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + simple_form (3.2.0) + actionpack (~> 4.0) + activemodel (~> 4.0) slop (3.6.0) spring (1.3.6) sprockets (3.2.0) @@ -193,13 +204,17 @@ DEPENDENCIES awesome_rails_console bootstrap-sass byebug + carrierwave coffee-rails (~> 4.1.0) devise + font-awesome-rails jbuilder (~> 2.0) jquery-rails + mini_magick rails (= 4.2.2) sass-rails (~> 5.0) sdoc (~> 0.4.0) + simple_form spring sqlite3 turbolinks diff --git a/app/assets/javascripts/admin/users.coffee b/app/assets/javascripts/admin/users.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/admin/users.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e07c5a830..11374a391 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,6 @@ //= require jquery //= require jquery_ujs //= require turbolinks +//= require bootstrap/dropdown +//= require bootstrap/alert //= require_tree . diff --git a/app/assets/javascripts/products.coffee b/app/assets/javascripts/products.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/products.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/admin/users.scss b/app/assets/stylesheets/admin/users.scss new file mode 100644 index 000000000..2cabf7bd7 --- /dev/null +++ b/app/assets/stylesheets/admin/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the admin::users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index b848adade..923602d3a 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -16,3 +16,13 @@ @import "bootstrap-sprockets"; @import "bootstrap"; +@import "font-awesome"; + +.product-price{ + padding-top: 20px; + padding-bottom: 20px; + + font-size: 30px; + font-weight: bold; + color: #ff007c; +} diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 000000000..89e2e8db0 --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the products controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/admin/products_controller.rb b/app/controllers/admin/products_controller.rb index 3568d198b..7b736c44f 100644 --- a/app/controllers/admin/products_controller.rb +++ b/app/controllers/admin/products_controller.rb @@ -1,5 +1,7 @@ class Admin::ProductsController < ApplicationController + layout "admin" + before_action :authenticate_user! before_action :admin_required @@ -9,6 +11,17 @@ def index def new @product = Product.new + @photo = @product.build_photo + end + + def edit + @product = Product.find(params[:id]) + + if @product.photo.present? + @photo = @product.photo + else + @photo = @product.build_photo + end end def create @@ -21,9 +34,20 @@ def create end end + def update + @product = Product.find(params[:id]) + + if @product.update(product_params) + redirect_to admin_products_path + else + render :edit + end + end + private def product_params - params.require(:product).permit(:title, :description, :quantity, :price) + params.require(:product).permit(:title, :description, :quantity, :price, + photo_attributes: [:image, :id]) end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 000000000..684dcb906 --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -0,0 +1,24 @@ +class Admin::UsersController < ApplicationController + layout "admin" + + before_action :authenticate_user! + before_action :admin_required + + def index + @users = User.all + end + + def to_admin + @user = User.find(params[:id]) + @user.to_admin + + redirect_to admin_users_path + end + + def to_normal + @user = User.find(params[:id]) + @user.to_normal + + redirect_to admin_users_path + end +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 000000000..0e295c258 --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,9 @@ +class ProductsController < ApplicationController + def index + @products = Product.all + end + + def show + @product = Product.find(params[:id]) + end +end diff --git a/app/helpers/admin/users_helper.rb b/app/helpers/admin/users_helper.rb new file mode 100644 index 000000000..e23333d37 --- /dev/null +++ b/app/helpers/admin/users_helper.rb @@ -0,0 +1,9 @@ +module Admin::UsersHelper + def render_to_normal_user_button(user, current_user) + if user == current_user + "這是你的帳號,無法變更" + else + link_to("轉為一般使用者", to_normal_admin_user_path(user), method: :post, class: "btn btn-info") + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be7945..242322a84 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,19 @@ module ApplicationHelper + def notice_message + alert_types = { notice: :success, alert: :danger } + + close_button_options = { class: "close", "data-dismiss" => "alert", "aria-hidden" => true } + close_button = content_tag(:button, "×", close_button_options) + + alerts = flash.map do |type, message| + alert_content = close_button + message + + alert_type = alert_types[type.to_sym] || type + alert_class = "alert alert-#{alert_type} alert-dismissable" + + content_tag(:div, alert_content, class: alert_class) + end + + alerts.join("\n").html_safe + end end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb new file mode 100644 index 000000000..ab5c42b32 --- /dev/null +++ b/app/helpers/products_helper.rb @@ -0,0 +1,2 @@ +module ProductsHelper +end diff --git a/app/models/photo.rb b/app/models/photo.rb new file mode 100644 index 000000000..6250c6856 --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,5 @@ +class Photo < ActiveRecord::Base + belongs_to :product + + mount_uploader :image, ImageUploader +end diff --git a/app/models/product.rb b/app/models/product.rb index 077a81979..d353e515c 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,2 +1,5 @@ class Product < ActiveRecord::Base + has_one :photo + + accepts_nested_attributes_for :photo end diff --git a/app/models/user.rb b/app/models/user.rb index 2a15996af..2939357b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,4 +7,12 @@ class User < ActiveRecord::Base def admin? is_admin end + + def to_admin + self.update_columns(is_admin: true) + end + + def to_normal + self.update_columns(is_admin: false) + end end diff --git a/app/uploaders/image_uploader.rb b/app/uploaders/image_uploader.rb new file mode 100644 index 000000000..cbb614e63 --- /dev/null +++ b/app/uploaders/image_uploader.rb @@ -0,0 +1,59 @@ +# encoding: utf-8 + +class ImageUploader < CarrierWave::Uploader::Base + + # Include RMagick or MiniMagick support: + # include CarrierWave::RMagick + include CarrierWave::MiniMagick + + # Choose what kind of storage to use for this uploader: + storage :file + # storage :fog + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + process resize_to_fit: [800, 800] + version :thumb do + process resize_to_fill: [200,200] + end + version :medium do + process resize_to_fill: [400,400] + end + + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # # For Rails 3.1+ asset pipeline compatibility: + # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) + # + # "/images/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Process files as they are uploaded: + # process :scale => [200, 300] + # + # def scale(width, height) + # # do something + # end + + # Create different versions of your uploaded files: + # version :thumb do + # process :resize_to_fit => [50, 50] + # end + + # Add a white list of extensions which are allowed to be uploaded. + # For images you might use something like this: + # def extension_white_list + # %w(jpg jpeg gif png) + # end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + # def filename + # "something.jpg" if original_filename + # end + +end diff --git a/app/views/admin/products/edit.html.erb b/app/views/admin/products/edit.html.erb new file mode 100644 index 000000000..663debd58 --- /dev/null +++ b/app/views/admin/products/edit.html.erb @@ -0,0 +1,32 @@ +<%= simple_form_for [:admin, @product] do |f| %> + +
+ <%= f.input :title %> +
+ +
+ <%= f.input :description %> +
+ +
+ <%= f.input :quantity %> +
+ +
+ <%= f.input :price %> +
+ + <% if @photo.image.present? %> + 目前商品圖
+ <%= image_tag(@photo.image.thumb.url) %> + <% end %> + +
+ <%= f.simple_fields_for :photo do |c| %> + <%= c.input :image , as: :file %> + <% end %> +
+ + <%= f.submit "Submit", data: { disable_with: "Submitting..." } %> + +<% end %> diff --git a/app/views/admin/products/index.html.erb b/app/views/admin/products/index.html.erb index 1cc1d7c87..5cacbf0aa 100644 --- a/app/views/admin/products/index.html.erb +++ b/app/views/admin/products/index.html.erb @@ -1,6 +1,42 @@ - +

Product List

+
+ <%= link_to("新增產品", new_admin_product_path, class: "btn btn-primary btn-sm") %> +
+ + + + + + + + + + + + <% @products.each do |product| %> + + + + + + + + <% end %> + +
#Product PicNamePrice Options
+ <%= product.id %> + + <%= link_to product_path(product) do %> + <% if product.photo.present? %> + <%= image_tag(product.photo.image.thumb.url, class: "thumbnail") %> + <% else %> + <%= image_tag("http://placehold.it/200x200&text=No Pic", class: "thumbnail") %> + <% end %> + <% end %> + + <%= product.title %> + + <%= product.price %> + + <%= link_to("Edit", edit_admin_product_path(product)) %> +
diff --git a/app/views/admin/products/new.html.erb b/app/views/admin/products/new.html.erb index 421b15db4..b4fdf52ab 100644 --- a/app/views/admin/products/new.html.erb +++ b/app/views/admin/products/new.html.erb @@ -1,23 +1,25 @@ -<%= form_for [:admin, @product] do |f| %> +<%= simple_form_for [:admin, @product] do |f| %>
- <%= f.label("標題") %> - <%= f.text_field :title %> + <%= f.input :title %>
- <%= f.label("敘述") %> - <%= f.text_area :description %> + <%= f.input :description %>
- <%= f.label("數量") %> - <%= f.text_field :quantity %> + <%= f.input :quantity %>
- <%= f.label("價錢") %> - <%= f.text_field :price %> + <%= f.input :price %> +
+ +
+ <%= f.simple_fields_for :photo do |c| %> + <%= c.input :image , as: :file %> + <% end %>
<%= f.submit "Submit", data: { disable_with: "Submitting..." } %> diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb new file mode 100644 index 000000000..2d7e216db --- /dev/null +++ b/app/views/admin/users/index.html.erb @@ -0,0 +1,38 @@ +

使用者列表

+ + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + <% end %> + +
#Email權限功能
+ <%= user.id %> + + <%= user.email %> + + <% if user.admin? %> + 管理者 + <% else %> + 一般使用者 + <% end %> + + <% if user.admin? %> + <%= render_to_normal_user_button(user, current_user) %> + <% else %> + <%= link_to("轉為管理者", to_admin_admin_user_path(user), method: :post, class: "btn btn-primary") %> + <% end %> +
diff --git a/app/views/common/_navbar.html.erb b/app/views/common/_navbar.html.erb new file mode 100644 index 000000000..9c3e7e6c1 --- /dev/null +++ b/app/views/common/_navbar.html.erb @@ -0,0 +1,48 @@ + diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb new file mode 100644 index 000000000..20a9adeec --- /dev/null +++ b/app/views/layouts/admin.html.erb @@ -0,0 +1,26 @@ + + + + Artstore 後台 + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> + + +
+ <%= render "common/navbar" %> +
+
+ +
+
+ <%= notice_message %> + <%= yield %> +
+
+
+ + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index ac224341a..6e0218b4e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,13 +8,11 @@ - <% if !current_user %> - <%= link_to("登入", new_user_session_path) %> - <% else %> - <%= link_to("登出", destroy_user_session_path, method: :delete) %> - <% end %> - -<%= yield %> +
+ <%= render "common/navbar" %> + <%= notice_message %> + <%= yield %> +
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 000000000..842adaf34 --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,14 @@ +
+ <% @products.each do |product| %> +
+ <%= link_to product_path(product) do %> + <% if product.photo.present? %> + <%= image_tag(product.photo.image.thumb.url, class: "thumbnail") %> + <% else %> + <%= image_tag("http://placehold.it/200x200&text=No Pic", class: "thumbnail") %> + <% end %> + <% end %> + <%= product.title %> $ <%= product.price %> +
+ <% end %> +
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 000000000..f698e096b --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,22 @@ +
+
+ <% if @product.photo.present? %> + <%= image_tag(@product.photo.image.medium.url, class: "thumbnail") %> + <% else %> + <%= image_tag("http://placehold.it/400x400&text=No Pic", class: "thumbnail") %> + <% end %> +
+
+

<%= @product.title %>

+
+

+ <%= @product.description %> +

+
+
數量 : <%= @product.quantity %>
+
$ <%= @product.price %>
+
+ <%= link_to("加入購物車", "#", :class => "btn btn-primary btn-lg btn-danger") %> +
+
+
diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb new file mode 100644 index 000000000..934487af6 --- /dev/null +++ b/config/initializers/simple_form.rb @@ -0,0 +1,165 @@ +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + # Wrappers are used by the form builder to generate a + # complete input. You can remove any component from the + # wrapper, change the order or even add your own to the + # stack. The options given below are used to wrap the + # whole input. + config.wrappers :default, class: :input, + hint_class: :field_with_hint, error_class: :field_with_errors do |b| + ## Extensions enabled by default + # Any of these extensions can be disabled for a + # given input by passing: `f.input EXTENSION_NAME => false`. + # You can make any of these extensions optional by + # renaming `b.use` to `b.optional`. + + # Determines whether to use HTML5 (:email, :url, ...) + # and required attributes + b.use :html5 + + # Calculates placeholders automatically from I18n + # You can also pass a string as f.input placeholder: "Placeholder" + b.use :placeholder + + ## Optional extensions + # They are disabled unless you pass `f.input EXTENSION_NAME => true` + # to the input. If so, they will retrieve the values from the model + # if any exists. If you want to enable any of those + # extensions by default, you can change `b.optional` to `b.use`. + + # Calculates maxlength from length validations for string inputs + b.optional :maxlength + + # Calculates pattern from format validations for string inputs + b.optional :pattern + + # Calculates min and max from length validations for numeric inputs + b.optional :min_max + + # Calculates readonly automatically from readonly attributes + b.optional :readonly + + ## Inputs + b.use :label_input + b.use :hint, wrap_with: { tag: :span, class: :hint } + b.use :error, wrap_with: { tag: :span, class: :error } + + ## full_messages_for + # If you want to display the full error message for the attribute, you can + # use the component :full_error, like: + # + # b.use :full_error, wrap_with: { tag: :span, class: :error } + end + + # The default wrapper to be used by the FormBuilder. + config.default_wrapper = :default + + # Define the way to render check boxes / radio buttons with labels. + # Defaults to :nested for bootstrap config. + # inline: input + label + # nested: label > input + config.boolean_style = :nested + + # Default class for buttons + config.button_class = 'btn' + + # Method used to tidy up errors. Specify any Rails Array method. + # :first lists the first message for each field. + # Use :to_sentence to list all errors for each field. + # config.error_method = :first + + # Default tag used for error notification helper. + config.error_notification_tag = :div + + # CSS class to add for error notification helper. + config.error_notification_class = 'error_notification' + + # ID to add for error notification helper. + # config.error_notification_id = nil + + # Series of attempts to detect a default label method for collection. + # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] + + # Series of attempts to detect a default value method for collection. + # config.collection_value_methods = [ :id, :to_s ] + + # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. + # config.collection_wrapper_tag = nil + + # You can define the class to use on all collection wrappers. Defaulting to none. + # config.collection_wrapper_class = nil + + # You can wrap each item in a collection of radio/check boxes with a tag, + # defaulting to :span. + # config.item_wrapper_tag = :span + + # You can define a class to use in all item wrappers. Defaulting to none. + # config.item_wrapper_class = nil + + # How the label text should be generated altogether with the required text. + # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } + + # You can define the class to use on all labels. Default is nil. + # config.label_class = nil + + # You can define the default class to be used on forms. Can be overriden + # with `html: { :class }`. Defaulting to none. + # config.default_form_class = nil + + # You can define which elements should obtain additional classes + # config.generate_additional_classes_for = [:wrapper, :label, :input] + + # Whether attributes are required by default (or not). Default is true. + # config.required_by_default = true + + # Tell browsers whether to use the native HTML5 validations (novalidate form option). + # These validations are enabled in SimpleForm's internal config but disabled by default + # in this configuration, which is recommended due to some quirks from different browsers. + # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, + # change this configuration to true. + config.browser_validations = false + + # Collection of methods to detect if a file type was given. + # config.file_methods = [ :mounted_as, :file?, :public_filename ] + + # Custom mappings for input types. This should be a hash containing a regexp + # to match as key, and the input type that will be used when the field name + # matches the regexp as value. + # config.input_mappings = { /count/ => :integer } + + # Custom wrappers for input types. This should be a hash containing an input + # type as key and the wrapper that will be used for all inputs with specified type. + # config.wrapper_mappings = { string: :prepend } + + # Namespaces where SimpleForm should look for custom input classes that + # override default inputs. + # config.custom_inputs_namespaces << "CustomInputs" + + # Default priority for time_zone inputs. + # config.time_zone_priority = nil + + # Default priority for country inputs. + # config.country_priority = nil + + # When false, do not use translations for labels. + # config.translate_labels = true + + # Automatically discover new inputs in Rails' autoload path. + # config.inputs_discovery = true + + # Cache SimpleForm inputs discovery + # config.cache_discovery = !Rails.env.development? + + # Default class for inputs + # config.input_class = nil + + # Define the default class of the input wrapper of the boolean input. + config.boolean_label_class = 'checkbox' + + # Defines if the default input wrapper class should be included in radio + # collection wrappers. + # config.include_default_input_wrapper_class = true + + # Defines which i18n scope will be used in Simple Form. + # config.i18n_scope = 'simple_form' +end diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb new file mode 100644 index 000000000..109d29a37 --- /dev/null +++ b/config/initializers/simple_form_bootstrap.rb @@ -0,0 +1,149 @@ +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + config.error_notification_class = 'alert alert-danger' + config.button_class = 'btn btn-default' + config.boolean_label_class = nil + + config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'control-label' + + b.use :input, class: 'form-control' + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :readonly + b.use :label, class: 'control-label' + + b.use :input + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + + b.wrapper tag: 'div', class: 'checkbox' do |ba| + ba.use :label_input + end + + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'control-label' + b.use :input + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'col-sm-3 control-label' + + b.wrapper tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, class: 'form-control' + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :readonly + b.use :label, class: 'col-sm-3 control-label' + + b.wrapper tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + + b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr| + wr.wrapper tag: 'div', class: 'checkbox' do |ba| + ba.use :label_input + end + + wr.use :error, wrap_with: { tag: 'span', class: 'help-block' } + wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + + b.use :label, class: 'col-sm-3 control-label' + + b.wrapper tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :inline_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'sr-only' + + b.use :input, class: 'form-control' + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :multi_select, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'control-label' + b.wrapper tag: 'div', class: 'form-inline' do |ba| + ba.use :input, class: 'form-control' + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + # Wrappers for forms and inputs using the Bootstrap toolkit. + # Check the Bootstrap docs (http://getbootstrap.com) + # to learn about the different styles for forms and inputs, + # buttons and other elements. + config.default_wrapper = :vertical_form + config.wrapper_mappings = { + check_boxes: :vertical_radio_and_checkboxes, + radio_buttons: :vertical_radio_and_checkboxes, + file: :vertical_file_input, + boolean: :vertical_boolean, + datetime: :multi_select, + date: :multi_select, + time: :multi_select + } +end diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml new file mode 100644 index 000000000..237438334 --- /dev/null +++ b/config/locales/simple_form.en.yml @@ -0,0 +1,31 @@ +en: + simple_form: + "yes": 'Yes' + "no": 'No' + required: + text: 'required' + mark: '*' + # You can uncomment the line below if you need to overwrite the whole required html. + # When using html, text and mark won't be used. + # html: '*' + error_notification: + default_message: "Please review the problems below:" + # Examples + # labels: + # defaults: + # password: 'Password' + # user: + # new: + # email: 'E-mail to sign in.' + # edit: + # email: 'E-mail.' + # hints: + # defaults: + # username: 'User name to sign in.' + # password: 'No special characters, please.' + # include_blanks: + # defaults: + # age: 'Rather not say' + # prompts: + # defaults: + # age: 'Select your age' diff --git a/config/routes.rb b/config/routes.rb index da070cf02..71f6d5452 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,19 @@ Rails.application.routes.draw do + root "products#index" + devise_for :users namespace :admin do resources :products + resources :users do + member do + post :to_admin + post :to_normal + end + end end + + resources :products + # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/db/migrate/20160308072947_create_photos.rb b/db/migrate/20160308072947_create_photos.rb new file mode 100644 index 000000000..b5b4325e0 --- /dev/null +++ b/db/migrate/20160308072947_create_photos.rb @@ -0,0 +1,10 @@ +class CreatePhotos < ActiveRecord::Migration + def change + create_table :photos do |t| + t.integer :product_id + t.string :image + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 39005515d..833e0e747 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160308071101) do +ActiveRecord::Schema.define(version: 20160308072947) do + + create_table "photos", force: :cascade do |t| + t.integer "product_id" + t.string "image" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end create_table "products", force: :cascade do |t| t.string "title" diff --git a/db/seeds.rb b/db/seeds.rb index a0aa8c28e..54b8e9480 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -14,3 +14,28 @@ u.is_admin = true u.save + +# 建立二筆商品資料 + +product = Product.new +product.title = "macbook" +product.price = "60000" +product.quantity = "5" +product.description = "蘋果電腦" +product.save + +product.build_photo.save +product.photo.image.store!(File.open(File.join(Rails.root, 'public/macbook.jpg'))) +product.photo.save! + + +product2 = Product.new +product2.title = "iphone" +product2.price = "20000" +product2.quantity = "5" +product2.description = "蘋果手機" +product2.save + +product2.build_photo.save +product2.photo.image.store!(File.open(File.join(Rails.root, 'public/iphone.jpg'))) +product2.photo.save! diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb new file mode 100644 index 000000000..201a069e2 --- /dev/null +++ b/lib/templates/erb/scaffold/_form.html.erb @@ -0,0 +1,13 @@ +<%%= simple_form_for(@<%= singular_table_name %>) do |f| %> + <%%= f.error_notification %> + +
+ <%- attributes.each do |attribute| -%> + <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> + <%- end -%> +
+ +
+ <%%= f.button :submit %> +
+<%% end %> diff --git a/public/iphone.jpg b/public/iphone.jpg new file mode 100644 index 000000000..44d14152d Binary files /dev/null and b/public/iphone.jpg differ diff --git a/public/macbook.jpg b/public/macbook.jpg new file mode 100644 index 000000000..b26d977e7 Binary files /dev/null and b/public/macbook.jpg differ diff --git a/test/controllers/admin/users_controller_test.rb b/test/controllers/admin/users_controller_test.rb new file mode 100644 index 000000000..a0352fc30 --- /dev/null +++ b/test/controllers/admin/users_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Admin::UsersControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb new file mode 100644 index 000000000..c881fa681 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ProductsControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/photos.yml b/test/fixtures/photos.yml new file mode 100644 index 000000000..5cc49b723 --- /dev/null +++ b/test/fixtures/photos.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + product_id: 1 + image: MyString + +two: + product_id: 1 + image: MyString diff --git a/test/models/photo_test.rb b/test/models/photo_test.rb new file mode 100644 index 000000000..e2ec03a1b --- /dev/null +++ b/test/models/photo_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class PhotoTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end