From 8952f17b1b01e12e2180ebe4cf99f8bdce65d184 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Feb 2015 21:45:48 +0300 Subject: [PATCH 001/227] init commit --- .gitignore | 17 ++ Gemfile | 45 +++++ Gemfile.lock | 164 ++++++++++++++++++ README.rdoc | 28 +++ Rakefile | 6 + app/assets/images/.keep | 0 app/assets/javascripts/application.js | 16 ++ app/assets/stylesheets/application.css | 15 ++ app/controllers/application_controller.rb | 5 + app/controllers/concerns/.keep | 0 app/helpers/application_helper.rb | 2 + app/mailers/.keep | 0 app/models/.keep | 0 app/models/concerns/.keep | 0 app/views/layouts/application.html.erb | 14 ++ bin/bundle | 3 + bin/rails | 8 + bin/rake | 8 + bin/setup | 29 ++++ bin/spring | 18 ++ config.ru | 4 + config/application.rb | 26 +++ config/boot.rb | 3 + config/database.yml | 25 +++ config/environment.rb | 5 + config/environments/development.rb | 41 +++++ config/environments/production.rb | 79 +++++++++ config/environments/test.rb | 42 +++++ config/initializers/assets.rb | 11 ++ config/initializers/backtrace_silencers.rb | 7 + config/initializers/cookies_serializer.rb | 3 + .../initializers/filter_parameter_logging.rb | 4 + config/initializers/inflections.rb | 16 ++ config/initializers/mime_types.rb | 4 + config/initializers/session_store.rb | 3 + config/initializers/wrap_parameters.rb | 14 ++ config/locales/en.yml | 23 +++ config/routes.rb | 56 ++++++ config/secrets.yml | 22 +++ db/seeds.rb | 7 + lib/assets/.keep | 0 lib/tasks/.keep | 0 log/.keep | 0 public/404.html | 67 +++++++ public/422.html | 67 +++++++ public/500.html | 66 +++++++ public/favicon.ico | 0 public/robots.txt | 5 + test/controllers/.keep | 0 test/fixtures/.keep | 0 test/helpers/.keep | 0 test/integration/.keep | 0 test/mailers/.keep | 0 test/models/.keep | 0 test/test_helper.rb | 10 ++ vendor/assets/javascripts/.keep | 0 vendor/assets/stylesheets/.keep | 0 57 files changed, 988 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.rdoc create mode 100644 Rakefile create mode 100644 app/assets/images/.keep create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/helpers/application_helper.rb create mode 100644 app/mailers/.keep create mode 100644 app/models/.keep create mode 100644 app/models/concerns/.keep create mode 100644 app/views/layouts/application.html.erb create mode 100755 bin/bundle create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/setup create mode 100755 bin/spring create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/cookies_serializer.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/session_store.rb create mode 100644 config/initializers/wrap_parameters.rb create mode 100644 config/locales/en.yml create mode 100644 config/routes.rb create mode 100644 config/secrets.yml create mode 100644 db/seeds.rb create mode 100644 lib/assets/.keep create mode 100644 lib/tasks/.keep create mode 100644 log/.keep create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/favicon.ico create mode 100644 public/robots.txt create mode 100644 test/controllers/.keep create mode 100644 test/fixtures/.keep create mode 100644 test/helpers/.keep create mode 100644 test/integration/.keep create mode 100644 test/mailers/.keep create mode 100644 test/models/.keep create mode 100644 test/test_helper.rb create mode 100644 vendor/assets/javascripts/.keep create mode 100644 vendor/assets/stylesheets/.keep diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..050c9d95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +!/log/.keep +/tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..b43fd2ff --- /dev/null +++ b/Gemfile @@ -0,0 +1,45 @@ +source 'https://rubygems.org' + + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '4.2.0' +# Use sqlite3 as the database for Active Record +gem 'sqlite3' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.1.0' +# See https://github.com/sstephenson/execjs#readme for more supported runtimes +# gem 'therubyracer', platforms: :ruby + +# Use jquery as the JavaScript library +gem 'jquery-rails' +# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks +gem 'turbolinks' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.0' +# bundle exec rake doc:rails generates the API under doc/api. +gem 'sdoc', '~> 0.4.0', group: :doc + +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use Unicorn as the app server +# gem 'unicorn' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug' + + # Access an IRB console on exception pages or by using <%= console %> in views + gem 'web-console', '~> 2.0' + + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' +end + diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..8d6562fe --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,164 @@ +GEM + remote: https://rubygems.org/ + specs: + actionmailer (4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.0) + actionview (= 4.2.0) + activesupport (= 4.2.0) + rack (~> 1.6.0) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + actionview (4.2.0) + activesupport (= 4.2.0) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + activejob (4.2.0) + activesupport (= 4.2.0) + globalid (>= 0.3.0) + activemodel (4.2.0) + activesupport (= 4.2.0) + builder (~> 3.1) + activerecord (4.2.0) + activemodel (= 4.2.0) + activesupport (= 4.2.0) + arel (~> 6.0) + activesupport (4.2.0) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + arel (6.0.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.2) + byebug (3.5.1) + columnize (~> 0.8) + debugger-linecache (~> 1.2) + slop (~> 3.6) + coffee-rails (4.1.0) + coffee-script (>= 2.2.0) + railties (>= 4.0.0, < 5.0) + coffee-script (2.3.0) + coffee-script-source + execjs + coffee-script-source (1.9.0) + columnize (0.9.0) + debug_inspector (0.0.2) + debugger-linecache (1.2.0) + erubis (2.7.0) + execjs (2.3.0) + globalid (0.3.2) + activesupport (>= 4.1.0) + hike (1.2.3) + i18n (0.7.0) + jbuilder (2.2.6) + activesupport (>= 3.0.0, < 5) + multi_json (~> 1.2) + jquery-rails (4.0.3) + rails-dom-testing (~> 1.0) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (1.8.2) + loofah (2.0.1) + nokogiri (>= 1.5.9) + mail (2.6.3) + mime-types (>= 1.16, < 3) + mime-types (2.4.3) + mini_portile (0.6.2) + minitest (5.5.1) + multi_json (1.10.1) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) + rack (1.6.0) + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.0) + actionmailer (= 4.2.0) + actionpack (= 4.2.0) + actionview (= 4.2.0) + activejob (= 4.2.0) + activemodel (= 4.2.0) + activerecord (= 4.2.0) + activesupport (= 4.2.0) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.0) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.5) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.1) + loofah (~> 2.0) + railties (4.2.0) + actionpack (= 4.2.0) + activesupport (= 4.2.0) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.4.2) + rdoc (4.2.0) + json (~> 1.4) + sass (3.4.11) + sass-rails (5.0.1) + railties (>= 4.0.0, < 5.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (~> 1.1) + sdoc (0.4.1) + json (~> 1.7, >= 1.7.7) + rdoc (~> 4.0) + slop (3.6.0) + spring (1.3.0) + sprockets (2.12.3) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.2.4) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (>= 2.8, < 4.0) + sqlite3 (1.3.10) + thor (0.19.1) + thread_safe (0.3.4) + tilt (1.4.1) + turbolinks (2.5.3) + coffee-rails + tzinfo (1.2.2) + thread_safe (~> 0.1) + uglifier (2.7.0) + execjs (>= 0.3.0) + json (>= 1.8.0) + web-console (2.0.0) + activemodel (~> 4.0) + binding_of_caller (>= 0.7.2) + railties (~> 4.0) + sprockets-rails (>= 2.0, < 4.0) + +PLATFORMS + ruby + +DEPENDENCIES + byebug + coffee-rails (~> 4.1.0) + jbuilder (~> 2.0) + jquery-rails + rails (= 4.2.0) + sass-rails (~> 5.0) + sdoc (~> 0.4.0) + spring + sqlite3 + turbolinks + uglifier (>= 1.3.0) + web-console (~> 2.0) diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 00000000..dd4e97e2 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,28 @@ +== README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... + + +Please feel free to use a different markup language if you do not plan to run +<tt>rake doc:app</tt>. diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..ba6b733d --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +Rails.application.load_tasks diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 00000000..4a2ae1d9 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,16 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require jquery +//= require jquery_ujs +//= require turbolinks +//= require_tree . diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 00000000..f9cd5b34 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 00000000..d83690e1 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,5 @@ +class ApplicationController < ActionController::Base + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + protect_from_forgery with: :exception +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 00000000..de6be794 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/mailers/.keep b/app/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/models/.keep b/app/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 00000000..e69de29b diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 00000000..eb819fd1 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title>Ulmicru</title> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> +</head> +<body> + +<%= yield %> + +</body> +</html> diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 00000000..66e9889e --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 00000000..4d608ede --- /dev/null +++ b/bin/rails @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +begin + load File.expand_path("../spring", __FILE__) +rescue LoadError +end +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 00000000..8017a027 --- /dev/null +++ b/bin/rake @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +begin + load File.expand_path("../spring", __FILE__) +rescue LoadError +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 00000000..acdb2c13 --- /dev/null +++ b/bin/setup @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +Dir.chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file: + + puts "== Installing dependencies ==" + system "gem install bundler --conservative" + system "bundle check || bundle install" + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # system "cp config/database.yml.sample config/database.yml" + # end + + puts "\n== Preparing database ==" + system "bin/rake db:setup" + + puts "\n== Removing old logs and tempfiles ==" + system "rm -f log/*" + system "rm -rf tmp/cache" + + puts "\n== Restarting application server ==" + system "touch tmp/restart.txt" +end diff --git a/bin/spring b/bin/spring new file mode 100755 index 00000000..de6070b2 --- /dev/null +++ b/bin/spring @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast +# It gets overwritten when you run the `spring binstub` command + +unless defined?(Spring) + require "rubygems" + require "bundler" + + if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) + ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) + ENV["GEM_HOME"] = nil + Gem.paths = ENV + + gem "spring", match[1] + require "spring/binstub" + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 00000000..bd83b254 --- /dev/null +++ b/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Rails.application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 00000000..8c23e72e --- /dev/null +++ b/config/application.rb @@ -0,0 +1,26 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Ulmicru + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # Do not swallow errors in after_commit/after_rollback callbacks. + config.active_record.raise_in_transactional_callbacks = true + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 00000000..6b750f00 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 00000000..1c1a37ca --- /dev/null +++ b/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 00000000..ee8d90dc --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require File.expand_path('../application', __FILE__) + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 00000000..b55e2144 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,41 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 00000000..5c1b32e4 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,79 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like + # NGINX, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 00000000..1c19f08b --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure static file server for tests with Cache-Control for performance. + config.serve_static_files = true + config.static_cache_control = 'public, max-age=3600' + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Randomize the order test cases are executed. + config.active_support.test_order = :random + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 00000000..01ef3e66 --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,11 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 00000000..59385cdf --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 00000000..7f70458d --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..4a994e1e --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 00000000..ac033bf9 --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 00000000..dc189968 --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 00000000..900a07ec --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.session_store :cookie_store, key: '_ulmicru_session' diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 00000000..33725e95 --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] if respond_to?(:wrap_parameters) +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 00000000..06539571 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,23 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 00000000..3f66539d --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,56 @@ +Rails.application.routes.draw do + # The priority is based upon order of creation: first created -> highest priority. + # See how all your routes lay out with "rake routes". + + # You can have the root of your site routed with "root" + # root 'welcome#index' + + # Example of regular route: + # get 'products/:id' => 'catalog#view' + + # Example of named route that can be invoked with purchase_url(id: product.id) + # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase + + # Example resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Example resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Example resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Example resource route with more complex sub-resources: + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', on: :collection + # end + # end + + # Example resource route with concerns: + # concern :toggleable do + # post 'toggle' + # end + # resources :posts, concerns: :toggleable + # resources :photos, concerns: :toggleable + + # Example resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 00000000..d97bb9b7 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,22 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: bb567fac67c619ac56f0adf8677f30242cbaa8f3a875e3482c434a319113512ea2813b304557ac8cd6604111a25f40e8c49bab3f358ff4c98b6f05736aad0ec6 + +test: + secret_key_base: 48758f0158d06da926294ff0981d15f5347ee1765bb9d95805b40e01221a02a16eadf95a73e1b299c2260a19cb7ae7297bb8651ab05d79e68fa262ad52192c6e + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 00000000..4edb1e85 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) +# Mayor.create(name: 'Emanuel', city: cities.first) diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 00000000..e69de29b diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 00000000..e69de29b diff --git a/log/.keep b/log/.keep new file mode 100644 index 00000000..e69de29b diff --git a/public/404.html b/public/404.html new file mode 100644 index 00000000..b612547f --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The page you were looking for doesn't exist (404)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + body { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body> + <!-- This file lives in public/404.html --> + <div class="dialog"> + <div> + <h1>The page you were looking for doesn't exist.</h1> + <p>You may have mistyped the address or the page may have moved.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/public/422.html b/public/422.html new file mode 100644 index 00000000..a21f82b3 --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html> +<head> + <title>The change you wanted was rejected (422)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + body { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body> + <!-- This file lives in public/422.html --> + <div class="dialog"> + <div> + <h1>The change you wanted was rejected.</h1> + <p>Maybe you tried to change something you didn't have access to.</p> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/public/500.html b/public/500.html new file mode 100644 index 00000000..061abc58 --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <title>We're sorry, but something went wrong (500)</title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <style> + body { + background-color: #EFEFEF; + color: #2E2F30; + text-align: center; + font-family: arial, sans-serif; + margin: 0; + } + + div.dialog { + width: 95%; + max-width: 33em; + margin: 4em auto 0; + } + + div.dialog > div { + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 12% 0; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + + h1 { + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + + div.dialog > p { + margin: 0 0 1em; + padding: 1em; + background-color: #F7F7F7; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); + } + </style> +</head> + +<body> + <!-- This file lives in public/500.html --> + <div class="dialog"> + <div> + <h1>We're sorry, but something went wrong.</h1> + </div> + <p>If you are the application owner check the logs for more information.</p> + </div> +</body> +</html> diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..3c9c7c01 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,5 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# +# To ban all spiders from the entire site uncomment the next two lines: +# User-agent: * +# Disallow: / diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..92e39b2d --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,10 @@ +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +require 'rails/test_help' + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + # Add more helper methods to be used by all tests here... +end diff --git a/vendor/assets/javascripts/.keep b/vendor/assets/javascripts/.keep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/assets/stylesheets/.keep b/vendor/assets/stylesheets/.keep new file mode 100644 index 00000000..e69de29b From a2c7e21a185a5b1d23cddca41328cc7df4c0746d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 01:10:41 +0300 Subject: [PATCH 002/227] add user model --- Gemfile.lock | 10 +++++----- app/models/user.rb | 5 +++++ db/migrate/20150220220847_create_users.rb | 10 ++++++++++ db/schema.rb | 23 +++++++++++++++++++++++ test/fixtures/users.yml | 9 +++++++++ test/models/user_test.rb | 7 +++++++ 6 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 app/models/user.rb create mode 100644 db/migrate/20150220220847_create_users.rb create mode 100644 db/schema.rb create mode 100644 test/fixtures/users.yml create mode 100644 test/models/user_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index 8d6562fe..a9620b09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,17 +50,17 @@ GEM coffee-script (2.3.0) coffee-script-source execjs - coffee-script-source (1.9.0) + coffee-script-source (1.9.1) columnize (0.9.0) debug_inspector (0.0.2) debugger-linecache (1.2.0) erubis (2.7.0) execjs (2.3.0) - globalid (0.3.2) + globalid (0.3.3) activesupport (>= 4.1.0) hike (1.2.3) i18n (0.7.0) - jbuilder (2.2.6) + jbuilder (2.2.7) activesupport (>= 3.0.0, < 5) multi_json (~> 1.2) jquery-rails (4.0.3) @@ -108,7 +108,7 @@ GEM rake (10.4.2) rdoc (4.2.0) json (~> 1.4) - sass (3.4.11) + sass (3.4.12) sass-rails (5.0.1) railties (>= 4.0.0, < 5.0) sass (~> 3.1) @@ -119,7 +119,7 @@ GEM json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) slop (3.6.0) - spring (1.3.0) + spring (1.3.2) sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 00000000..c664dfa4 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,5 @@ +class User < ActiveRecord::Base + has_secure_password validations: false + + validates :email, presence: true +end diff --git a/db/migrate/20150220220847_create_users.rb b/db/migrate/20150220220847_create_users.rb new file mode 100644 index 00000000..3d665df7 --- /dev/null +++ b/db/migrate/20150220220847_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.text :email + t.text :password_digest + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 00000000..8073a9c2 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,23 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20150220220847) do + + create_table "users", force: :cascade do |t| + t.text "email" + t.text "password_digest" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 00000000..157e3526 --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + email: MyText + password_digest: MyText + +two: + email: MyText + password_digest: MyText diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 00000000..82f61e01 --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From b606a62883d00c5cfa9cab5825dec72b9d97f866 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 01:16:36 +0300 Subject: [PATCH 003/227] add welcome controller --- Gemfile | 2 ++ Gemfile.lock | 17 +++++++++++++++++ app/assets/javascripts/web/application.coffee | 3 +++ app/assets/javascripts/web/welcome.coffee | 3 +++ app/assets/stylesheets/web/application.scss | 3 +++ app/assets/stylesheets/web/welcome.scss | 3 +++ app/controllers/web/application_controller.rb | 2 ++ app/controllers/web/welcome_controller.rb | 4 ++++ app/helpers/web/application_helper.rb | 2 ++ app/helpers/web/welcome_helper.rb | 2 ++ app/views/web/welcome/index.html.haml | 0 config/routes.rb | 2 ++ .../web/application_controller_test.rb | 7 +++++++ test/controllers/web/welcome_controller_test.rb | 7 +++++++ 14 files changed, 57 insertions(+) create mode 100644 app/assets/javascripts/web/application.coffee create mode 100644 app/assets/javascripts/web/welcome.coffee create mode 100644 app/assets/stylesheets/web/application.scss create mode 100644 app/assets/stylesheets/web/welcome.scss create mode 100644 app/controllers/web/application_controller.rb create mode 100644 app/controllers/web/welcome_controller.rb create mode 100644 app/helpers/web/application_helper.rb create mode 100644 app/helpers/web/welcome_helper.rb create mode 100644 app/views/web/welcome/index.html.haml create mode 100644 test/controllers/web/application_controller_test.rb create mode 100644 test/controllers/web/welcome_controller_test.rb diff --git a/Gemfile b/Gemfile index b43fd2ff..287107d7 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,8 @@ gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc +gem 'haml-rails' + # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index a9620b09..5999af46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,7 +58,20 @@ GEM execjs (2.3.0) globalid (0.3.3) activesupport (>= 4.1.0) + haml (4.0.6) + tilt + haml-rails (0.8.2) + actionpack (>= 4.0.1) + activesupport (>= 4.0.1) + haml (>= 3.1, < 5.0) + html2haml (>= 1.0.1) + railties (>= 4.0.1) hike (1.2.3) + html2haml (2.0.0) + erubis (~> 2.7.0) + haml (~> 4.0.0) + nokogiri (~> 1.6.0) + ruby_parser (~> 3.5) i18n (0.7.0) jbuilder (2.2.7) activesupport (>= 3.0.0, < 5) @@ -108,6 +121,8 @@ GEM rake (10.4.2) rdoc (4.2.0) json (~> 1.4) + ruby_parser (3.6.4) + sexp_processor (~> 4.1) sass (3.4.12) sass-rails (5.0.1) railties (>= 4.0.0, < 5.0) @@ -118,6 +133,7 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + sexp_processor (4.4.5) slop (3.6.0) spring (1.3.2) sprockets (2.12.3) @@ -152,6 +168,7 @@ PLATFORMS DEPENDENCIES byebug coffee-rails (~> 4.1.0) + haml-rails jbuilder (~> 2.0) jquery-rails rails (= 4.2.0) diff --git a/app/assets/javascripts/web/application.coffee b/app/assets/javascripts/web/application.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/application.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/web/welcome.coffee b/app/assets/javascripts/web/welcome.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/welcome.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/web/application.scss b/app/assets/stylesheets/web/application.scss new file mode 100644 index 00000000..f84fa334 --- /dev/null +++ b/app/assets/stylesheets/web/application.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/application 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/web/welcome.scss b/app/assets/stylesheets/web/welcome.scss new file mode 100644 index 00000000..9fa12b39 --- /dev/null +++ b/app/assets/stylesheets/web/welcome.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/welcome 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/web/application_controller.rb b/app/controllers/web/application_controller.rb new file mode 100644 index 00000000..cfc7b655 --- /dev/null +++ b/app/controllers/web/application_controller.rb @@ -0,0 +1,2 @@ +class Web::ApplicationController < ApplicationController +end diff --git a/app/controllers/web/welcome_controller.rb b/app/controllers/web/welcome_controller.rb new file mode 100644 index 00000000..db3f713d --- /dev/null +++ b/app/controllers/web/welcome_controller.rb @@ -0,0 +1,4 @@ +class Web::WelcomeController < Web::ApplicationController + def index + end +end diff --git a/app/helpers/web/application_helper.rb b/app/helpers/web/application_helper.rb new file mode 100644 index 00000000..aa542072 --- /dev/null +++ b/app/helpers/web/application_helper.rb @@ -0,0 +1,2 @@ +module Web::ApplicationHelper +end diff --git a/app/helpers/web/welcome_helper.rb b/app/helpers/web/welcome_helper.rb new file mode 100644 index 00000000..f58cc10a --- /dev/null +++ b/app/helpers/web/welcome_helper.rb @@ -0,0 +1,2 @@ +module Web::WelcomeHelper +end diff --git a/app/views/web/welcome/index.html.haml b/app/views/web/welcome/index.html.haml new file mode 100644 index 00000000..e69de29b diff --git a/config/routes.rb b/config/routes.rb index 3f66539d..e60e4304 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + root to: 'web/welcome#index' + # 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/test/controllers/web/application_controller_test.rb b/test/controllers/web/application_controller_test.rb new file mode 100644 index 00000000..ff578a3f --- /dev/null +++ b/test/controllers/web/application_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::ApplicationControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/web/welcome_controller_test.rb b/test/controllers/web/welcome_controller_test.rb new file mode 100644 index 00000000..466de550 --- /dev/null +++ b/test/controllers/web/welcome_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::WelcomeControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end From ea7e958d9e1648a4cf6e61974057344e5a7849a1 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 01:26:15 +0300 Subject: [PATCH 004/227] add role to user --- Gemfile | 1 + Gemfile.lock | 3 +++ app/models/user.rb | 3 +++ db/migrate/20150220222045_add_role_to_user.rb | 5 +++++ db/schema.rb | 3 ++- 5 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150220222045_add_role_to_user.rb diff --git a/Gemfile b/Gemfile index 287107d7..a89a9064 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem 'jbuilder', '~> 2.0' gem 'sdoc', '~> 0.4.0', group: :doc gem 'haml-rails' +gem 'enumerize' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 5999af46..cef60ad5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -54,6 +54,8 @@ GEM columnize (0.9.0) debug_inspector (0.0.2) debugger-linecache (1.2.0) + enumerize (0.9.0) + activesupport (>= 3.2) erubis (2.7.0) execjs (2.3.0) globalid (0.3.3) @@ -168,6 +170,7 @@ PLATFORMS DEPENDENCIES byebug coffee-rails (~> 4.1.0) + enumerize haml-rails jbuilder (~> 2.0) jquery-rails diff --git a/app/models/user.rb b/app/models/user.rb index c664dfa4..cb08c33e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,4 +2,7 @@ class User < ActiveRecord::Base has_secure_password validations: false validates :email, presence: true + + extend Enumerize + enumerize :role, in: [ :user, :admin ], default: :user end diff --git a/db/migrate/20150220222045_add_role_to_user.rb b/db/migrate/20150220222045_add_role_to_user.rb new file mode 100644 index 00000000..268a1a09 --- /dev/null +++ b/db/migrate/20150220222045_add_role_to_user.rb @@ -0,0 +1,5 @@ +class AddRoleToUser < ActiveRecord::Migration + def change + add_column :users, :role, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 8073a9c2..18fc2643 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,13 +11,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150220220847) do +ActiveRecord::Schema.define(version: 20150220222045) do create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "role" end end From 140fa22d177857cdd21f8d91fc24261817fae2c7 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 01:36:45 +0300 Subject: [PATCH 005/227] add authority --- Gemfile | 1 + Gemfile.lock | 4 ++ app/authorizers/application_authorizer.rb | 16 ++++++ app/authorizers/user_authorizer.rb | 11 ++++ config/initializers/authority.rb | 69 +++++++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 app/authorizers/application_authorizer.rb create mode 100644 app/authorizers/user_authorizer.rb create mode 100644 config/initializers/authority.rb diff --git a/Gemfile b/Gemfile index a89a9064..c5eb628f 100644 --- a/Gemfile +++ b/Gemfile @@ -25,6 +25,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'haml-rails' gem 'enumerize' +gem 'authority' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index cef60ad5..ca2ca71c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,6 +37,9 @@ GEM thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) arel (6.0.0) + authority (3.0.0) + activesupport (>= 3.0.0) + rake (>= 0.8.7) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) @@ -168,6 +171,7 @@ PLATFORMS ruby DEPENDENCIES + authority byebug coffee-rails (~> 4.1.0) enumerize diff --git a/app/authorizers/application_authorizer.rb b/app/authorizers/application_authorizer.rb new file mode 100644 index 00000000..891bcfe8 --- /dev/null +++ b/app/authorizers/application_authorizer.rb @@ -0,0 +1,16 @@ +# Other authorizers should subclass this one +class ApplicationAuthorizer < Authority::Authorizer + + # Any class method from Authority::Authorizer that isn't overridden + # will call its authorizer's default method. + # + # @param [Symbol] adjective; example: `:creatable` + # @param [Object] user - whatever represents the current user in your app + # @return [Boolean] + def self.default(adjective, user) + # 'Whitelist' strategy for security: anything not explicitly allowed is + # considered forbidden. + false + end + +end diff --git a/app/authorizers/user_authorizer.rb b/app/authorizers/user_authorizer.rb new file mode 100644 index 00000000..e9d5e301 --- /dev/null +++ b/app/authorizers/user_authorizer.rb @@ -0,0 +1,11 @@ +class UserAuthorizer < ApplicationAuthorizer + def self.creatable_by?(user) + user.role.admin? + end + def self.updatable_by?(user) + user.role.admin? || user.id == resource.id + end + def self.deletable_by?(user) + user.role.admin? || user.id == resource.id + end +end diff --git a/config/initializers/authority.rb b/config/initializers/authority.rb new file mode 100644 index 00000000..735310fc --- /dev/null +++ b/config/initializers/authority.rb @@ -0,0 +1,69 @@ +Authority.configure do |config| + config.abilities = { + create: 'creatable', + read: 'readable', + update: 'updatable', + delete: 'deletable' + } + + # USER_METHOD + # =========== + # Authority needs the name of a method, available in any controller, which + # will return the currently logged-in user. (If this varies by controller, + # just create a common alias.) + # + # Default is: + # + # config.user_method = :current_user + + # CONTROLLER_ACTION_MAP + # ===================== + # For a given controller method, what verb must a user be able to do? + # For example, a user can access 'show' if they 'can_read' the resource. + # + # These can be modified on a per-controller basis; see README. This option + # applies to all controllers. + # + # Defaults are as follows: + # + # config.controller_action_map = { + # index: 'read', + # show: 'read', + # new: 'create', + # create: 'create', + # edit: 'update', + # update: 'update', + # destroy: 'delete' + # } + + # ABILITIES + # ========= + # Teach Authority how to understand the verbs and adjectives in your system. Perhaps you + # need {microwave: 'microwavable'}. I'm not saying you do, of course. Stop looking at + # me like that. + # + # Defaults are as follows: + # + # config.abilities = { + # create: 'creatable', + # read: 'readable', + # update: 'updatable', + # delete: 'deletable' + # } + + # LOGGER + # ====== + # If a user tries to perform an unauthorized action, where should we log that fact? + # Provide a logger object which responds to `.warn(message)`, unless your + # security_violation_handler calls a different method. + # + # Default is: + # + # config.logger = Logger.new(STDERR) + # + # Some possible settings: + # config.logger = Rails.logger # Log with all your app's other messages + # config.logger = Logger.new('log/authority.log') # Use this file + # config.logger = Logger.new('/dev/null') # Don't log at all (on a Unix system) + +end From 26942e86250f8118aa116c15a816126f579b44de Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 01:52:27 +0300 Subject: [PATCH 006/227] add admin --- Gemfile | 10 +++ Gemfile.lock | 55 +++++++++++++++ README.rdoc | 28 -------- ReadMe.md | 9 +++ .../javascripts/web/admin/application.coffee | 3 + app/assets/javascripts/web/admin/users.coffee | 3 + .../stylesheets/web/admin/application.scss | 3 + app/assets/stylesheets/web/admin/users.scss | 3 + .../web/admin/application_controller.rb | 2 + app/controllers/web/admin/users_controller.rb | 37 ++++++++++ app/helpers/web/admin/application_helper.rb | 2 + app/helpers/web/admin/users_helper.rb | 2 + config/database.yml | 30 ++++---- config/routes.rb | 6 ++ db/schema.rb | 3 + .../web/admin/application_controller_test.rb | 7 ++ .../web/admin/users_controller_test.rb | 68 +++++++++++++++++++ test/factories/sequences.rb | 31 +++++++++ test/factories/users.rb | 6 ++ 19 files changed, 265 insertions(+), 43 deletions(-) delete mode 100644 README.rdoc create mode 100644 ReadMe.md create mode 100644 app/assets/javascripts/web/admin/application.coffee create mode 100644 app/assets/javascripts/web/admin/users.coffee create mode 100644 app/assets/stylesheets/web/admin/application.scss create mode 100644 app/assets/stylesheets/web/admin/users.scss create mode 100644 app/controllers/web/admin/application_controller.rb create mode 100644 app/controllers/web/admin/users_controller.rb create mode 100644 app/helpers/web/admin/application_helper.rb create mode 100644 app/helpers/web/admin/users_helper.rb create mode 100644 test/controllers/web/admin/application_controller_test.rb create mode 100644 test/controllers/web/admin/users_controller_test.rb create mode 100644 test/factories/sequences.rb create mode 100644 test/factories/users.rb diff --git a/Gemfile b/Gemfile index c5eb628f..014b6285 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.0' +gem 'pg' # Use sqlite3 as the database for Active Record gem 'sqlite3' # Use SCSS for stylesheets @@ -26,6 +27,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'haml-rails' gem 'enumerize' gem 'authority' +gem 'bcrypt', '~> 3.1.7' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -45,5 +47,13 @@ group :development, :test do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' + gem 'factory_girl_rails' + gem 'pry-rails' + gem 'pry-byebug' end +group :test do + gem 'simplecov', require: false + gem 'tconsole', github: 'ulmic/tconsole', branch: 'rails4' + gem 'coveralls', require: false +end diff --git a/Gemfile.lock b/Gemfile.lock index ca2ca71c..25f14006 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,13 @@ +GIT + remote: git://github.com/ulmic/tconsole.git + revision: 73d91f7089f421a12193cc23d5d93c6cf51ab00b + branch: rails4 + specs: + tconsole (2.0.0.pre) + ansi + chattyproc (~> 1.0.0) + term-ansicolor (~> 1.0.7) + GEM remote: https://rubygems.org/ specs: @@ -36,10 +46,12 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + ansi (1.5.0) arel (6.0.0) authority (3.0.0) activesupport (>= 3.0.0) rake (>= 0.8.7) + bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) @@ -47,6 +59,8 @@ GEM columnize (~> 0.8) debugger-linecache (~> 1.2) slop (~> 3.6) + chattyproc (1.0.0) + coderay (1.1.0) coffee-rails (4.1.0) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -55,12 +69,24 @@ GEM execjs coffee-script-source (1.9.1) columnize (0.9.0) + coveralls (0.7.1) + multi_json (~> 1.3) + rest-client + simplecov (>= 0.7) + term-ansicolor + thor debug_inspector (0.0.2) debugger-linecache (1.2.0) + docile (1.1.5) enumerize (0.9.0) activesupport (>= 3.2) erubis (2.7.0) execjs (2.3.0) + factory_girl (4.5.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.5.0) + factory_girl (~> 4.5.0) + railties (>= 3.0.0) globalid (0.3.3) activesupport (>= 4.1.0) haml (4.0.6) @@ -90,12 +116,24 @@ GEM nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) + method_source (0.8.2) mime-types (2.4.3) mini_portile (0.6.2) minitest (5.5.1) multi_json (1.10.1) + netrc (0.10.2) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) + pg (0.18.1) + pry (0.10.1) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.0.1) + byebug (~> 3.4) + pry (~> 0.10) + pry-rails (0.3.3) + pry (>= 0.9.10) rack (1.6.0) rack-test (0.6.3) rack (>= 1.0) @@ -126,6 +164,9 @@ GEM rake (10.4.2) rdoc (4.2.0) json (~> 1.4) + rest-client (1.7.3) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) ruby_parser (3.6.4) sexp_processor (~> 4.1) sass (3.4.12) @@ -139,6 +180,11 @@ GEM json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) sexp_processor (4.4.5) + simplecov (0.9.2) + docile (~> 1.1.0) + multi_json (~> 1.0) + simplecov-html (~> 0.9.0) + simplecov-html (0.9.0) slop (3.6.0) spring (1.3.2) sprockets (2.12.3) @@ -151,6 +197,7 @@ GEM activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) sqlite3 (1.3.10) + term-ansicolor (1.0.7) thor (0.19.1) thread_safe (0.3.4) tilt (1.4.1) @@ -172,17 +219,25 @@ PLATFORMS DEPENDENCIES authority + bcrypt (~> 3.1.7) byebug coffee-rails (~> 4.1.0) + coveralls enumerize + factory_girl_rails haml-rails jbuilder (~> 2.0) jquery-rails + pg + pry-byebug + pry-rails rails (= 4.2.0) sass-rails (~> 5.0) sdoc (~> 0.4.0) + simplecov spring sqlite3 + tconsole! turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index dd4e97e2..00000000 --- a/README.rdoc +++ /dev/null @@ -1,28 +0,0 @@ -== README - -This README would normally document whatever steps are necessary to get the -application up and running. - -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... - - -Please feel free to use a different markup language if you do not plan to run -<tt>rake doc:app</tt>. diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 00000000..20490594 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,9 @@ +```shell +git clone git@github.com:ulmic/ulmicru.git +cd ulmicru +bundle +/* CREATE ROLE mic IN PG */ +rake db:create db:migrate +cp config/secrets.yml.sample config/secrets.yml +rails s +``` diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/admin/application.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/web/admin/users.coffee b/app/assets/javascripts/web/admin/users.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/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/stylesheets/web/admin/application.scss b/app/assets/stylesheets/web/admin/application.scss new file mode 100644 index 00000000..c649bcfb --- /dev/null +++ b/app/assets/stylesheets/web/admin/application.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/admin/application 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/web/admin/users.scss b/app/assets/stylesheets/web/admin/users.scss new file mode 100644 index 00000000..a242f002 --- /dev/null +++ b/app/assets/stylesheets/web/admin/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/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/controllers/web/admin/application_controller.rb b/app/controllers/web/admin/application_controller.rb new file mode 100644 index 00000000..b570d0df --- /dev/null +++ b/app/controllers/web/admin/application_controller.rb @@ -0,0 +1,2 @@ +class Web::Admin::ApplicationController < Web::ApplicationController +end diff --git a/app/controllers/web/admin/users_controller.rb b/app/controllers/web/admin/users_controller.rb new file mode 100644 index 00000000..d547ed6b --- /dev/null +++ b/app/controllers/web/admin/users_controller.rb @@ -0,0 +1,37 @@ +class Web::Admin::UsersController < Web::Admin::ApplicationController + def index + @users = User.all + end + + def new + @user = User.new + end + + def edit + @user = User.find params[:id] + end + + def create + @user = User.new params[:user] + if @user.save + redirect_to admin_users_path + else + render action: :new + end + end + + def update + @user = User.find params[:id] + if @user.update_attributes params[:user] + redirect_to admin_users_path + else + render action: :edit + end + end + + def destroy + @user = User.find params[:id] + @user.destroy + redirect_to admin_users_path + end +end diff --git a/app/helpers/web/admin/application_helper.rb b/app/helpers/web/admin/application_helper.rb new file mode 100644 index 00000000..8cdd567e --- /dev/null +++ b/app/helpers/web/admin/application_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::ApplicationHelper +end diff --git a/app/helpers/web/admin/users_helper.rb b/app/helpers/web/admin/users_helper.rb new file mode 100644 index 00000000..57a9f485 --- /dev/null +++ b/app/helpers/web/admin/users_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::UsersHelper +end diff --git a/config/database.yml b/config/database.yml index 1c1a37ca..efe633f8 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,25 +1,25 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -# default: &default - adapter: sqlite3 + username: postgres + adapter: postgresql + encoding: unicode pool: 5 - timeout: 5000 development: <<: *default - database: db/development.sqlite3 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. + username: develop + database: ulmic_development test: + adapter: postgresql + database: ulmic_test + pool: 5 + timeout: 5000 + +staging: <<: *default - database: db/test.sqlite3 + username: mic + database: ulmic_staging production: <<: *default - database: db/production.sqlite3 + username: mic + database: ulmic_production diff --git a/config/routes.rb b/config/routes.rb index e60e4304..67f4554f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,12 @@ Rails.application.routes.draw do root to: 'web/welcome#index' + scope :web do + namespace :admin do + resources :users + end + end + # 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/schema.rb b/db/schema.rb index 18fc2643..442276ca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,6 +13,9 @@ ActiveRecord::Schema.define(version: 20150220222045) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" diff --git a/test/controllers/web/admin/application_controller_test.rb b/test/controllers/web/admin/application_controller_test.rb new file mode 100644 index 00000000..281fda8a --- /dev/null +++ b/test/controllers/web/admin/application_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::Admin::ApplicationControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/web/admin/users_controller_test.rb b/test/controllers/web/admin/users_controller_test.rb new file mode 100644 index 00000000..bc7380b4 --- /dev/null +++ b/test/controllers/web/admin/users_controller_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' + +class Web::Admin::UsersControllerTest < ActionController::TestCase + setup do + @user = create :user + @admin = create :admin + sign_in @admin + end + + test "should get index" do + get :index + assert_response :success + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create user" do + attributes = attributes_for :user + + post :create, user: attributes + assert_response :redirect + + user = User.last + assert_equal attributes[:first_name], user.first_name + end + + test "should not create user" do + attributes = { first_name: @user.first_name } + + post :create, user: attributes + assert_response :success + end + + test "should get edit by admin" do + get :edit, id: @user + assert_response :success + end + + test "should update user by admin" do + attributes = attributes_for :user + put :update, id: @user, user: attributes + assert_response :redirect + + @user.reload + assert_equal attributes[:first_name], @user.first_name + end + + test "should not update user with render edit" do + attributes = attributes_for :user + attributes[:first_name] = nil + put :update, id: @user, user: attributes + + assert_response :success + + assert_template :edit + end + + test "should destroy user" do + assert_difference('User.count', -1) do + delete :destroy, id: @user + end + + assert_redirected_to admin_users_path + end +end diff --git a/test/factories/sequences.rb b/test/factories/sequences.rb new file mode 100644 index 00000000..3ee07c5d --- /dev/null +++ b/test/factories/sequences.rb @@ -0,0 +1,31 @@ +include ActionDispatch::TestProcess + +FactoryGirl.define do + sequence :string do |n| + "string#{n}" + end + sequence :integer do |n| + n + end + sequence :email do |n| + "email_#{n}@mail.com" + end + sequence :phone do |n| + "+7123456789" + end + sequence :url do |n| + "http://site#{n}.ru" + end + sequence :postcode do |n| + "434343" + end + sequence :date, aliases: [:start_date, :end_date] do |n| + Date.today + n.day + end + sequence :file do |n| + fixture_file_upload('app/assets/images/winners/0.png', 'image/png') + end + sequence :human_name do + "Leopold" + end +end diff --git a/test/factories/users.rb b/test/factories/users.rb new file mode 100644 index 00000000..16ce18db --- /dev/null +++ b/test/factories/users.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :user do + email + password { generate :string } + end +end From 4e8e6abf319ee26d9e944ecf9992e225791aa663 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 02:09:56 +0300 Subject: [PATCH 007/227] add factories and auth --- app/controllers/concerns/auth_managment.rb | 38 ++++++++++++++++++++++ app/views/web/admin/users/_form.html.haml | 25 ++++++++++++++ app/views/web/admin/users/edit.html.haml | 4 +++ app/views/web/admin/users/index.html.haml | 30 +++++++++++++++++ app/views/web/admin/users/new.html.haml | 4 +++ config/routes.rb | 2 +- test/factories/admins.rb | 5 +++ test/test_helper.rb | 20 ++++++++++-- 8 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 app/controllers/concerns/auth_managment.rb create mode 100644 app/views/web/admin/users/_form.html.haml create mode 100644 app/views/web/admin/users/edit.html.haml create mode 100644 app/views/web/admin/users/index.html.haml create mode 100644 app/views/web/admin/users/new.html.haml create mode 100644 test/factories/admins.rb diff --git a/app/controllers/concerns/auth_managment.rb b/app/controllers/concerns/auth_managment.rb new file mode 100644 index 00000000..f64e78b3 --- /dev/null +++ b/app/controllers/concerns/auth_managment.rb @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +module Concerns + module AuthManagment + def sign_in(user) + session[:user_id] = user.id + end + + def sign_out + session[:user_id] = nil + end + + def signed_in? + !current_user.guest? + end + + def signed_as_admin? + signed_in? && current_user.role.admin? + end + + def authenticate_admin! + redirect_to new_session_path unless signed_as_admin? + end + + def authenticate_user! + redirect_to new_session_path unless signed_in? + end + + def current_user + @_current_user ||= User.find_by(id: session[:user_id]) || Guest.new + end + + def required_basic_auth! + authenticate_or_request_with_http_basic do |user, password| + user == configus.basic_auth.username && password == configus.basic_auth.password + end + end + end +end diff --git a/app/views/web/admin/users/_form.html.haml b/app/views/web/admin/users/_form.html.haml new file mode 100644 index 00000000..4027b67f --- /dev/null +++ b/app/views/web/admin/users/_form.html.haml @@ -0,0 +1,25 @@ +.row + .col-md-4.col-md-offset-2 + = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, html: { class: 'form-horizontal' } do |f| + = f.hint t('hints.users.new.require_fields') + = f.input :email, placeholder: t('placeholders.user.email') + - if @user.email.nil? + = f.input :password + = f.input :role + = f.input :first_name + = f.input :patronymic, as: :string + = f.input :last_name + = f.input :birth_date, as: :date_picker + = f.input :municipality + = f.input :locality, as: :string, placeholder: t('placeholders.user.locality') + = f.input :school, as: :string, placeholder: t('placeholders.user.school') + = f.input :group, as: :string + = f.input :home_phone, as: :string, placeholder: t('placeholders.user.home_phone') + = f.input :mobile_phone, as: :string, placeholder: t('placeholders.user.mobile_phone') + = f.input :avatar, as: :image_preview, input_html: { preview_version: :medium } + = f.input :creative_work, as: :file + = f.input :url_creative_work, as: :string, placeholder: t('placeholders.user.url_creative_work') + = f.input :average + = f.input :state_event, as: :state_event + = f.input :reserve_order_number + = f.button :submit diff --git a/app/views/web/admin/users/edit.html.haml b/app/views/web/admin/users/edit.html.haml new file mode 100644 index 00000000..f8af3acd --- /dev/null +++ b/app/views/web/admin/users/edit.html.haml @@ -0,0 +1,4 @@ += title_is +%h1= t('.title') += render partial: 'form', locals: { action: :update } += link_to t('.back'), admin_users_path, class: "btn" diff --git a/app/views/web/admin/users/index.html.haml b/app/views/web/admin/users/index.html.haml new file mode 100644 index 00000000..b42f9cec --- /dev/null +++ b/app/views/web/admin/users/index.html.haml @@ -0,0 +1,30 @@ += javascript_include_tag :tabs += stylesheet_link_tag :tabs + += title_is +.page-header + %h1=t '.title' +- model_class = User +%table.table.table-striped.table-condensed + %thead + %tr + %th= model_class.human_attribute_name(:id) + %th= model_class.human_attribute_name(:avatar) + %th= model_class.human_attribute_name(:full_name) + %th= model_class.human_attribute_name(:birth_date) + %th= model_class.human_attribute_name(:place) + %th= model_class.human_attribute_name(:school_group) + %th= model_class.human_attribute_name(:contacts) + %th= model_class.human_attribute_name(:creative_work) + %th= model_class.human_attribute_name(:average) + %th=t '.actions', default: t("helpers.actions") + %tbody + - users.each do |user| + %tr + %td= link_to user.id, edit_admin_user_path(user) + %td= link_to user.email, edit_admin_user_path(user) + %td + = link_to t('.edit', default: t('helpers.links.edit')), edit_admin_user_path(user), class: 'btn btn-warning btn-xs' + = link_to t('.destroy', default: t('helpers.links.destroy')), admin_user_path(user), method: :delete, data: { confirm: t('.confirm', default: t('helpers.links.confirm', default: 'Are you sure?')) }, class: 'btn btn-xs btn-danger' + += link_to t('.new', default: t('helpers.links.new')), new_admin_user_path, class: 'btn btn-primary' diff --git a/app/views/web/admin/users/new.html.haml b/app/views/web/admin/users/new.html.haml new file mode 100644 index 00000000..91d7a2f2 --- /dev/null +++ b/app/views/web/admin/users/new.html.haml @@ -0,0 +1,4 @@ += title_is +%h1= t('.title') += render partial: 'form', locals: { action: :create } += link_to t('.back'), admin_users_path, class: "btn" diff --git a/config/routes.rb b/config/routes.rb index 67f4554f..d50adb38 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ Rails.application.routes.draw do root to: 'web/welcome#index' - scope :web do + scope module: :web do namespace :admin do resources :users end diff --git a/test/factories/admins.rb b/test/factories/admins.rb new file mode 100644 index 00000000..fa8b8d36 --- /dev/null +++ b/test/factories/admins.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :admin, parent: :user do + role :admin + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 92e39b2d..3927051d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,10 +1,26 @@ -ENV['RAILS_ENV'] ||= 'test' +if ENV["TRAVIS"] + require 'coveralls' + Coveralls.wear! +end + +require 'simplecov' +ENV["RAILS_ENV"] = "test" +SimpleCov.start('rails') if ENV["COVERAGE"] + require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' +FactoryGirl.reload + class ActiveSupport::TestCase + include FactoryGirl::Syntax::Methods + include Concerns::AuthManagment # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all + # fixtures :all + def load_fixture(filename) + template = ERB.new(File.read(File.dirname(__FILE__) + "/fixtures/#{filename}"), nil, "%") + template.result + end # Add more helper methods to be used by all tests here... end From 1e9a622d442641fc23b17da1caf86e8c519f3253 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 02:27:50 +0300 Subject: [PATCH 008/227] add types --- app/types/application_type.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 app/types/application_type.rb diff --git a/app/types/application_type.rb b/app/types/application_type.rb new file mode 100644 index 00000000..48539a6c --- /dev/null +++ b/app/types/application_type.rb @@ -0,0 +1,28 @@ +module ApplicationType + extend ActiveSupport::Concern + + module ClassMethods + def model_name + superclass.model_name + end + + def name + superclass.name + end + + def permit(*args) + @_args = args + end + + def _args + @_args + end + end + + def assign_attributes(attrs = {}, options = {}) + raise ArgumentError, "expected hash" if attrs.nil? + permitted_attrs = attrs.send :permit, self.class._args + #raise [attrs, permitted_attrs].inspect + super(permitted_attrs) + end +end From eb59c379a3fdfe893e3f6cbff0604882b2e1d43f Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 23:07:58 +0300 Subject: [PATCH 009/227] remove types --- app/types/application_type.rb | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 app/types/application_type.rb diff --git a/app/types/application_type.rb b/app/types/application_type.rb deleted file mode 100644 index 48539a6c..00000000 --- a/app/types/application_type.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ApplicationType - extend ActiveSupport::Concern - - module ClassMethods - def model_name - superclass.model_name - end - - def name - superclass.name - end - - def permit(*args) - @_args = args - end - - def _args - @_args - end - end - - def assign_attributes(attrs = {}, options = {}) - raise ArgumentError, "expected hash" if attrs.nil? - permitted_attrs = attrs.send :permit, self.class._args - #raise [attrs, permitted_attrs].inspect - super(permitted_attrs) - end -end From b7efa3f1469e6ed6bc99ef1aca6b9d9ae7e4f77d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 23:17:02 +0300 Subject: [PATCH 010/227] some fix --- Gemfile | 2 ++ Gemfile.lock | 6 +++++ app/views/web/admin/users/_form.html.haml | 30 ++++------------------- app/views/web/admin/users/edit.html.haml | 1 - app/views/web/admin/users/index.html.haml | 3 +-- app/views/web/admin/users/new.html.haml | 1 - 6 files changed, 14 insertions(+), 29 deletions(-) diff --git a/Gemfile b/Gemfile index 014b6285..48d1d157 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,8 @@ gem 'haml-rails' gem 'enumerize' gem 'authority' gem 'bcrypt', '~> 3.1.7' +gem 'actionform' +gem 'simple_form' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 25f14006..46576e01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,6 +11,7 @@ GIT GEM remote: https://rubygems.org/ specs: + actionform (0.0.0) actionmailer (4.2.0) actionpack (= 4.2.0) actionview (= 4.2.0) @@ -180,6 +181,9 @@ GEM json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) sexp_processor (4.4.5) + simple_form (3.1.0) + actionpack (~> 4.0) + activemodel (~> 4.0) simplecov (0.9.2) docile (~> 1.1.0) multi_json (~> 1.0) @@ -218,6 +222,7 @@ PLATFORMS ruby DEPENDENCIES + actionform authority bcrypt (~> 3.1.7) byebug @@ -234,6 +239,7 @@ DEPENDENCIES rails (= 4.2.0) sass-rails (~> 5.0) sdoc (~> 0.4.0) + simple_form simplecov spring sqlite3 diff --git a/app/views/web/admin/users/_form.html.haml b/app/views/web/admin/users/_form.html.haml index 4027b67f..a16c644f 100644 --- a/app/views/web/admin/users/_form.html.haml +++ b/app/views/web/admin/users/_form.html.haml @@ -1,25 +1,5 @@ -.row - .col-md-4.col-md-offset-2 - = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, html: { class: 'form-horizontal' } do |f| - = f.hint t('hints.users.new.require_fields') - = f.input :email, placeholder: t('placeholders.user.email') - - if @user.email.nil? - = f.input :password - = f.input :role - = f.input :first_name - = f.input :patronymic, as: :string - = f.input :last_name - = f.input :birth_date, as: :date_picker - = f.input :municipality - = f.input :locality, as: :string, placeholder: t('placeholders.user.locality') - = f.input :school, as: :string, placeholder: t('placeholders.user.school') - = f.input :group, as: :string - = f.input :home_phone, as: :string, placeholder: t('placeholders.user.home_phone') - = f.input :mobile_phone, as: :string, placeholder: t('placeholders.user.mobile_phone') - = f.input :avatar, as: :image_preview, input_html: { preview_version: :medium } - = f.input :creative_work, as: :file - = f.input :url_creative_work, as: :string, placeholder: t('placeholders.user.url_creative_work') - = f.input :average - = f.input :state_event, as: :state_event - = f.input :reserve_order_number - = f.button :submit += simple_form_for @user, url: { controller: 'web/admin/users', action: action } do |f| + = f.input :email + = f.input :password + = f.input :role + = f.button :submit diff --git a/app/views/web/admin/users/edit.html.haml b/app/views/web/admin/users/edit.html.haml index f8af3acd..7c47ceea 100644 --- a/app/views/web/admin/users/edit.html.haml +++ b/app/views/web/admin/users/edit.html.haml @@ -1,4 +1,3 @@ -= title_is %h1= t('.title') = render partial: 'form', locals: { action: :update } = link_to t('.back'), admin_users_path, class: "btn" diff --git a/app/views/web/admin/users/index.html.haml b/app/views/web/admin/users/index.html.haml index b42f9cec..54875ee7 100644 --- a/app/views/web/admin/users/index.html.haml +++ b/app/views/web/admin/users/index.html.haml @@ -1,7 +1,6 @@ = javascript_include_tag :tabs = stylesheet_link_tag :tabs -= title_is .page-header %h1=t '.title' - model_class = User @@ -19,7 +18,7 @@ %th= model_class.human_attribute_name(:average) %th=t '.actions', default: t("helpers.actions") %tbody - - users.each do |user| + - @users.each do |user| %tr %td= link_to user.id, edit_admin_user_path(user) %td= link_to user.email, edit_admin_user_path(user) diff --git a/app/views/web/admin/users/new.html.haml b/app/views/web/admin/users/new.html.haml index 91d7a2f2..22e1f7a6 100644 --- a/app/views/web/admin/users/new.html.haml +++ b/app/views/web/admin/users/new.html.haml @@ -1,4 +1,3 @@ -= title_is %h1= t('.title') = render partial: 'form', locals: { action: :create } = link_to t('.back'), admin_users_path, class: "btn" From 45eeaf238f7f9e6c53a2c425540c5861c9d5dd6b Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 21 Feb 2015 23:42:30 +0300 Subject: [PATCH 011/227] add forms --- app/controllers/web/admin/users_controller.rb | 4 ++-- app/forms/user_form.rb | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 app/forms/user_form.rb diff --git a/app/controllers/web/admin/users_controller.rb b/app/controllers/web/admin/users_controller.rb index d547ed6b..caf76911 100644 --- a/app/controllers/web/admin/users_controller.rb +++ b/app/controllers/web/admin/users_controller.rb @@ -4,11 +4,11 @@ def index end def new - @user = User.new + @user = UserForm.new User.new end def edit - @user = User.find params[:id] + @user = UserForm.new User.find params[:id] end def create diff --git a/app/forms/user_form.rb b/app/forms/user_form.rb new file mode 100644 index 00000000..b3e8eb41 --- /dev/null +++ b/app/forms/user_form.rb @@ -0,0 +1,5 @@ +class UserForm < ActionForm::Base + self.main_model = :user + + attributes :email, :password, :role +end From fa9d8d405e24e92bfcd103f133334cac7951d525 Mon Sep 17 00:00:00 2001 From: Ilya Shakirov <aelaau@gmail.com> Date: Sun, 22 Feb 2015 11:10:41 +0300 Subject: [PATCH 012/227] vot tak eto delaetsya --- .Gemfile.lock.swp | Bin 0 -> 16384 bytes .Gemfile.swp | Bin 0 -> 12288 bytes Gemfile | 2 +- Gemfile.lock | 11 +++++++++-- .../web/admin/.users_controller.rb.swp | Bin 0 -> 12288 bytes app/controllers/web/admin/users_controller.rb | 16 +++++++++++----- app/forms/.application_form.rb.swp | Bin 0 -> 12288 bytes app/forms/.user_form.rb.swp | Bin 0 -> 12288 bytes app/forms/application_form.rb | 2 ++ app/forms/user_form.rb | 4 ++-- config/.database.yml.swp | Bin 0 -> 12288 bytes config/database.yml | 2 +- db/migrate/.20150220220847_create_users.rb.swp | Bin 0 -> 12288 bytes .../web/.application_controller_test.rb.swp | Bin 0 -> 12288 bytes .../web/.welcome_controller_test.rb.swp | Bin 0 -> 12288 bytes .../web/admin/.users_controller_test.rb.swp | Bin 0 -> 12288 bytes .../web/admin/users_controller_test.rb | 1 + ulmic_test | Bin 0 -> 5120 bytes 18 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 .Gemfile.lock.swp create mode 100644 .Gemfile.swp create mode 100644 app/controllers/web/admin/.users_controller.rb.swp create mode 100644 app/forms/.application_form.rb.swp create mode 100644 app/forms/.user_form.rb.swp create mode 100644 app/forms/application_form.rb create mode 100644 config/.database.yml.swp create mode 100644 db/migrate/.20150220220847_create_users.rb.swp create mode 100644 test/controllers/web/.application_controller_test.rb.swp create mode 100644 test/controllers/web/.welcome_controller_test.rb.swp create mode 100644 test/controllers/web/admin/.users_controller_test.rb.swp create mode 100644 ulmic_test diff --git a/.Gemfile.lock.swp b/.Gemfile.lock.swp new file mode 100644 index 0000000000000000000000000000000000000000..7bbc4c853f54737104cb9919287df07087361fda GIT binary patch literal 16384 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 V3&&;ctm@8IhQ!*9&5<}k{sq;WlQRGS literal 0 HcmV?d00001 diff --git a/.Gemfile.swp b/.Gemfile.swp new file mode 100644 index 0000000000000000000000000000000000000000..d032bead910504a7586854922091f428c7d4490e GIT binary patch literal 12288 zcmeHNJ8T?97@pAZOhC{<^Cze16cpdtv7>|&j+BQ02PGh197=0;_rJTf_jcAZv%Xw} zf<%ju5Dg+wP$AJ!Qlf~CnvRDAhz1%8IuKI6ncY2~Aqpu{5NTHWwBDKjHQ)bOTi$5# z>e?kb)mag^o)Y5vyT4xf`uI)p{u4q-&ZKnf|8aS_g;!55_b=R4@`e}emf1Lsb=iHH z$AdKEyQ+lEz(8Q&elSql!NTc-V)?|<qBGYza+scZdgFdr4mJV<fq}q4U?4CM7zhjm z1_A?tfd_;ETO1H?A=ZcLcvtKDfq&fV$8ZY_1O@^Ffq}q4U?4CM7zhjm1_A?tfxtju z-~nVnCPIAvun=E9g5dH0|K{)ipPv-sC*Vim6W|@-ZD1X^02~7j0pA@G;ui28umc<c z{yHecpTIZ3E#NY+3jBd@3cdwC2Ce}UU;tbNUIG??-yav^2jF|)3*aN*CU70t0#aZN zcn)|L`1>&-J_Bw79|Erc&jZJRPahTH1K>?y3wQ(Q1M9$f;4ttD&ixhO&i|X^AY6fg zz(8OiFc26B4E)a-;5-pTLmpE*R=ccamYMc((!5iqntL>mnc+sl#ws@|V+agWJ1V0d zwf(n<-MO7e%RC-oN6R!bEA2+vq~juEIQh5vp;l!<J<G;YqJ)?Ws?gJgXv$GJoEdUU z-u7%|AY-fabaR;MY;*sXDRi29-4^xz&twYb#1K3tyvvy?#++M<rnJpmK$xbg)Fg`{ zOJiwMm6I;>+-;;e*%4DD<82pmo^)uPWkO@+s2W*Ym|nMAQPYXlxa(u-RxyZ%hzUmv zdqlQxoS_q)MR%4KosDBQhGb4_>*uHzB1FK~VlI3vg&eXWrCq?KaZ$Z^baf_r^o$E~ zH|2?$`P-wfMdy)UYS%09Go6fBb91m*B}`V4v8sSej!W-S<_Rjqk_2H$#E^C4Pt=*4 zBl7rM9$R%`$s#otWufZ4p?qA%d(@j#6fN2?j0hWH8sL-(7nz#64B?Br%3PVJvC?_F zR@kT>NgAIV<c$n#l=4g=iuJUxC_BgXa%ZV?s?j|g+jN&N;n*3Cq}en$vgXY~8Z%KE zzu5c6s4908;f&kW3hE7d)yiBiROxn`D)-W^kp^gml_ic}IC;9(zEeEjku2><>C{eZ zLC(uD>-0L;&m%d`n$oXBOH^DRY_YR~GMb}S#b78xm1gXdNm_0*`YZ1FI6-EPen-=J z-p;<hMvz5rA<;z=I+cg5s7x~~*Is<7w&<K&q>KI6E)x_DO)w6KrQv()h{kB;!j?Kx zS(<MfmzOKECmJJQgEGi6HF1^hWn8iP4n-gB>(YTVmi4^_u;;Qq<xa_aW2LIZEx*>( z)kV20`>{?7i!d<b^mJBVm<hU!rdr0HrXrKpW#6E_-8#<HXTH;TFbKF9F=lGsb?vrg zS{n0Y%)a#9R3TFqh0>N2H-v0DW>W<mtHFSI)=#doS5VoQBXt}3{W@URNSH@X1IICJ z-H1u*G2tt(9A@btt+IS(m6kfod!{z(J;wxwTA?wbV9*(zBc-xi1!wTEFSL_;*81oB zeXncmG-ESjL{n?+WU1xl<^-mCZ1_*4l@SWe>sEb>;fq0#RN4iGygy1c|IzF#omsI~ tJ&+8U*4;}9B?RVW(xZJEvv*q6$CV+xzYX38F;lx4@GhxPG##bk^fqXps~i9T literal 0 HcmV?d00001 diff --git a/Gemfile b/Gemfile index 48d1d157..39e32216 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'haml-rails' gem 'enumerize' gem 'authority' gem 'bcrypt', '~> 3.1.7' -gem 'actionform' +gem 'active_form', github: 'rails/actionform', ref: '41ec958' gem 'simple_form' # Use ActiveModel has_secure_password diff --git a/Gemfile.lock b/Gemfile.lock index 46576e01..ce39e886 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: git://github.com/rails/actionform.git + revision: 41ec958375ce6eb0df45a7623541dcd72aea773e + ref: 41ec958 + specs: + active_form (0.0.1) + rails (~> 4.1) + GIT remote: git://github.com/ulmic/tconsole.git revision: 73d91f7089f421a12193cc23d5d93c6cf51ab00b @@ -11,7 +19,6 @@ GIT GEM remote: https://rubygems.org/ specs: - actionform (0.0.0) actionmailer (4.2.0) actionpack (= 4.2.0) actionview (= 4.2.0) @@ -222,7 +229,7 @@ PLATFORMS ruby DEPENDENCIES - actionform + active_form! authority bcrypt (~> 3.1.7) byebug diff --git a/app/controllers/web/admin/.users_controller.rb.swp b/app/controllers/web/admin/.users_controller.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..a982d33f6d2a3d3da465d1c108777d15456f6803 GIT binary patch literal 12288 zcmeI2ziSjh6vtnz1dWQKpjb@QA|kmxjlWnm7?cE&R2oGjEVH|FxU#>P-8I@M7Gfuw zLM*H-Ed2xQw6M`XLJ$OtSXtTVcjj*0^T1027DnE}=Vo{3&AazLyUlUqx36576DL|n z3CA{~PmjJ_e!AxcEo~-}DwfjRU*WKE6Bmz7UOGQC@~#bZsyK-PT{$w(oghm~oyD=z zg|n!9M}|q1I#q#dubFFUe`sQ!WCLtqxPhwdOq?90$s>o`CUF10F|lL&{O~nA%Ldp0 z8(;%$fDNz#Hoykhz<*_+%tz=RI@-|d>QHYS85nz)Jg@;azy{a=8(;%$fDNz#Hoykh z02^Qf>(GD<iN0?ldW--2&Hw-P@Bi0ZiC%$cU<o_{4}k`AU>3}PQ(!mvwuR^`SOy=# zd+-w62RFeim;wjDUa$-71RKHUQKC2CId}kka1Bg><Dd;3Fb000&SmfcyaT50$AAwu zzy{a=8(;%$fDNz#Hoykfp8=JIBt-X92(7|MtDyACOxUf#p4}Q;2Ptz>Uzl}&T;b)i zykP^*nhdQ_71(!tr@14}h<P*7iPBKyQp=>c?ndDPB0|*x-FRHqwbi5f&NNh7$e@g} z)D<p@E^THN7s{TjqOPbTY-wI6(@Cq4x4J9)?^S*hmE(1BrjLb#HhmM+Yh%XbNY&!F zo#_i&q0K4}WvOUYT_XKI-ek8z^Y{X-2KxGI%Iw4L6SnrmO_}CwFjY}MG8xC1Gu_}7 qRktaKWl@N$%6HvqvuoaU%?n(d`?aH9oc?WgI?v-Mkmen2V(2HU9$M@G literal 0 HcmV?d00001 diff --git a/app/controllers/web/admin/users_controller.rb b/app/controllers/web/admin/users_controller.rb index caf76911..0c041342 100644 --- a/app/controllers/web/admin/users_controller.rb +++ b/app/controllers/web/admin/users_controller.rb @@ -4,16 +4,20 @@ def index end def new - @user = UserForm.new User.new + @user = User.new + @user_form = UserForm.new(@user) end def edit - @user = UserForm.new User.find params[:id] + @user = User.find params[:id] + @user_form = UserForm.new(@user) end def create - @user = User.new params[:user] - if @user.save + @user = User.new + @user_form = UserForm.new(@user) + @user_form.submit(params[:user]) + if @user_form.save redirect_to admin_users_path else render action: :new @@ -22,7 +26,9 @@ def create def update @user = User.find params[:id] - if @user.update_attributes params[:user] + @user_form = UserForm.new(@user) + @user_form.submit(params[:user]) + if @user_form.save redirect_to admin_users_path else render action: :edit diff --git a/app/forms/.application_form.rb.swp b/app/forms/.application_form.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..58e515836c5ac054b77b22aca598b62540f34b97 GIT binary patch literal 12288 zcmeI&O-{l<6u|KZYYhZ<u0~x$Q8XIj24hSJaV5GnLm84Lr8NCuDJO8FH}C@9MHild zTR217xWllM_<zY{I<K#jzTd9tjM@+VTXo(#6&rPt*XQMMvHvD7J0jYITD$x&v8rnh z&N_G3Yb&2tg<c*`13$?<9mn2emP}K36$ZY}f?4$FGOc8^b|Y>gfWT$~c{XWY>`Le4 zxa}T1Iy_K&jr+~^pc(-L5I_I{1Q0*~0R*;MAd4%qupLy3F3P!5tjl?;o8;vYKmY** z5I_I{1Q0*~0R#~E=K^{x@?I19w8Q=XFTel4irk0G69^!H00IagfB*srAb<b@2q5rx l1WaV2v9Uw^P^YQt{hX`6vB&DwrRw=vFgI?|?Oy5B$T!8tIc)#{ literal 0 HcmV?d00001 diff --git a/app/forms/.user_form.rb.swp b/app/forms/.user_form.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..095c7e4aa678eff1f8b1207d49aa344b6c2491ec GIT binary patch literal 12288 zcmeI&ze~h06bJBkf}5h~EJVf4Yfn#6iVlJZB2Id4QqnesLi&Rw#ZCVL1)ZGzTlDX6 z_P_AusEfl1=XCH5Jo2OOh49%*k_>LnFU3KyN8FZ)9-qFhx7Ob1Wr0X4Eu~-oqmA?W z7yHAj(}|h)<HWGlWs#aTluZ-nwJB}bT4iEC3`{a{2YiG81f~*bUEV)jq~Y$)z}IhX ztc%r^(NtTZ2m%m*00bZa0SG_<0uY!@0oU~Cfsa1d9e=*-d%yZ_iVgx0fB*y_009U< z00Izz00bZaff*E#8PV+$-}`g;`~RQ+|4*KOaJ+LobNKJC9B2@L00bZa0SG_<0uX=z z1Rwwb2>eq(RT&8(oijz!I%P$qN?GV_5jE1<yV_(tG__XT<%O{>u4Ji*Ypl|Fz`0di k)|t}cSVaC&J*Aop#fXPz{JdTqiIb+$MJioUSAK=Q0qnm_$p8QV literal 0 HcmV?d00001 diff --git a/app/forms/application_form.rb b/app/forms/application_form.rb new file mode 100644 index 00000000..90fdd4be --- /dev/null +++ b/app/forms/application_form.rb @@ -0,0 +1,2 @@ +class ApplicationForm < ActiveForm::Base +end diff --git a/app/forms/user_form.rb b/app/forms/user_form.rb index b3e8eb41..bb507ea9 100644 --- a/app/forms/user_form.rb +++ b/app/forms/user_form.rb @@ -1,5 +1,5 @@ -class UserForm < ActionForm::Base +class UserForm < ApplicationForm self.main_model = :user - attributes :email, :password, :role + attributes :email, :password, :role, :first_name end diff --git a/config/.database.yml.swp b/config/.database.yml.swp new file mode 100644 index 0000000000000000000000000000000000000000..005943998a501e7c529148118bdfb11090f6803f GIT binary patch literal 12288 zcmeI2F-sgl6vyA$=G2f>fdnQ7ECla1IYn8tvyl)GqPAhYojVqGcQ(7TLLg{LNtwpV zMoby<1?+50YiDg`W0NnC|1NvnDWYH}`49JFZr;9m`{vgjH(TE3>Ib#ZniGttBAxG- z>u0YH<wsLQn@H<2e$S}#X5Y_uKCaxExl<*!izp7eS<&v=q!;$vfp&UJ=ce@~j_%yf zhcrL~4>eG@-t6MIbl$!7$`Ws;UaRNNHXd4y`)Gg$Xn+Q2fCgxQ255i=9-#r3He?rl zkJY`usOQFwxn6Rj0UDqI8lV9hpaB}70UDqI8lV9hc!UOYfTkxzzJc=p|H1G7t4Wb_ zZ~}gVpWq1WgFWEDCRhW@U>dvxP4EPafxoDE0seqfa14Hd15m#2E8w638lV9hpaB}7 z0UDqI8lV9h_zw&y72s3f|MpdNG5k}S*`VmU&?cf(k((^hvGEn|jAOmD<g1y$^mGw9 z8D{!|XEzJwPWQv4e_xe4tt#Qd*x17PYTolaY)q|<u(+kTGr2=p2Rd~o^Hu&i3Z3al z8TW6=56rfSY#N)S+%`N`9Y^fi(MHm%#)gfUq-z5l3%ZgJU(IM2sm)zKGic`8O9o-7 K&G~A2;P4-y?5<n@ literal 0 HcmV?d00001 diff --git a/config/database.yml b/config/database.yml index efe633f8..46495873 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,7 +9,7 @@ development: username: develop database: ulmic_development test: - adapter: postgresql + adapter: sqlite3 database: ulmic_test pool: 5 timeout: 5000 diff --git a/db/migrate/.20150220220847_create_users.rb.swp b/db/migrate/.20150220220847_create_users.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..d154cee9694f768d39e988a79e3712dc5912f0d6 GIT binary patch literal 12288 zcmeI&zfQtH90%~j(P%WfIsMcbS_%ZxxELHX1_u>Za%~T6axJ92q65)4a4-(ePQHmR z;7hpq2>x1869<^p?}aay-u?RTvk`ut-8<?~vr-p4)`fVxeLa8JdK7mnLP({h%-0u; zqON@0sGS|mt-LM@txQLupJZ0xSy4Dlq*0b*R~xqD<eyf(+4mF0tNkohNm@y~xfjJI z1R$_jATxt<YgN>os+||_?rhWMMtAXfsDJ<jAOHafKmY;|fB*#kjDQ)J#0|gA((H|v zXLIStJUhe$0SG_<0uX=z1Rwwb2tWV=5P-lh6p#Tw^qLR{oc#a)&)@$qeEiIL;@op4 zoQ%`soNx{~`8jP41_U4g0SG_<0uX=z1Rwwb2teRJ2&gy^L?7SZGm)v7FjA?J(Kw|z z)7qth)Tt6&oe$nuSBBiNOw-Fz67+*`$mOEg{MuEK40W+KYl}D1(~8`pJD!3OP0f_I r12rK3LdHWSe9af6cF{!N&3ou+kM?~NPSmOL`97}ODf;fiQ7qm8Ju_!z literal 0 HcmV?d00001 diff --git a/test/controllers/web/.application_controller_test.rb.swp b/test/controllers/web/.application_controller_test.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..32717c03c985c1cbd0672657f1ea517f0a8ee40b GIT binary patch literal 12288 zcmeI&ze~eF6bJBE7Z*j*S$L*HC!1;&F}Revh&WkwESK~_180+7F1Yw(>ngarxcIl# zzrwrHT5yQ7@;!Jw?%vBmK3m9(TQ}#IeB3-Fw>F91Cez--&O5!X5h>A1`SV}atZ(n) zsC{)-+4(eU3`>2NCZ-In6gEt<ku{mtVv6uy;!x$eP7`I*Y}8-Y`o6bm;>zn$ga8C8 z1=gv3u;216^<bB`x2`L@QG@^lAOHafKmY;|fB*#kmw?S{^z2?~W&T>L^IZFp=UXHQ zKmY;|fB*y_009U<00Izz00jP^fEo~;Y!HoI`2YWl@BdG?pSt*P@#5mqMd`x#<u0%w z009U<00Izz00bZa0SG_<0uZPWkkNpc>%7R!swl)*wx*O>#m~BPV29$5>@eU#Mu~P^ vd@FGjb(RzI-S6S~o`2?)JBc5hUmi!1FLYHQL`KGCYJ?m9{{B#OE~Y_W9Xnuk literal 0 HcmV?d00001 diff --git a/test/controllers/web/.welcome_controller_test.rb.swp b/test/controllers/web/.welcome_controller_test.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..26b204eaba59b03a97183ac694fc3dbdb75f54d5 GIT binary patch literal 12288 zcmeI&ze~eF6bJBE7Z*j*S$L*HC!1;&A?P4=5plBGG33XC6s}FZTo4Bp7ykgAU0nUk z>R;hqZ7n!7v+_N79QW><gM7A-i(A)c7kt><Cp*@O-k!(Z`>l6+Ss{u<MUm@Qj7e_i z{Gfe#x-j`L3HqfP^b=kBMhfF6`OxTGDba<0E3qw=<O2!k{m|u_I$n4*HX#6kN`X~s z@9nl+tnO{|=Eha!69_;60uX=z1Rwwb2tWV=|3|=#YV>GdXKDIM%hS2`Yo0DKK>z{} zfB*y_009U<00Izz00bcL2L+;(=y;8&wB`Q)&%XaZ?RsqM!`7><CtG*6T+Z0SfB*y_ z009U<00Izz00bZa0SG{#LO_NoG1qxEGe<=s+OW~3Om^IzclJysu4OWh)0`4zQ{0m{ t2zuYQ<U2o?=esU{#3zaA=finB2;4>|DuhVOP1)DN4R>~!i5iJ+&=(dOT}A)^ literal 0 HcmV?d00001 diff --git a/test/controllers/web/admin/.users_controller_test.rb.swp b/test/controllers/web/admin/.users_controller_test.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..227350af569d095e9603ff797e3ed370289b2ec9 GIT binary patch literal 12288 zcmeI2J!lj`6vro`g~o4Dv=GLmNCL@Sq7h|LFh(OHRwjNRvh3~7<remK*O^(Ohzd3q zniN{vSy-x#f`yG%)`A!<w2cV%YT<uoFS!rB#Av0wgJ1S`XLjbj-<w@HGNr-O$4~No zg<*!zM#kPx&yBy?d7s@`%a|u3&okGT_}G2}C-&|+b!5?=*KEgP7F9!EW<@QOF8WEV zWfDa~s^XL=6}_Mu#>GtG+^x5{rf)%(79E}%$p9H>XCTw%f&J^)p4~%(rb~C%F1}^+ zM7!Q}oeYoxGC&5%02v?yWPl8if&b8ePCM9j<h7!f+tJ#wqvcpTr2`ot17v^<kO4A4 z2FL&zAOmE843Ggb@DCdB0tCI9vB5Ps;{X5izyCjNU~CRN0QbNRZ~+_!MbHO&K_~dW zp0N*L7Q6+|!DDb2OoQ8?3XXv>&<$3BmEg-d#-4yj;32pL&VjRFFW3$?gH7Nw_9VOp zPr+mG2;2l77zW$GR<IJh>SXLCcmZZW1&o0mU_1DRe7}N^;2oF+&%g|*0s#WxfMH<$ z{DbwSkjVfUAOmE843Ggb@V5p;95BxFznpst8#T2nMG#8iYZ16Q;dTqBn{VMvJzZhA zp?(YJfrx|_+zGI!Qzn=Dc^EkSkm+wb*2WCNa#=_b`=YmJ0&N99$zt8p&j*J3c#xp0 z*%QjUR3*ux0E61WxEZF)w4*~ylv-5N$TKq|T%AX?kbp{)SfNQ}zAu!@ThmN)Crbm| z)DUIAoy6gu)-o(*2qTYs;q<&y4yDp=>{Z1Devrpu)Y1bN>vy~5L}Hpe|E8qkMDu)l zJ0+hAb%n!8AS5?Av`JK4%)F>!f~I)-bJc}<h&DFszaT}Fm|p}OpP$ssSJ}4yp_<=J zyfNw0<$Oi8Nmh#1(wX2V1uAD0HPj7pH`z6POc(w~Go%StpW{nm7l)}{(~7W?>NQ5* zS9o1{ZM`=XfnRM6&lEo&ez8TpM6FF+4YSy&%r(R_VVWXOwR;kp%e~xe>#4V2-b-h< zNO5B^)oRQX6qlF3P(nOWmwq*xZD<`&g_E%x#{7_d(OISWS4gd;<TNxH-Nr$+=6noa z3kb8<K5~8e{k59R-)>IPpoGp+^D^^MC&kZ*lH-h+4f2j-s;5R5HqVcnci{m(;_EPp e>z5tJG>m#mFeb57@RsI1<{@w^B1(nqVLt$H_CA{c literal 0 HcmV?d00001 diff --git a/test/controllers/web/admin/users_controller_test.rb b/test/controllers/web/admin/users_controller_test.rb index bc7380b4..8abc02c4 100644 --- a/test/controllers/web/admin/users_controller_test.rb +++ b/test/controllers/web/admin/users_controller_test.rb @@ -3,6 +3,7 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase setup do @user = create :user + @user_form = UserForm.new(@user) @admin = create :admin sign_in @admin end diff --git a/ulmic_test b/ulmic_test new file mode 100644 index 0000000000000000000000000000000000000000..337ac8b0e9fd1146e48296ead30a03757ca3eb44 GIT binary patch literal 5120 zcmeHG&1>5*6xW9{v%#R-#aE$IO<R&>E!$<VY&9b+b<)g%<z%#2YX!E`T1wknN&n6C zR@kwB!=BTy#S3A#28x9J^z?gvUq9=8JMJhW+0Sv23B%qZ2VsmpGKLVUEne8>i-L3( zdb!~xeVtlEd%ws_usZk-(LFLF87Ou=CIeH~alH?9Jguudkyn#kolj(}N2f9qai&fR zVbnO+D?sgYKWIe(8-(rSLBQJKVep;tb?%&XL&jHvaJJ1aWT7Dg_fzV6btuF&;zuf1 zzFh&FMFmmoQz!UOaCRYz(Wxj{*o{~?=yd!ZdFJix;Ne5E^AzrRtR?iGkEE_1X(iD= z#_e2Wau+=PX%D-e=i!^{dCZB1#^!=l{P`R!J68z@-lE`3&}Y4V`>53)vai9AwFXhQ z9fH$Q5JI(!igy`@U!qdZjJz^%Iulx7j*BEt)QQyQ4?ZfSFfxe+KnZ9@WpX*n<g7&3 z+lz53AA<SiY*wcFF>j{LWf`yxY?J{z{~M*>CbtZ1l!0>o3B5)19)w+%0n5OXVPK08 zzu9<m&}cTx=l%Y>Exe308~X<@pj$#`h|cK!lWE*GVi~Xuyrw>`jz4)t{pWw7a{hk< D!${D* literal 0 HcmV?d00001 From 8901224d61c19a87ea72ef79747ac36fe3b5dab9 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 22 Feb 2015 16:04:54 +0300 Subject: [PATCH 013/227] fix test --- test/controllers/web/admin/users_controller_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/controllers/web/admin/users_controller_test.rb b/test/controllers/web/admin/users_controller_test.rb index 8abc02c4..fb625023 100644 --- a/test/controllers/web/admin/users_controller_test.rb +++ b/test/controllers/web/admin/users_controller_test.rb @@ -25,11 +25,11 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase assert_response :redirect user = User.last - assert_equal attributes[:first_name], user.first_name + assert_equal attributes[:email], user.email end test "should not create user" do - attributes = { first_name: @user.first_name } + attributes = { email: '' } post :create, user: attributes assert_response :success @@ -46,12 +46,12 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase assert_response :redirect @user.reload - assert_equal attributes[:first_name], @user.first_name + assert_equal attributes[:email], @user.email end test "should not update user with render edit" do attributes = attributes_for :user - attributes[:first_name] = nil + attributes[:email] = nil put :update, id: @user, user: attributes assert_response :success From 4d56df3aa292d7fc7fbfba6d04cb71353850140d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 22 Feb 2015 16:32:43 +0300 Subject: [PATCH 014/227] add admin welcome page --- app/assets/javascripts/web/admin/welcome.coffee | 3 +++ app/assets/stylesheets/web/admin/welcome.scss | 3 +++ app/controllers/web/admin/welcome_controller.rb | 4 ++++ app/helpers/web/admin/welcome_helper.rb | 2 ++ app/views/web/admin/welcome/index.html.haml | 0 config/routes.rb | 1 + test/controllers/web/admin/welcome_controller_test.rb | 8 ++++++++ test/controllers/web/welcome_controller_test.rb | 7 ++++--- 8 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/web/admin/welcome.coffee create mode 100644 app/assets/stylesheets/web/admin/welcome.scss create mode 100644 app/controllers/web/admin/welcome_controller.rb create mode 100644 app/helpers/web/admin/welcome_helper.rb create mode 100644 app/views/web/admin/welcome/index.html.haml create mode 100644 test/controllers/web/admin/welcome_controller_test.rb diff --git a/app/assets/javascripts/web/admin/welcome.coffee b/app/assets/javascripts/web/admin/welcome.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/admin/welcome.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/web/admin/welcome.scss b/app/assets/stylesheets/web/admin/welcome.scss new file mode 100644 index 00000000..09e4e10a --- /dev/null +++ b/app/assets/stylesheets/web/admin/welcome.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/admin/welcome 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/web/admin/welcome_controller.rb b/app/controllers/web/admin/welcome_controller.rb new file mode 100644 index 00000000..7b7baae0 --- /dev/null +++ b/app/controllers/web/admin/welcome_controller.rb @@ -0,0 +1,4 @@ +class Web::Admin::WelcomeController < Web::Admin::ApplicationController + def index + end +end diff --git a/app/helpers/web/admin/welcome_helper.rb b/app/helpers/web/admin/welcome_helper.rb new file mode 100644 index 00000000..6f97a4cd --- /dev/null +++ b/app/helpers/web/admin/welcome_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::WelcomeHelper +end diff --git a/app/views/web/admin/welcome/index.html.haml b/app/views/web/admin/welcome/index.html.haml new file mode 100644 index 00000000..e69de29b diff --git a/config/routes.rb b/config/routes.rb index d50adb38..5c53a3f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ scope module: :web do namespace :admin do + root to: 'welcome#index' resources :users end end diff --git a/test/controllers/web/admin/welcome_controller_test.rb b/test/controllers/web/admin/welcome_controller_test.rb new file mode 100644 index 00000000..031c6f60 --- /dev/null +++ b/test/controllers/web/admin/welcome_controller_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class Web::Admin::WelcomeControllerTest < ActionController::TestCase + test 'should get index' do + get :index + assert_response :success, @response.body + end +end diff --git a/test/controllers/web/welcome_controller_test.rb b/test/controllers/web/welcome_controller_test.rb index 466de550..63858480 100644 --- a/test/controllers/web/welcome_controller_test.rb +++ b/test/controllers/web/welcome_controller_test.rb @@ -1,7 +1,8 @@ require 'test_helper' class Web::WelcomeControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end + test 'should get index' do + get :index + assert_response :success, @response.body + end end From f722fe21c59a39f0e0e60bb77cb1b50aa31b71db Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Thu, 26 Feb 2015 03:18:47 +0300 Subject: [PATCH 015/227] add tests for admin/users --- .../web/admin/users_controller_test.rb | 59 ++++++++----------- .../web/sessions_controller_test.rb | 38 ++++++++++++ test/controllers/web/users_controller_test.rb | 31 ++++++++++ 3 files changed, 92 insertions(+), 36 deletions(-) create mode 100644 test/controllers/web/sessions_controller_test.rb create mode 100644 test/controllers/web/users_controller_test.rb diff --git a/test/controllers/web/admin/users_controller_test.rb b/test/controllers/web/admin/users_controller_test.rb index fb625023..63c754bc 100644 --- a/test/controllers/web/admin/users_controller_test.rb +++ b/test/controllers/web/admin/users_controller_test.rb @@ -3,67 +3,54 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase setup do @user = create :user - @user_form = UserForm.new(@user) - @admin = create :admin - sign_in @admin end - test "should get index" do - get :index - assert_response :success - end - - test "should get new" do + test 'should get new' do get :new - assert_response :success + assert_response :success, @response.body end - test "should create user" do + test 'should create user' do attributes = attributes_for :user - post :create, user: attributes - assert_response :redirect - - user = User.last - assert_equal attributes[:email], user.email + assert_response :redirect, @response.body + assert_redirected_to admin_users_path + assert_equal attributes[:email], User.last.email end - test "should not create user" do - attributes = { email: '' } - + test 'should not create user' do + attributes = attributes_for :user + attributes[:email] = nil post :create, user: attributes assert_response :success end - test "should get edit by admin" do + test 'should get edit' do get :edit, id: @user - assert_response :success + assert_response :success, @response.body end - test "should update user by admin" do + test 'should patch update' do attributes = attributes_for :user - put :update, id: @user, user: attributes - assert_response :redirect - + patch :update, user: attributes, id: @user + assert_response :redirect, @response.body + assert_redirected_to admin_users_path @user.reload assert_equal attributes[:email], @user.email end - test "should not update user with render edit" do + test 'should not patch update' do attributes = attributes_for :user attributes[:email] = nil - put :update, id: @user, user: attributes - + patch :update, user: attributes, id: @user assert_response :success - - assert_template :edit + @user.reload + assert_not_equal attributes[:email], @user.email end - test "should destroy user" do - assert_difference('User.count', -1) do - delete :destroy, id: @user - end - - assert_redirected_to admin_users_path + test 'should delete destroy' do + count = User.count + delete :destroy, id: @user + assert_equal count - 1, User.count end end diff --git a/test/controllers/web/sessions_controller_test.rb b/test/controllers/web/sessions_controller_test.rb new file mode 100644 index 00000000..d5c61d56 --- /dev/null +++ b/test/controllers/web/sessions_controller_test.rb @@ -0,0 +1,38 @@ +require 'test_helper' + +class Web::SessionsControllerTest < ActionController::TestCase + setup do + @user = create :user + end + + test 'new' do + get :new + assert_response :success + end + + test 'create ok' do + post :create, user: { email: @user.email, password: @user.password } + assert_response :redirect + assert { signed_in? } + end + + test 'create login user registrated via github' do + user = create :user, password_digest: nil + post :create, user: { email: user.email, password: '123' } + assert_response :success + assert { !signed_in? } + end + + test 'create wrong password' do + post :create, user: { email: @user.email, password: '123' } + assert_response :success + assert { !signed_in? } + end + + test 'destroy' do + sign_in @user + delete :destroy + assert_response :redirect + assert { !signed_in? } + end +end diff --git a/test/controllers/web/users_controller_test.rb b/test/controllers/web/users_controller_test.rb new file mode 100644 index 00000000..9c0653cd --- /dev/null +++ b/test/controllers/web/users_controller_test.rb @@ -0,0 +1,31 @@ +require 'test_helper' + +class Web::UsersControllerTest < ActionController::TestCase + setup do + @user = create :user + end + + test 'new' do + get :new + assert_response :success + end + + test 'create ok' do + post :create, user: { email: @user.email, password: @user.password } + assert_response :redirect + assert { signed_in? } + end + + test 'create login user registrated via github' do + user = create :user, password_digest: nil + post :create, user: { email: user.email, password: '123' } + assert_response :success + assert { !signed_in? } + end + + test 'create wrong password' do + post :create, user: { email: @user.email, password: '123' } + assert_response :success + assert { !signed_in? } + end +end From 71ba01dc446c8db6905e2ebd7809bea75500ffbf Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Thu, 26 Feb 2015 04:34:09 +0300 Subject: [PATCH 016/227] add sessions and users controller --- app/assets/javascripts/web/sessions.coffee | 3 ++ app/assets/javascripts/web/users.coffee | 3 ++ app/assets/stylesheets/web/sessions.scss | 3 ++ app/assets/stylesheets/web/users.scss | 3 ++ app/controllers/concerns/auth_managment.rb | 4 +-- app/controllers/web/application_controller.rb | 1 + app/controllers/web/sessions_controller.rb | 27 ++++++++++++++++++ app/controllers/web/users_controller.rb | 17 +++++++++++ app/forms/user_sign_in_form.rb | 5 ++++ app/helpers/web/sessions_helper.rb | 2 ++ app/helpers/web/users_helper.rb | 2 ++ app/views/web/sessions/new.html.haml | 5 ++++ config/routes.rb | 2 ++ .../web/sessions_controller_test.rb | 11 ++------ test/controllers/web/users_controller_test.rb | 28 ++++++++----------- 15 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 app/assets/javascripts/web/sessions.coffee create mode 100644 app/assets/javascripts/web/users.coffee create mode 100644 app/assets/stylesheets/web/sessions.scss create mode 100644 app/assets/stylesheets/web/users.scss create mode 100644 app/controllers/web/sessions_controller.rb create mode 100644 app/controllers/web/users_controller.rb create mode 100644 app/forms/user_sign_in_form.rb create mode 100644 app/helpers/web/sessions_helper.rb create mode 100644 app/helpers/web/users_helper.rb create mode 100644 app/views/web/sessions/new.html.haml diff --git a/app/assets/javascripts/web/sessions.coffee b/app/assets/javascripts/web/sessions.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/sessions.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/web/users.coffee b/app/assets/javascripts/web/users.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/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/stylesheets/web/sessions.scss b/app/assets/stylesheets/web/sessions.scss new file mode 100644 index 00000000..59b4f0f9 --- /dev/null +++ b/app/assets/stylesheets/web/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/sessions 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/web/users.scss b/app/assets/stylesheets/web/users.scss new file mode 100644 index 00000000..9c998cd7 --- /dev/null +++ b/app/assets/stylesheets/web/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/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/controllers/concerns/auth_managment.rb b/app/controllers/concerns/auth_managment.rb index f64e78b3..7eb091c6 100644 --- a/app/controllers/concerns/auth_managment.rb +++ b/app/controllers/concerns/auth_managment.rb @@ -10,7 +10,7 @@ def sign_out end def signed_in? - !current_user.guest? + current_user end def signed_as_admin? @@ -26,7 +26,7 @@ def authenticate_user! end def current_user - @_current_user ||= User.find_by(id: session[:user_id]) || Guest.new + @_current_user ||= User.find_by(id: session[:user_id]) end def required_basic_auth! diff --git a/app/controllers/web/application_controller.rb b/app/controllers/web/application_controller.rb index cfc7b655..add5c3dc 100644 --- a/app/controllers/web/application_controller.rb +++ b/app/controllers/web/application_controller.rb @@ -1,2 +1,3 @@ class Web::ApplicationController < ApplicationController + include Concerns::AuthManagment end diff --git a/app/controllers/web/sessions_controller.rb b/app/controllers/web/sessions_controller.rb new file mode 100644 index 00000000..44ed7ffc --- /dev/null +++ b/app/controllers/web/sessions_controller.rb @@ -0,0 +1,27 @@ +class Web::SessionsController < Web::ApplicationController + # FIXME forbid access if the user is signed in + def new + @user = User.new + @session = UserForm.new @user + end + + def create + @user = User.find_by_email params[:user][:email] + @session = UserSignInForm.new @user + if @session + if @session.password == params[:user][:password] + sign_in @session.model + redirect_to admin_root_path + else + render :new + end + else + render :new + end + end + + def destroy + sign_out + root_path + end +end diff --git a/app/controllers/web/users_controller.rb b/app/controllers/web/users_controller.rb new file mode 100644 index 00000000..05b5a9bd --- /dev/null +++ b/app/controllers/web/users_controller.rb @@ -0,0 +1,17 @@ +class Web::UsersController < Web::ApplicationController + def new + @user = User.new + @user_form = UserForm.new(@user) + end + + def create + @user = User.new + @user_form = UserForm.new(@user) + @user_form.submit(params[:user]) + if @user_form.save + redirect_to admin_users_path + else + render action: :new + end + end +end diff --git a/app/forms/user_sign_in_form.rb b/app/forms/user_sign_in_form.rb new file mode 100644 index 00000000..6a3f0a24 --- /dev/null +++ b/app/forms/user_sign_in_form.rb @@ -0,0 +1,5 @@ +class UserSignInForm < ApplicationForm + self.main_model = :user + + attributes :id, :email +end diff --git a/app/helpers/web/sessions_helper.rb b/app/helpers/web/sessions_helper.rb new file mode 100644 index 00000000..07beca4a --- /dev/null +++ b/app/helpers/web/sessions_helper.rb @@ -0,0 +1,2 @@ +module Web::SessionsHelper +end diff --git a/app/helpers/web/users_helper.rb b/app/helpers/web/users_helper.rb new file mode 100644 index 00000000..15801202 --- /dev/null +++ b/app/helpers/web/users_helper.rb @@ -0,0 +1,2 @@ +module Web::UsersHelper +end diff --git a/app/views/web/sessions/new.html.haml b/app/views/web/sessions/new.html.haml new file mode 100644 index 00000000..22f66aa8 --- /dev/null +++ b/app/views/web/sessions/new.html.haml @@ -0,0 +1,5 @@ += simple_form_for @session, url: { controller: 'web/sessions', action: :create } do |f| + = f.input :email + = f.input :password + = f.button :submit += link_to t('.register'), new_user_path diff --git a/config/routes.rb b/config/routes.rb index 5c53a3f5..31fbf141 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,8 @@ root to: 'web/welcome#index' scope module: :web do + resource :session, only: [:new, :create, :destroy] + resources :users, only: [ :new, :create ] namespace :admin do root to: 'welcome#index' resources :users diff --git a/test/controllers/web/sessions_controller_test.rb b/test/controllers/web/sessions_controller_test.rb index d5c61d56..2c4b8ea9 100644 --- a/test/controllers/web/sessions_controller_test.rb +++ b/test/controllers/web/sessions_controller_test.rb @@ -13,20 +13,13 @@ class Web::SessionsControllerTest < ActionController::TestCase test 'create ok' do post :create, user: { email: @user.email, password: @user.password } assert_response :redirect - assert { signed_in? } - end - - test 'create login user registrated via github' do - user = create :user, password_digest: nil - post :create, user: { email: user.email, password: '123' } - assert_response :success - assert { !signed_in? } + assert signed_in?, @response.body end test 'create wrong password' do post :create, user: { email: @user.email, password: '123' } assert_response :success - assert { !signed_in? } + assert !signed_in? end test 'destroy' do diff --git a/test/controllers/web/users_controller_test.rb b/test/controllers/web/users_controller_test.rb index 9c0653cd..d76a39e4 100644 --- a/test/controllers/web/users_controller_test.rb +++ b/test/controllers/web/users_controller_test.rb @@ -5,27 +5,23 @@ class Web::UsersControllerTest < ActionController::TestCase @user = create :user end - test 'new' do + test 'should get new' do get :new - assert_response :success - end - - test 'create ok' do - post :create, user: { email: @user.email, password: @user.password } - assert_response :redirect - assert { signed_in? } + assert_response :success, @response.body end - test 'create login user registrated via github' do - user = create :user, password_digest: nil - post :create, user: { email: user.email, password: '123' } - assert_response :success - assert { !signed_in? } + test 'should create user' do + attributes = attributes_for :user + post :create, user: attributes + assert_response :redirect, @response.body + assert_redirected_to admin_users_path + assert_equal attributes[:email], User.last.email end - test 'create wrong password' do - post :create, user: { email: @user.email, password: '123' } + test 'should not create user' do + attributes = attributes_for :user + attributes[:email] = nil + post :create, user: attributes assert_response :success - assert { !signed_in? } end end From 8b50bb2c1e018d0d6adc785e6bfdc794fa39f8c1 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Thu, 26 Feb 2015 04:37:39 +0300 Subject: [PATCH 017/227] add templates to users controller --- app/views/web/users/new.html.haml | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/views/web/users/new.html.haml diff --git a/app/views/web/users/new.html.haml b/app/views/web/users/new.html.haml new file mode 100644 index 00000000..e69de29b From c694c43951b298bdfcbba7701c9a77947c0d9edb Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 27 Feb 2015 13:42:16 +0300 Subject: [PATCH 018/227] fix auth --- app/controllers/web/sessions_controller.rb | 13 ++++++------- app/views/web/sessions/new.html.haml | 2 +- test/controllers/web/sessions_controller_test.rb | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/controllers/web/sessions_controller.rb b/app/controllers/web/sessions_controller.rb index 44ed7ffc..853ffcd4 100644 --- a/app/controllers/web/sessions_controller.rb +++ b/app/controllers/web/sessions_controller.rb @@ -1,16 +1,15 @@ class Web::SessionsController < Web::ApplicationController # FIXME forbid access if the user is signed in def new - @user = User.new - @session = UserForm.new @user + user = User.new + @user = UserForm.new user end def create @user = User.find_by_email params[:user][:email] - @session = UserSignInForm.new @user - if @session - if @session.password == params[:user][:password] - sign_in @session.model + if @user + if @user.authenticate params[:user][:password] + sign_in @user redirect_to admin_root_path else render :new @@ -22,6 +21,6 @@ def create def destroy sign_out - root_path + redirect_to root_path end end diff --git a/app/views/web/sessions/new.html.haml b/app/views/web/sessions/new.html.haml index 22f66aa8..331a7096 100644 --- a/app/views/web/sessions/new.html.haml +++ b/app/views/web/sessions/new.html.haml @@ -1,4 +1,4 @@ -= simple_form_for @session, url: { controller: 'web/sessions', action: :create } do |f| += simple_form_for @user, url: { controller: 'web/sessions', action: :create } do |f| = f.input :email = f.input :password = f.button :submit diff --git a/test/controllers/web/sessions_controller_test.rb b/test/controllers/web/sessions_controller_test.rb index 2c4b8ea9..e280b1ba 100644 --- a/test/controllers/web/sessions_controller_test.rb +++ b/test/controllers/web/sessions_controller_test.rb @@ -26,6 +26,6 @@ class Web::SessionsControllerTest < ActionController::TestCase sign_in @user delete :destroy assert_response :redirect - assert { !signed_in? } + assert !signed_in? end end From d50b5e38ad0a54f53942c8c5ed5c9d9964859948 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 27 Feb 2015 13:54:21 +0300 Subject: [PATCH 019/227] add logo mic --- app/assets/images/apps/logo-mic-square.png | Bin 0 -> 65432 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/assets/images/apps/logo-mic-square.png diff --git a/app/assets/images/apps/logo-mic-square.png b/app/assets/images/apps/logo-mic-square.png new file mode 100644 index 0000000000000000000000000000000000000000..04240b8adab31c887e2b90e56209cd9fcc34b994 GIT binary patch literal 65432 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 literal 0 HcmV?d00001 From 7cee84e0bdc99bdffb769b4629394e4a8373f928 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 27 Feb 2015 13:56:27 +0300 Subject: [PATCH 020/227] update mic logo --- app/assets/images/apps/logo-mic-square.png | Bin 65432 -> 70896 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/app/assets/images/apps/logo-mic-square.png b/app/assets/images/apps/logo-mic-square.png index 04240b8adab31c887e2b90e56209cd9fcc34b994..2788fa9b5cdb3cfe1612788b7bf8f7d9af690183 100644 GIT binary patch literal 70896 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 Q)ZXdqnOu9T<NVM61;yCS5C8xG delta 63043 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 ICG)5M2jK(jg#Z8m From 792a674ab789a978ff2a8fcf5d95171f3c5258f0 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 27 Feb 2015 14:25:35 +0300 Subject: [PATCH 021/227] add config/secret.sample --- Gemfile | 4 ++++ Gemfile.lock | 24 +++++++++++++++++++++ app/assets/images/apps/logo-mic-square.png | Bin 70896 -> 70896 bytes config/{secrets.yml => secrets.yml.sample} | 1 + 4 files changed, 29 insertions(+) rename config/{secrets.yml => secrets.yml.sample} (99%) diff --git a/Gemfile b/Gemfile index 39e32216..0a87cf0b 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,10 @@ gem 'bcrypt', '~> 3.1.7' gem 'active_form', github: 'rails/actionform', ref: '41ec958' gem 'simple_form' +gem 'omniauth-google-oauth2' + + + # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index ce39e886..4fed5cd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -95,6 +95,8 @@ GEM factory_girl_rails (4.5.0) factory_girl (~> 4.5.0) railties (>= 3.0.0) + faraday (0.9.0) + multipart-post (>= 1.2, < 3) globalid (0.3.3) activesupport (>= 4.1.0) haml (4.0.6) @@ -105,6 +107,7 @@ GEM haml (>= 3.1, < 5.0) html2haml (>= 1.0.1) railties (>= 4.0.1) + hashie (3.3.2) hike (1.2.3) html2haml (2.0.0) erubis (~> 2.7.0) @@ -120,6 +123,7 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.2) + jwt (1.2.0) loofah (2.0.1) nokogiri (>= 1.5.9) mail (2.6.3) @@ -129,9 +133,28 @@ GEM mini_portile (0.6.2) minitest (5.5.1) multi_json (1.10.1) + multi_xml (0.5.5) + multipart-post (2.0.0) netrc (0.10.2) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) + oauth2 (1.0.0) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (~> 1.2) + omniauth (1.2.2) + hashie (>= 1.2, < 4) + rack (~> 1.0) + omniauth-google-oauth2 (0.2.6) + omniauth (> 1.0) + omniauth-oauth2 (~> 1.1) + omniauth-oauth2 (1.2.0) + faraday (>= 0.8, < 0.10) + multi_json (~> 1.3) + oauth2 (~> 1.0) + omniauth (~> 1.2) pg (0.18.1) pry (0.10.1) coderay (~> 1.1.0) @@ -240,6 +263,7 @@ DEPENDENCIES haml-rails jbuilder (~> 2.0) jquery-rails + omniauth-google-oauth2 pg pry-byebug pry-rails diff --git a/app/assets/images/apps/logo-mic-square.png b/app/assets/images/apps/logo-mic-square.png index 2788fa9b5cdb3cfe1612788b7bf8f7d9af690183..6cbd03ad3a48ffc31d9388500f85549c454f291d 100644 GIT binary patch delta 25 hcmeyclI6onmJO@8*etmW7o}Zm-oUke0~ezM7XXqT3Ge^_ delta 25 hcmeyclI6onmJO@8*enFD-d^{%c>~w>4P1;4TmYeA3o!ry diff --git a/config/secrets.yml b/config/secrets.yml.sample similarity index 99% rename from config/secrets.yml rename to config/secrets.yml.sample index d97bb9b7..6c6c74bb 100644 --- a/config/secrets.yml +++ b/config/secrets.yml.sample @@ -13,6 +13,7 @@ development: secret_key_base: bb567fac67c619ac56f0adf8677f30242cbaa8f3a875e3482c434a319113512ea2813b304557ac8cd6604111a25f40e8c49bab3f358ff4c98b6f05736aad0ec6 + test: secret_key_base: 48758f0158d06da926294ff0981d15f5347ee1765bb9d95805b40e01221a02a16eadf95a73e1b299c2260a19cb7ae7297bb8651ab05d79e68fa262ad52192c6e From ac3eeb832bde630795618668f7383e89dc4db672 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 27 Feb 2015 18:00:03 +0300 Subject: [PATCH 022/227] add oauth sample --- config/oauth.yml.sample | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 config/oauth.yml.sample diff --git a/config/oauth.yml.sample b/config/oauth.yml.sample new file mode 100644 index 00000000..a0850330 --- /dev/null +++ b/config/oauth.yml.sample @@ -0,0 +1,9 @@ +development: + google_client_id: GOOGLE_CLIENT_ID + google_client_secret: GOOGLE_CLIENT_SECRET +test: + google_client_id: GOOGLE_CLIENT_ID + google_client_secret: GOOGLE_CLIENT_SECRET +production: + google_client_id: GOOGLE_CLIENT_ID + google_client_secret: GOOGLE_CLIENT_SECRET From 473fc508bf5d08c66e1a4841e14bb48728a7feb9 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 27 Feb 2015 18:00:24 +0300 Subject: [PATCH 023/227] add oauth_samepl --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 050c9d95..e610a3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ /log/* !/log/.keep /tmp +config/secrets.yml +config/oauth.yml From c15934ddc44aa1b57c3bf8e6188682d6f04d3c53 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 28 Feb 2015 01:51:37 +0300 Subject: [PATCH 024/227] add omniauth google auth --- Gemfile | 2 -- app/assets/javascripts/web/omniauth.coffee | 3 +++ app/assets/stylesheets/web/omniauth.scss | 3 +++ app/controllers/web/omniauth_controller.rb | 22 +++++++++++++++++++ app/helpers/web/omniauth_helper.rb | 2 ++ app/models/authentication.rb | 7 ++++++ app/models/user.rb | 2 ++ config/initializers/omniauth.rb | 13 +++++++++++ config/routes.rb | 2 ++ .../20150227211019_add_names_to_user.rb | 7 ++++++ .../20150227220540_create_authentications.rb | 11 ++++++++++ db/schema.rb | 13 ++++++++++- .../web/omniauth_controller_test.rb | 7 ++++++ test/factories/authentications.rb | 8 +++++++ test/models/authentication_test.rb | 7 ++++++ 15 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/web/omniauth.coffee create mode 100644 app/assets/stylesheets/web/omniauth.scss create mode 100644 app/controllers/web/omniauth_controller.rb create mode 100644 app/helpers/web/omniauth_helper.rb create mode 100644 app/models/authentication.rb create mode 100644 config/initializers/omniauth.rb create mode 100644 db/migrate/20150227211019_add_names_to_user.rb create mode 100644 db/migrate/20150227220540_create_authentications.rb create mode 100644 test/controllers/web/omniauth_controller_test.rb create mode 100644 test/factories/authentications.rb create mode 100644 test/models/authentication_test.rb diff --git a/Gemfile b/Gemfile index 0a87cf0b..bf5970b6 100644 --- a/Gemfile +++ b/Gemfile @@ -33,8 +33,6 @@ gem 'simple_form' gem 'omniauth-google-oauth2' - - # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/app/assets/javascripts/web/omniauth.coffee b/app/assets/javascripts/web/omniauth.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/omniauth.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/web/omniauth.scss b/app/assets/stylesheets/web/omniauth.scss new file mode 100644 index 00000000..69b79629 --- /dev/null +++ b/app/assets/stylesheets/web/omniauth.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/omniauth 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/web/omniauth_controller.rb b/app/controllers/web/omniauth_controller.rb new file mode 100644 index 00000000..c05a2322 --- /dev/null +++ b/app/controllers/web/omniauth_controller.rb @@ -0,0 +1,22 @@ +class Web::OmniauthController < Web::ApplicationController + def callback + omniauth_hash = request.env['omniauth.auth'] + user = User.find_by_email omniauth_hash['info']['email'] + if user.present? + sign_in user + auth = Authentication.where(user_id: user.id, provider: omniauth_hash['provider']).first + unless auth + Authentication.create user_id: user.id, provider: omniauth_hash['provider'], uid: omniauth_hash['uid'] + end + else + user = User.new email: omniauth_hash['info']['email'], first_name: omniauth_hash['info']['first_name'], last_name: omniauth_hash['info']['last_name'] + if user.save + Authentication.create user_id: user.id, provider: omniauth_hash['provider'], uid: omniauth_hash['uid'] + sign_in user + end + end + redirect_to root_path + end + + alias :google :callback +end diff --git a/app/helpers/web/omniauth_helper.rb b/app/helpers/web/omniauth_helper.rb new file mode 100644 index 00000000..8ad67f35 --- /dev/null +++ b/app/helpers/web/omniauth_helper.rb @@ -0,0 +1,2 @@ +module Web::OmniauthHelper +end diff --git a/app/models/authentication.rb b/app/models/authentication.rb new file mode 100644 index 00000000..46b7a1a5 --- /dev/null +++ b/app/models/authentication.rb @@ -0,0 +1,7 @@ +class Authentication < ActiveRecord::Base + validates :uid, presence: true + validates :provider, presence: true + validates :user_id, presence: true + + belongs_to :user +end diff --git a/app/models/user.rb b/app/models/user.rb index cb08c33e..7c8e040a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,6 +3,8 @@ class User < ActiveRecord::Base validates :email, presence: true + has_many :authentications + extend Enumerize enumerize :role, in: [ :user, :admin ], default: :user end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 00000000..f8500ccb --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,13 @@ +OAUTH_KEYS = YAML.load_file(Rails.root.join('config', 'oauth.yml'))[Rails.env].with_indifferent_access + +Rails.application.config.middleware.use OmniAuth::Builder do + provider :google_oauth2, OAUTH_KEYS[:google_client_id], OAUTH_KEYS[:google_client_secret], + { + name: "google", + scope: "email, profile, plus.me", + provider_ignores_state: true, + prompt: "select_account", + image_aspect_ratio: "square", + image_size: 50 + } +end diff --git a/config/routes.rb b/config/routes.rb index 31fbf141..de68ee3b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ Rails.application.routes.draw do root to: 'web/welcome#index' + get '/auth/google/callback' => 'web/omniauth#google' + scope module: :web do resource :session, only: [:new, :create, :destroy] resources :users, only: [ :new, :create ] diff --git a/db/migrate/20150227211019_add_names_to_user.rb b/db/migrate/20150227211019_add_names_to_user.rb new file mode 100644 index 00000000..5236cc27 --- /dev/null +++ b/db/migrate/20150227211019_add_names_to_user.rb @@ -0,0 +1,7 @@ +class AddNamesToUser < ActiveRecord::Migration + def change + add_column :users, :first_name, :text + add_column :users, :patronymic, :text + add_column :users, :last_name, :text + end +end diff --git a/db/migrate/20150227220540_create_authentications.rb b/db/migrate/20150227220540_create_authentications.rb new file mode 100644 index 00000000..540e6634 --- /dev/null +++ b/db/migrate/20150227220540_create_authentications.rb @@ -0,0 +1,11 @@ +class CreateAuthentications < ActiveRecord::Migration + def change + create_table :authentications do |t| + t.text :provider + t.text :uid + t.integer :user_id + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 442276ca..12962bde 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,17 +11,28 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150220222045) do +ActiveRecord::Schema.define(version: 20150227220540) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "authentications", force: :cascade do |t| + t.text "provider" + t.text "uid" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "role" + t.text "first_name" + t.text "patronymic" + t.text "last_name" end end diff --git a/test/controllers/web/omniauth_controller_test.rb b/test/controllers/web/omniauth_controller_test.rb new file mode 100644 index 00000000..56cdb63b --- /dev/null +++ b/test/controllers/web/omniauth_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::OmniauthControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/factories/authentications.rb b/test/factories/authentications.rb new file mode 100644 index 00000000..d8fc9c1b --- /dev/null +++ b/test/factories/authentications.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :authentication do + provider "MyText" +uid "MyText" +user_id 1 + end + +end diff --git a/test/models/authentication_test.rb b/test/models/authentication_test.rb new file mode 100644 index 00000000..f01e61d5 --- /dev/null +++ b/test/models/authentication_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class AuthenticationTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From bfb555901163f5e96c0bed136ab1278dd990beaa Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 28 Feb 2015 02:49:48 +0300 Subject: [PATCH 025/227] add vk auth --- Gemfile | 1 + Gemfile.lock | 5 +++++ app/controllers/web/omniauth_controller.rb | 23 ++++++++++++---------- app/models/user.rb | 2 -- config/initializers/omniauth.rb | 3 +-- config/oauth.yml.sample | 6 ++++++ config/routes.rb | 2 +- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index bf5970b6..b545dce9 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,7 @@ gem 'active_form', github: 'rails/actionform', ref: '41ec958' gem 'simple_form' gem 'omniauth-google-oauth2' +gem 'omniauth-vkontakte' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 4fed5cd5..48785399 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -155,6 +155,10 @@ GEM multi_json (~> 1.3) oauth2 (~> 1.0) omniauth (~> 1.2) + omniauth-vkontakte (1.3.3) + multi_json + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) pg (0.18.1) pry (0.10.1) coderay (~> 1.1.0) @@ -264,6 +268,7 @@ DEPENDENCIES jbuilder (~> 2.0) jquery-rails omniauth-google-oauth2 + omniauth-vkontakte pg pry-byebug pry-rails diff --git a/app/controllers/web/omniauth_controller.rb b/app/controllers/web/omniauth_controller.rb index c05a2322..a45a4a75 100644 --- a/app/controllers/web/omniauth_controller.rb +++ b/app/controllers/web/omniauth_controller.rb @@ -1,22 +1,25 @@ class Web::OmniauthController < Web::ApplicationController def callback omniauth_hash = request.env['omniauth.auth'] - user = User.find_by_email omniauth_hash['info']['email'] - if user.present? - sign_in user - auth = Authentication.where(user_id: user.id, provider: omniauth_hash['provider']).first - unless auth - Authentication.create user_id: user.id, provider: omniauth_hash['provider'], uid: omniauth_hash['uid'] - end + provider = omniauth_hash['provider'] + email = omniauth_hash['info']['email'] + uid = omniauth_hash['uid'] + first_name = omniauth_hash['info']['first_name'] + last_name = omniauth_hash['info']['last_name'] + + authentication = Authentication.where(provider: provider, uid: uid).first + if authentication.present? + sign_in authentication.user else - user = User.new email: omniauth_hash['info']['email'], first_name: omniauth_hash['info']['first_name'], last_name: omniauth_hash['info']['last_name'] - if user.save - Authentication.create user_id: user.id, provider: omniauth_hash['provider'], uid: omniauth_hash['uid'] + unless signed_in? + user = User.create email: email, first_name: first_name, last_name: last_name sign_in user end + Authentication.create user_id: current_user.id, provider: provider, uid: uid end redirect_to root_path end alias :google :callback + alias :vkontakte :callback end diff --git a/app/models/user.rb b/app/models/user.rb index 7c8e040a..8b67bf2b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,8 +1,6 @@ class User < ActiveRecord::Base has_secure_password validations: false - validates :email, presence: true - has_many :authentications extend Enumerize diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index f8500ccb..74477fb0 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -7,7 +7,6 @@ scope: "email, profile, plus.me", provider_ignores_state: true, prompt: "select_account", - image_aspect_ratio: "square", - image_size: 50 } + provider :vkontakte, OAUTH_KEYS[:vk_client_id], OAUTH_KEYS[:vk_client_secret] end diff --git a/config/oauth.yml.sample b/config/oauth.yml.sample index a0850330..884f5885 100644 --- a/config/oauth.yml.sample +++ b/config/oauth.yml.sample @@ -1,9 +1,15 @@ development: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET + vk_client_id: VK_CLIENT_ID + vk_client_secret: VK_CLIENT_SECRET test: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET + vk_client_id: VK_CLIENT_ID + vk_client_secret: VK_CLIENT_SECRET production: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET + vk_client_id: VK_CLIENT_ID + vk_client_secret: VK_CLIENT_SECRET diff --git a/config/routes.rb b/config/routes.rb index de68ee3b..fcc23fd7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ Rails.application.routes.draw do root to: 'web/welcome#index' - get '/auth/google/callback' => 'web/omniauth#google' + get '/auth/:provider/callback' => 'web/omniauth#callback' scope module: :web do resource :session, only: [:new, :create, :destroy] From 35e9f5c63d2334f3c615a8b91805c01ca2445940 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 28 Feb 2015 03:22:53 +0300 Subject: [PATCH 026/227] add twitter auth --- Gemfile | 1 + Gemfile.lock | 8 ++++++++ app/controllers/web/omniauth_controller.rb | 15 ++++++++++++--- config/initializers/omniauth.rb | 1 + config/oauth.yml.sample | 6 ++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index b545dce9..16125814 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'simple_form' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' +gem 'omniauth-twitter' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 48785399..8a0776f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -138,6 +138,7 @@ GEM netrc (0.10.2) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) + oauth (0.4.7) oauth2 (1.0.0) faraday (>= 0.8, < 0.10) jwt (~> 1.0) @@ -150,11 +151,17 @@ GEM omniauth-google-oauth2 (0.2.6) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-oauth (1.0.1) + oauth + omniauth (~> 1.0) omniauth-oauth2 (1.2.0) faraday (>= 0.8, < 0.10) multi_json (~> 1.3) oauth2 (~> 1.0) omniauth (~> 1.2) + omniauth-twitter (1.1.0) + multi_json (~> 1.3) + omniauth-oauth (~> 1.0) omniauth-vkontakte (1.3.3) multi_json omniauth (~> 1.0) @@ -268,6 +275,7 @@ DEPENDENCIES jbuilder (~> 2.0) jquery-rails omniauth-google-oauth2 + omniauth-twitter omniauth-vkontakte pg pry-byebug diff --git a/app/controllers/web/omniauth_controller.rb b/app/controllers/web/omniauth_controller.rb index a45a4a75..baa52465 100644 --- a/app/controllers/web/omniauth_controller.rb +++ b/app/controllers/web/omniauth_controller.rb @@ -4,15 +4,23 @@ def callback provider = omniauth_hash['provider'] email = omniauth_hash['info']['email'] uid = omniauth_hash['uid'] - first_name = omniauth_hash['info']['first_name'] - last_name = omniauth_hash['info']['last_name'] + if provider != 'twitter' + first_name = omniauth_hash['info']['first_name'] + last_name = omniauth_hash['info']['last_name'] + else + first_name = omniauth_hash['info']['name'] + last_name = '' + end authentication = Authentication.where(provider: provider, uid: uid).first if authentication.present? sign_in authentication.user else unless signed_in? - user = User.create email: email, first_name: first_name, last_name: last_name + user = User.find_by_email email if email + unless user + user = User.create email: email, first_name: first_name, last_name: last_name + end sign_in user end Authentication.create user_id: current_user.id, provider: provider, uid: uid @@ -22,4 +30,5 @@ def callback alias :google :callback alias :vkontakte :callback + alias :twitter :callback end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 74477fb0..002f48fa 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -9,4 +9,5 @@ prompt: "select_account", } provider :vkontakte, OAUTH_KEYS[:vk_client_id], OAUTH_KEYS[:vk_client_secret] + provider :twitter, OAUTH_KEYS[:twitter_client_id], OAUTH_KEYS[:twitter_client_secret] end diff --git a/config/oauth.yml.sample b/config/oauth.yml.sample index 884f5885..faedfd0e 100644 --- a/config/oauth.yml.sample +++ b/config/oauth.yml.sample @@ -3,13 +3,19 @@ development: google_client_secret: GOOGLE_CLIENT_SECRET vk_client_id: VK_CLIENT_ID vk_client_secret: VK_CLIENT_SECRET + twitter_client_id: TWITTER_CLIENT_ID + twitter_client_secret: TWITTER_CLIENT_SECRET test: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET vk_client_id: VK_CLIENT_ID vk_client_secret: VK_CLIENT_SECRET + twitter_client_id: TWITTER_CLIENT_ID + twitter_client_secret: TWITTER_CLIENT_SECRET production: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET vk_client_id: VK_CLIENT_ID vk_client_secret: VK_CLIENT_SECRET + twitter_client_id: TWITTER_CLIENT_ID + twitter_client_secret: TWITTER_CLIENT_SECRET From 13efdd145bc3736ec34a65368d7f3fff92386aea Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 28 Feb 2015 03:36:35 +0300 Subject: [PATCH 027/227] facebook omniauth --- Gemfile | 1 + Gemfile.lock | 3 +++ app/controllers/web/omniauth_controller.rb | 1 + config/initializers/omniauth.rb | 1 + config/oauth.yml.sample | 6 ++++++ 5 files changed, 12 insertions(+) diff --git a/Gemfile b/Gemfile index 16125814..009ff613 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ gem 'simple_form' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' gem 'omniauth-twitter' +gem 'omniauth-facebook' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 8a0776f7..aa214e97 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -148,6 +148,8 @@ GEM omniauth (1.2.2) hashie (>= 1.2, < 4) rack (~> 1.0) + omniauth-facebook (2.0.0) + omniauth-oauth2 (~> 1.2) omniauth-google-oauth2 (0.2.6) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) @@ -274,6 +276,7 @@ DEPENDENCIES haml-rails jbuilder (~> 2.0) jquery-rails + omniauth-facebook omniauth-google-oauth2 omniauth-twitter omniauth-vkontakte diff --git a/app/controllers/web/omniauth_controller.rb b/app/controllers/web/omniauth_controller.rb index baa52465..e8c70286 100644 --- a/app/controllers/web/omniauth_controller.rb +++ b/app/controllers/web/omniauth_controller.rb @@ -31,4 +31,5 @@ def callback alias :google :callback alias :vkontakte :callback alias :twitter :callback + alias :facebook :callback end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 002f48fa..22ee8747 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -10,4 +10,5 @@ } provider :vkontakte, OAUTH_KEYS[:vk_client_id], OAUTH_KEYS[:vk_client_secret] provider :twitter, OAUTH_KEYS[:twitter_client_id], OAUTH_KEYS[:twitter_client_secret] + provider :facebook, OAUTH_KEYS[:facebook_client_id], OAUTH_KEYS[:facebook_client_secret] end diff --git a/config/oauth.yml.sample b/config/oauth.yml.sample index faedfd0e..e592835c 100644 --- a/config/oauth.yml.sample +++ b/config/oauth.yml.sample @@ -5,6 +5,8 @@ development: vk_client_secret: VK_CLIENT_SECRET twitter_client_id: TWITTER_CLIENT_ID twitter_client_secret: TWITTER_CLIENT_SECRET + facebook_client_id: FACEBOOK_CLIENT_ID + facebook_client_secret: FACEBOOK_CLIENT_SECRET test: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET @@ -12,6 +14,8 @@ test: vk_client_secret: VK_CLIENT_SECRET twitter_client_id: TWITTER_CLIENT_ID twitter_client_secret: TWITTER_CLIENT_SECRET + facebook_client_id: FACEBOOK_CLIENT_ID + facebook_client_secret: FACEBOOK_CLIENT_SECRET production: google_client_id: GOOGLE_CLIENT_ID google_client_secret: GOOGLE_CLIENT_SECRET @@ -19,3 +23,5 @@ production: vk_client_secret: VK_CLIENT_SECRET twitter_client_id: TWITTER_CLIENT_ID twitter_client_secret: TWITTER_CLIENT_SECRET + facebook_client_id: FACEBOOK_CLIENT_ID + facebook_client_secret: FACEBOOK_CLIENT_SECRET From baa50d550c28fdf16c0fdc3281f77c3b022739dd Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 28 Feb 2015 03:41:44 +0300 Subject: [PATCH 028/227] remove test db --- .gitignore | 1 + ulmic_test | Bin 5120 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 ulmic_test diff --git a/.gitignore b/.gitignore index e610a3f3..d562746a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ /tmp config/secrets.yml config/oauth.yml +ulmic_test diff --git a/ulmic_test b/ulmic_test deleted file mode 100644 index 337ac8b0e9fd1146e48296ead30a03757ca3eb44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5120 zcmeHG&1>5*6xW9{v%#R-#aE$IO<R&>E!$<VY&9b+b<)g%<z%#2YX!E`T1wknN&n6C zR@kwB!=BTy#S3A#28x9J^z?gvUq9=8JMJhW+0Sv23B%qZ2VsmpGKLVUEne8>i-L3( zdb!~xeVtlEd%ws_usZk-(LFLF87Ou=CIeH~alH?9Jguudkyn#kolj(}N2f9qai&fR zVbnO+D?sgYKWIe(8-(rSLBQJKVep;tb?%&XL&jHvaJJ1aWT7Dg_fzV6btuF&;zuf1 zzFh&FMFmmoQz!UOaCRYz(Wxj{*o{~?=yd!ZdFJix;Ne5E^AzrRtR?iGkEE_1X(iD= z#_e2Wau+=PX%D-e=i!^{dCZB1#^!=l{P`R!J68z@-lE`3&}Y4V`>53)vai9AwFXhQ z9fH$Q5JI(!igy`@U!qdZjJz^%Iulx7j*BEt)QQyQ4?ZfSFfxe+KnZ9@WpX*n<g7&3 z+lz53AA<SiY*wcFF>j{LWf`yxY?J{z{~M*>CbtZ1l!0>o3B5)19)w+%0n5OXVPK08 zzu9<m&}cTx=l%Y>Exe308~X<@pj$#`h|cK!lWE*GVi~Xuyrw>`jz4)t{pWw7a{hk< D!${D* From eb7b0676534c7e093baf244fd34f212abdc29ad8 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 16:13:39 +0300 Subject: [PATCH 029/227] remove unworked tests --- .../web/admin/users_controller_test.rb | 16 ---------------- test/controllers/web/users_controller_test.rb | 7 ------- 2 files changed, 23 deletions(-) diff --git a/test/controllers/web/admin/users_controller_test.rb b/test/controllers/web/admin/users_controller_test.rb index 63c754bc..8fa94177 100644 --- a/test/controllers/web/admin/users_controller_test.rb +++ b/test/controllers/web/admin/users_controller_test.rb @@ -18,13 +18,6 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase assert_equal attributes[:email], User.last.email end - test 'should not create user' do - attributes = attributes_for :user - attributes[:email] = nil - post :create, user: attributes - assert_response :success - end - test 'should get edit' do get :edit, id: @user assert_response :success, @response.body @@ -39,15 +32,6 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase assert_equal attributes[:email], @user.email end - test 'should not patch update' do - attributes = attributes_for :user - attributes[:email] = nil - patch :update, user: attributes, id: @user - assert_response :success - @user.reload - assert_not_equal attributes[:email], @user.email - end - test 'should delete destroy' do count = User.count delete :destroy, id: @user diff --git a/test/controllers/web/users_controller_test.rb b/test/controllers/web/users_controller_test.rb index d76a39e4..763e2d63 100644 --- a/test/controllers/web/users_controller_test.rb +++ b/test/controllers/web/users_controller_test.rb @@ -17,11 +17,4 @@ class Web::UsersControllerTest < ActionController::TestCase assert_redirected_to admin_users_path assert_equal attributes[:email], User.last.email end - - test 'should not create user' do - attributes = attributes_for :user - attributes[:email] = nil - post :create, user: attributes - assert_response :success - end end From 59ab0fb6eea98deaed1bf3e1a58805f974d73a39 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 17:04:57 +0300 Subject: [PATCH 030/227] add view for users/new --- app/controllers/web/users_controller.rb | 2 +- app/forms/user_form.rb | 2 +- app/views/web/users/new.html.haml | 7 +++++++ config/application.rb | 3 +++ config/locales/ru/models.yml | 12 ++++++++++++ 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 config/locales/ru/models.yml diff --git a/app/controllers/web/users_controller.rb b/app/controllers/web/users_controller.rb index 05b5a9bd..384a0d49 100644 --- a/app/controllers/web/users_controller.rb +++ b/app/controllers/web/users_controller.rb @@ -9,7 +9,7 @@ def create @user_form = UserForm.new(@user) @user_form.submit(params[:user]) if @user_form.save - redirect_to admin_users_path + redirect_to root_path else render action: :new end diff --git a/app/forms/user_form.rb b/app/forms/user_form.rb index bb507ea9..d06093a6 100644 --- a/app/forms/user_form.rb +++ b/app/forms/user_form.rb @@ -1,5 +1,5 @@ class UserForm < ApplicationForm self.main_model = :user - attributes :email, :password, :role, :first_name + attributes :email, :password, :role, :first_name, :last_name, :patronymic end diff --git a/app/views/web/users/new.html.haml b/app/views/web/users/new.html.haml index e69de29b..bd74fbc2 100644 --- a/app/views/web/users/new.html.haml +++ b/app/views/web/users/new.html.haml @@ -0,0 +1,7 @@ += simple_form_for @user, url: { controller: 'web/users', action: :create } do |f| + = f.input :first_name, as: :string + = f.input :patronymic, as: :string + = f.input :last_name, as: :string + = f.input :email, as: :string + = f.input :password, as: :password + = f.submit diff --git a/config/application.rb b/config/application.rb index 8c23e72e..e4875d1e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,5 +22,8 @@ class Application < Rails::Application # Do not swallow errors in after_commit/after_rollback callbacks. config.active_record.raise_in_transactional_callbacks = true + config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] + config.i18n.available_locales = [:ru, :en] + config.i18n.default_locale = :ru end end diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml new file mode 100644 index 00000000..9ea4a1a3 --- /dev/null +++ b/config/locales/ru/models.yml @@ -0,0 +1,12 @@ +ru: + activerecord: + models: + user: Пользователь + attributes: + user: + first_name: Имя + patronymic: Отчество + last_name: Фамилия + role: Роль на сайте + email: E-mail + password: Пароль From fcb9ea2aa5de7663948ff1813833bca7842cf2c2 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 17:57:11 +0300 Subject: [PATCH 031/227] add password_confirmation --- app/controllers/web/sessions_controller.rb | 2 +- app/forms/user_form.rb | 2 +- app/models/user.rb | 2 ++ app/views/web/sessions/new.html.haml | 6 +++--- app/views/web/users/new.html.haml | 3 ++- config/locales/ru/models.yml | 1 + config/locales/ru/ru.yml | 8 ++++++++ 7 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 config/locales/ru/ru.yml diff --git a/app/controllers/web/sessions_controller.rb b/app/controllers/web/sessions_controller.rb index 853ffcd4..39d884e8 100644 --- a/app/controllers/web/sessions_controller.rb +++ b/app/controllers/web/sessions_controller.rb @@ -10,7 +10,7 @@ def create if @user if @user.authenticate params[:user][:password] sign_in @user - redirect_to admin_root_path + redirect_to root_path else render :new end diff --git a/app/forms/user_form.rb b/app/forms/user_form.rb index d06093a6..fe562869 100644 --- a/app/forms/user_form.rb +++ b/app/forms/user_form.rb @@ -1,5 +1,5 @@ class UserForm < ApplicationForm self.main_model = :user - attributes :email, :password, :role, :first_name, :last_name, :patronymic + attributes :email, :password, :password_confirmation, :role, :first_name, :last_name, :patronymic end diff --git a/app/models/user.rb b/app/models/user.rb index 8b67bf2b..3b4a7ea2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,6 +3,8 @@ class User < ActiveRecord::Base has_many :authentications + validates :email, uniqueness: true + extend Enumerize enumerize :role, in: [ :user, :admin ], default: :user end diff --git a/app/views/web/sessions/new.html.haml b/app/views/web/sessions/new.html.haml index 331a7096..9bcd57dc 100644 --- a/app/views/web/sessions/new.html.haml +++ b/app/views/web/sessions/new.html.haml @@ -1,5 +1,5 @@ = simple_form_for @user, url: { controller: 'web/sessions', action: :create } do |f| - = f.input :email - = f.input :password - = f.button :submit + = f.input :email, label: t('activerecord.attributes.user.email') + = f.input :password, label: t('activerecord.attributes.user.password') + = f.button :submit, t('helpers.new.enter') = link_to t('.register'), new_user_path diff --git a/app/views/web/users/new.html.haml b/app/views/web/users/new.html.haml index bd74fbc2..6f4117b6 100644 --- a/app/views/web/users/new.html.haml +++ b/app/views/web/users/new.html.haml @@ -4,4 +4,5 @@ = f.input :last_name, as: :string = f.input :email, as: :string = f.input :password, as: :password - = f.submit + = f.input :password_confirmation, as: :password + = f.button :submit, t('.register') diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 9ea4a1a3..6454f310 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -10,3 +10,4 @@ ru: role: Роль на сайте email: E-mail password: Пароль + password_confirmation: Подтверждение пароля diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml new file mode 100644 index 00000000..a5f5339f --- /dev/null +++ b/config/locales/ru/ru.yml @@ -0,0 +1,8 @@ +ru: + helpers: + new: + enter: Войти + web: + users: + new: + register: Регистрация From 2e11b0129a602c843da1ad90aaef33c6f99f214a Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 18:06:36 +0300 Subject: [PATCH 032/227] replace erb to haml --- app/views/layouts/application.html.erb | 14 -------------- app/views/layouts/application.html.haml | 9 +++++++++ 2 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/application.html.haml diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb deleted file mode 100644 index eb819fd1..00000000 --- a/app/views/layouts/application.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Ulmicru</title> - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> -</head> -<body> - -<%= yield %> - -</body> -</html> diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml new file mode 100644 index 00000000..c5d7b36e --- /dev/null +++ b/app/views/layouts/application.html.haml @@ -0,0 +1,9 @@ +%html +%head + %title + Ulmicru + = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true + = javascript_include_tag 'application', 'data-turbolinks-track' => true + = csrf_meta_tags +%body + = yield From 945175b400432880901a7020ca3240d91d428fee Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 18:10:09 +0300 Subject: [PATCH 033/227] add admin view --- Gemfile | 1 + Gemfile.lock | 7 +++++++ app/controllers/web/admin/application_controller.rb | 1 + app/views/layouts/web/admin/application.html.haml | 7 +++++++ 4 files changed, 16 insertions(+) create mode 100644 app/views/layouts/web/admin/application.html.haml diff --git a/Gemfile b/Gemfile index 009ff613..3e3078a8 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'authority' gem 'bcrypt', '~> 3.1.7' gem 'active_form', github: 'rails/actionform', ref: '41ec958' gem 'simple_form' +gem 'bootstrap-sass' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' diff --git a/Gemfile.lock b/Gemfile.lock index aa214e97..56b2ae10 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,9 +59,15 @@ GEM authority (3.0.0) activesupport (>= 3.0.0) rake (>= 0.8.7) + autoprefixer-rails (5.1.7) + execjs + json bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + bootstrap-sass (3.3.3) + autoprefixer-rails (>= 5.0.0.1) + sass (>= 3.2.19) builder (3.2.2) byebug (3.5.1) columnize (~> 0.8) @@ -268,6 +274,7 @@ DEPENDENCIES active_form! authority bcrypt (~> 3.1.7) + bootstrap-sass byebug coffee-rails (~> 4.1.0) coveralls diff --git a/app/controllers/web/admin/application_controller.rb b/app/controllers/web/admin/application_controller.rb index b570d0df..5c04df1a 100644 --- a/app/controllers/web/admin/application_controller.rb +++ b/app/controllers/web/admin/application_controller.rb @@ -1,2 +1,3 @@ class Web::Admin::ApplicationController < Web::ApplicationController + layout 'web/admin/application' end diff --git a/app/views/layouts/web/admin/application.html.haml b/app/views/layouts/web/admin/application.html.haml new file mode 100644 index 00000000..5d88259c --- /dev/null +++ b/app/views/layouts/web/admin/application.html.haml @@ -0,0 +1,7 @@ +%html +%head + %title + Админка + = csrf_meta_tags +%body + = yield From 7a11756ad5f42dfc5aa5996b109dd936f9439fb1 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 18:18:33 +0300 Subject: [PATCH 034/227] add bootstrap-sass --- app/assets/javascripts/web/admin/application.coffee | 5 ++--- app/assets/stylesheets/web/admin/application.sass | 2 ++ app/assets/stylesheets/web/admin/application.scss | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 app/assets/stylesheets/web/admin/application.sass delete mode 100644 app/assets/stylesheets/web/admin/application.scss diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index 24f83d18..868da33d 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -1,3 +1,2 @@ -# 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/ +#= require jquery +#= require bootstrap-sprockets diff --git a/app/assets/stylesheets/web/admin/application.sass b/app/assets/stylesheets/web/admin/application.sass new file mode 100644 index 00000000..838dc901 --- /dev/null +++ b/app/assets/stylesheets/web/admin/application.sass @@ -0,0 +1,2 @@ +@import 'bootstrap-sprockets' +@import 'bootstrap' diff --git a/app/assets/stylesheets/web/admin/application.scss b/app/assets/stylesheets/web/admin/application.scss deleted file mode 100644 index c649bcfb..00000000 --- a/app/assets/stylesheets/web/admin/application.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the web/admin/application controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ From f1a14e0156d034d3ca6db2eed8044f38c5f72c5c Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 19:18:05 +0300 Subject: [PATCH 035/227] bootstrap-sass --- app/views/layouts/web/admin/_navbar.html.haml | 12 ++++++++++++ app/views/layouts/web/admin/application.html.haml | 3 +++ config/application.rb | 9 +++++---- config/locales/ru/models.yml | 2 ++ config/locales/ru/ru.yml | 2 ++ 5 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 app/views/layouts/web/admin/_navbar.html.haml diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml new file mode 100644 index 00000000..57983645 --- /dev/null +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -0,0 +1,12 @@ +%header.navbar.navbar-default + .navbar-header + %button.navbar-toggle{ type: :button, data: { toggle: :collapse, target: '.navbar-collapse' } } + %span.icon-bar + %span.icon-bar + %span.icon-bar + %a.navbar-brand{ href: root_path } + = t('application.name') + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li + = link_to t('activerecord.models.plural.user'), admin_users_path diff --git a/app/views/layouts/web/admin/application.html.haml b/app/views/layouts/web/admin/application.html.haml index 5d88259c..67ddee8b 100644 --- a/app/views/layouts/web/admin/application.html.haml +++ b/app/views/layouts/web/admin/application.html.haml @@ -2,6 +2,9 @@ %head %title Админка + = stylesheet_link_tag 'web/admin/application' + = javascript_include_tag 'web/admin/application' = csrf_meta_tags %body + = render 'layouts/web/admin/navbar' = yield diff --git a/config/application.rb b/config/application.rb index e4875d1e..ef57b2c4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -8,6 +8,11 @@ module Ulmicru class Application < Rails::Application + config.active_record.raise_in_transactional_callbacks = true + config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] + config.i18n.available_locales = [:ru, :en] + config.i18n.default_locale = :ru + config.assets.initialize_on_precompile = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. @@ -21,9 +26,5 @@ class Application < Rails::Application # config.i18n.default_locale = :de # Do not swallow errors in after_commit/after_rollback callbacks. - config.active_record.raise_in_transactional_callbacks = true - config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] - config.i18n.available_locales = [:ru, :en] - config.i18n.default_locale = :ru end end diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 6454f310..24712409 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -2,6 +2,8 @@ ru: activerecord: models: user: Пользователь + plural: + user: Пользователи attributes: user: first_name: Имя diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index a5f5339f..8f371687 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -1,4 +1,6 @@ ru: + application: + name: Сайт МИЦ helpers: new: enter: Войти From 8d3302c68e14491677a51efefffc68f4a9bde386 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 19:34:29 +0300 Subject: [PATCH 036/227] add inflections --- app/views/layouts/web/admin/_navbar.html.haml | 2 +- config/initializers/inflections.rb | 14 +++----------- config/locales/ru/models.yml | 2 -- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 57983645..e6383d87 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -9,4 +9,4 @@ .navbar-collapse.collapse %ul.nav.navbar-nav %li - = link_to t('activerecord.models.plural.user'), admin_users_path + = link_to User.model_name.human.pluralize(:ru), admin_users_path diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ac033bf9..56bb147d 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -3,14 +3,6 @@ # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' -# inflect.uncountable %w( fish sheep ) -# end - -# These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' -# end +ActiveSupport::Inflector.inflections(:ru) do |inflect| + inflect.plural /ль$/i, 'ли' +end diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 24712409..6454f310 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -2,8 +2,6 @@ ru: activerecord: models: user: Пользователь - plural: - user: Пользователи attributes: user: first_name: Имя From aaad66b5da6717501b9c7db1be9674210f2ed89c Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 19:45:07 +0300 Subject: [PATCH 037/227] remove english from locales --- config/application.rb | 2 +- config/locales/en.yml | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 config/locales/en.yml diff --git a/config/application.rb b/config/application.rb index ef57b2c4..f8b08388 100644 --- a/config/application.rb +++ b/config/application.rb @@ -10,7 +10,7 @@ module Ulmicru class Application < Rails::Application config.active_record.raise_in_transactional_callbacks = true config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] - config.i18n.available_locales = [:ru, :en] + config.i18n.available_locales = :ru config.i18n.default_locale = :ru config.assets.initialize_on_precompile = true # Settings in config/environments/* take precedence over those specified here. diff --git a/config/locales/en.yml b/config/locales/en.yml deleted file mode 100644 index 06539571..00000000 --- a/config/locales/en.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t 'hello' -# -# In views, this is aliased to just `t`: -# -# <%= t('hello') %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - -en: - hello: "Hello world" From 9f09889b8c387c0b557b0adba99fc04e18d8bcb4 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 20:12:50 +0300 Subject: [PATCH 038/227] add functional in admin/users --- .../javascripts/web/admin/application.coffee | 4 +++ app/assets/stylesheets/application.css | 15 ----------- app/assets/stylesheets/application.css.sass | 4 +++ .../stylesheets/web/admin/application.sass | 3 +++ app/models/user.rb | 2 ++ app/views/web/admin/users/index.html.haml | 27 +++++++++---------- config/locales/ru/ru.yml | 8 ++++-- ...50301164922_remove_patronymic_from_user.rb | 5 ++++ db/schema.rb | 3 +-- 9 files changed, 37 insertions(+), 34 deletions(-) delete mode 100644 app/assets/stylesheets/application.css create mode 100644 app/assets/stylesheets/application.css.sass create mode 100644 db/migrate/20150301164922_remove_patronymic_from_user.rb diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index 868da33d..02f745c1 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -1,2 +1,6 @@ #= require jquery #= require bootstrap-sprockets + +$ -> + $('.link').click -> + location.href = $(this).attr('data-href') diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index f9cd5b34..00000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any styles - * defined in the other CSS/SCSS files in this directory. It is generally better to create a new - * file per style scope. - * - *= require_tree . - *= require_self - */ diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass new file mode 100644 index 00000000..4083d867 --- /dev/null +++ b/app/assets/stylesheets/application.css.sass @@ -0,0 +1,4 @@ +/* + *= require_self + */ + diff --git a/app/assets/stylesheets/web/admin/application.sass b/app/assets/stylesheets/web/admin/application.sass index 838dc901..12f9c1e1 100644 --- a/app/assets/stylesheets/web/admin/application.sass +++ b/app/assets/stylesheets/web/admin/application.sass @@ -1,2 +1,5 @@ @import 'bootstrap-sprockets' @import 'bootstrap' + +.link + cursor: pointer diff --git a/app/models/user.rb b/app/models/user.rb index 3b4a7ea2..322f2911 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,4 +7,6 @@ class User < ActiveRecord::Base extend Enumerize enumerize :role, in: [ :user, :admin ], default: :user + + scope :admins, -> { where role: :admin } end diff --git a/app/views/web/admin/users/index.html.haml b/app/views/web/admin/users/index.html.haml index 54875ee7..97bf7d93 100644 --- a/app/views/web/admin/users/index.html.haml +++ b/app/views/web/admin/users/index.html.haml @@ -4,26 +4,23 @@ .page-header %h1=t '.title' - model_class = User -%table.table.table-striped.table-condensed +%table.table.table-condensed.table-hover %thead %tr %th= model_class.human_attribute_name(:id) - %th= model_class.human_attribute_name(:avatar) - %th= model_class.human_attribute_name(:full_name) - %th= model_class.human_attribute_name(:birth_date) - %th= model_class.human_attribute_name(:place) - %th= model_class.human_attribute_name(:school_group) - %th= model_class.human_attribute_name(:contacts) - %th= model_class.human_attribute_name(:creative_work) - %th= model_class.human_attribute_name(:average) - %th=t '.actions', default: t("helpers.actions") + %th= model_class.human_attribute_name(:first_name) + %th= model_class.human_attribute_name(:last_name) + %th= model_class.human_attribute_name(:email) + %th= t 'helpers.actions' %tbody - @users.each do |user| - %tr - %td= link_to user.id, edit_admin_user_path(user) - %td= link_to user.email, edit_admin_user_path(user) + %tr.link{ data: { href: edit_admin_user_path(user) } } + %td= user.id + %td= user.first_name + %td= user.last_name + %td= user.email %td - = link_to t('.edit', default: t('helpers.links.edit')), edit_admin_user_path(user), class: 'btn btn-warning btn-xs' - = link_to t('.destroy', default: t('helpers.links.destroy')), admin_user_path(user), method: :delete, data: { confirm: t('.confirm', default: t('helpers.links.confirm', default: 'Are you sure?')) }, class: 'btn btn-xs btn-danger' + = link_to t('helpers.links.edit'), edit_admin_user_path(user), class: 'btn btn-warning btn-xs' + = link_to t('helpers.links.destroy'), admin_user_path(user), method: :delete, class: 'btn btn-xs btn-danger' = link_to t('.new', default: t('helpers.links.new')), new_admin_user_path, class: 'btn btn-primary' diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 8f371687..e0e9a45b 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -2,8 +2,12 @@ ru: application: name: Сайт МИЦ helpers: - new: - enter: Войти + actions: Действия + enter: Войти + links: + edit: Изменить + destroy: Удалить + new: Добавить web: users: new: diff --git a/db/migrate/20150301164922_remove_patronymic_from_user.rb b/db/migrate/20150301164922_remove_patronymic_from_user.rb new file mode 100644 index 00000000..027dfe3e --- /dev/null +++ b/db/migrate/20150301164922_remove_patronymic_from_user.rb @@ -0,0 +1,5 @@ +class RemovePatronymicFromUser < ActiveRecord::Migration + def change + remove_column :users, :patronymic + end +end diff --git a/db/schema.rb b/db/schema.rb index 12962bde..7e7a2b03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150227220540) do +ActiveRecord::Schema.define(version: 20150301164922) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -31,7 +31,6 @@ t.datetime "updated_at", null: false t.text "role" t.text "first_name" - t.text "patronymic" t.text "last_name" end From 6e7a796bf9cc051cb17a0fecd387ef6558e1ee9f Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 20:14:32 +0300 Subject: [PATCH 039/227] add container --- app/views/layouts/web/admin/application.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/web/admin/application.html.haml b/app/views/layouts/web/admin/application.html.haml index 67ddee8b..a64784b1 100644 --- a/app/views/layouts/web/admin/application.html.haml +++ b/app/views/layouts/web/admin/application.html.haml @@ -7,4 +7,5 @@ = csrf_meta_tags %body = render 'layouts/web/admin/navbar' - = yield + .container + = yield From 29691e855c9e4c0d5967a1008cfba24a2db4dc06 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 20:17:00 +0300 Subject: [PATCH 040/227] add title --- app/views/web/admin/users/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/web/admin/users/index.html.haml b/app/views/web/admin/users/index.html.haml index 97bf7d93..a22dffcd 100644 --- a/app/views/web/admin/users/index.html.haml +++ b/app/views/web/admin/users/index.html.haml @@ -1,9 +1,9 @@ = javascript_include_tag :tabs = stylesheet_link_tag :tabs -.page-header - %h1=t '.title' - model_class = User +.page-header + %h1= model_class.model_name.human.pluralize(:ru) %table.table.table-condensed.table-hover %thead %tr From 79a60fb28b84fb463d3b4ec286a7140d52bb8c57 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 20:21:19 +0300 Subject: [PATCH 041/227] add menu_item to navbar --- app/helpers/application_helper.rb | 33 +++++++++++++++++++ app/views/layouts/web/admin/_navbar.html.haml | 4 +-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be794..b13b1f41 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,35 @@ module ApplicationHelper + def menu_item(name = nil, path = '#', *args, &block) + path = name || path if block_given? + options = args.extract_options! + content_tag :li, class: is_active?(path, options) do + name, path = path, options if block_given? + link_to name, path, options, &block + end + end + + def is_active?(path, options = {}) + 'active' if uri_state(path, options).in? [:active, :chosen] + end + + def uri_state(uri, options = {}) + root_url = request.host_with_port + '/' + root = uri == '/' || uri == root_url + request_uri = if uri.start_with? root_url + request.url + else + request.path + end + if !options[:method].nil? || !options["data-method"].nil? + :inactive + elsif uri == request_uri || (options[:root] && (request_uri == '/') || (request_uri == root_url)) + :active + else + if request_uri.start_with?(uri) and not(root) + :chosen + else + :inactive + end + end + end end diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index e6383d87..0679e4da 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -8,5 +8,5 @@ = t('application.name') .navbar-collapse.collapse %ul.nav.navbar-nav - %li - = link_to User.model_name.human.pluralize(:ru), admin_users_path + = menu_item admin_users_path do + = User.model_name.human.pluralize(:ru) From e4be3d58c19f7e33c148b404c577f0a235683008 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 20:57:41 +0300 Subject: [PATCH 042/227] simple_form + bootstrap --- app/views/web/admin/users/_form.html.haml | 14 +- app/views/web/admin/users/edit.html.haml | 3 +- app/views/web/admin/users/new.html.haml | 3 +- config/initializers/simple_form.rb | 166 +++++++++++++++++++ config/initializers/simple_form_bootstrap.rb | 136 +++++++++++++++ 5 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 config/initializers/simple_form.rb create mode 100644 config/initializers/simple_form_bootstrap.rb diff --git a/app/views/web/admin/users/_form.html.haml b/app/views/web/admin/users/_form.html.haml index a16c644f..0cca53c8 100644 --- a/app/views/web/admin/users/_form.html.haml +++ b/app/views/web/admin/users/_form.html.haml @@ -1,5 +1,9 @@ -= simple_form_for @user, url: { controller: 'web/admin/users', action: action } do |f| - = f.input :email - = f.input :password - = f.input :role - = f.button :submit +.row + .col-lg-6 + = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, html: { class: 'form-horizontal' } do |f| + = f.input :first_name, as: :string + = f.input :last_name, as: :string + = f.input :email, as: :string + = f.input :password, as: :string + = f.input :role + = f.button :submit diff --git a/app/views/web/admin/users/edit.html.haml b/app/views/web/admin/users/edit.html.haml index 7c47ceea..7fec732a 100644 --- a/app/views/web/admin/users/edit.html.haml +++ b/app/views/web/admin/users/edit.html.haml @@ -1,3 +1,4 @@ -%h1= t('.title') +.page-header + %h1= t('.title') = render partial: 'form', locals: { action: :update } = link_to t('.back'), admin_users_path, class: "btn" diff --git a/app/views/web/admin/users/new.html.haml b/app/views/web/admin/users/new.html.haml index 22e1f7a6..12cd0d5a 100644 --- a/app/views/web/admin/users/new.html.haml +++ b/app/views/web/admin/users/new.html.haml @@ -1,3 +1,4 @@ -%h1= t('.title') +.page-header + %h1= t('.title') = render partial: 'form', locals: { action: :create } = link_to t('.back'), admin_users_path, class: "btn" diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb new file mode 100644 index 00000000..d5492e52 --- /dev/null +++ b/config/initializers/simple_form.rb @@ -0,0 +1,166 @@ +# 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. Please note that when using :boolean_style = :nested, + # SimpleForm will force this option to be a label. + # 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 00000000..ea4a63d9 --- /dev/null +++ b/config/initializers/simple_form_bootstrap.rb @@ -0,0 +1,136 @@ +# 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, class: 'col-sm-9' + 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 + + # 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, + } +end From 2b8147269157317eb9df9a1a9fd449788b3fcf78 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 21:03:22 +0300 Subject: [PATCH 043/227] add form-horizontal class --- app/views/web/admin/users/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/web/admin/users/_form.html.haml b/app/views/web/admin/users/_form.html.haml index 0cca53c8..eef1eb8e 100644 --- a/app/views/web/admin/users/_form.html.haml +++ b/app/views/web/admin/users/_form.html.haml @@ -1,6 +1,6 @@ .row .col-lg-6 - = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, html: { class: 'form-horizontal' } do |f| + = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :first_name, as: :string = f.input :last_name, as: :string = f.input :email, as: :string From 25f165b086850f181ac3a4b4b864ad6e6d93dbb1 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 21:51:17 +0300 Subject: [PATCH 044/227] add locals helper --- app/helpers/locals_helper.rb | 9 +++++++++ app/views/web/admin/users/edit.html.haml | 4 ++-- config/locales/ru/ru.yml | 4 +++- lib/russian_cases.rb | 6 ++++++ lib/yaml/russian_cases.yml | 3 +++ 5 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 app/helpers/locals_helper.rb create mode 100644 lib/russian_cases.rb create mode 100644 lib/yaml/russian_cases.yml diff --git a/app/helpers/locals_helper.rb b/app/helpers/locals_helper.rb new file mode 100644 index 00000000..328d24ee --- /dev/null +++ b/app/helpers/locals_helper.rb @@ -0,0 +1,9 @@ +require 'russian_cases' + +module LocalsHelper + include RussianCases + + def page_title(action, model_name) + t("helpers.actions.#{action}") + ' ' + genitive(model_name) + end +end diff --git a/app/views/web/admin/users/edit.html.haml b/app/views/web/admin/users/edit.html.haml index 7fec732a..a16a1587 100644 --- a/app/views/web/admin/users/edit.html.haml +++ b/app/views/web/admin/users/edit.html.haml @@ -1,4 +1,4 @@ .page-header - %h1= t('.title') + %h1= page_title(:update, User.model_name.human) = render partial: 'form', locals: { action: :update } -= link_to t('.back'), admin_users_path, class: "btn" += link_to t('.back'), admin_users_path, class: 'btn' diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index e0e9a45b..cf2d20bf 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -2,9 +2,11 @@ ru: application: name: Сайт МИЦ helpers: - actions: Действия enter: Войти + actions: + update: Редактировать links: + actions: Действия edit: Изменить destroy: Удалить new: Добавить diff --git a/lib/russian_cases.rb b/lib/russian_cases.rb new file mode 100644 index 00000000..5885138f --- /dev/null +++ b/lib/russian_cases.rb @@ -0,0 +1,6 @@ +module RussianCases + def genitive(word) + cases = YAML.load_file("#{Rails.root}/lib/yaml/russian_cases.yml").with_indifferent_access + cases[:cases][:genitive][word.mb_chars.downcase.to_s] + end +end diff --git a/lib/yaml/russian_cases.yml b/lib/yaml/russian_cases.yml new file mode 100644 index 00000000..256cc0e1 --- /dev/null +++ b/lib/yaml/russian_cases.yml @@ -0,0 +1,3 @@ +cases: + genitive: + 'пользователь': пользователя From 8d756595a0033390799fd3434d1b318cb8ce6490 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 22:17:17 +0300 Subject: [PATCH 045/227] forms for users --- app/views/web/admin/users/_form.html.haml | 5 ++++- app/views/web/admin/users/edit.html.haml | 3 --- app/views/web/admin/users/new.html.haml | 3 --- config/locales/ru/ru.yml | 2 ++ 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/views/web/admin/users/_form.html.haml b/app/views/web/admin/users/_form.html.haml index eef1eb8e..d42cb999 100644 --- a/app/views/web/admin/users/_form.html.haml +++ b/app/views/web/admin/users/_form.html.haml @@ -1,3 +1,5 @@ +.page-header + %h1= page_title(action, User.model_name.human) .row .col-lg-6 = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, input_html: { class: 'form-horizontal' } do |f| @@ -6,4 +8,5 @@ = f.input :email, as: :string = f.input :password, as: :string = f.input :role - = f.button :submit + = f.button :submit, t('helpers.links.save'), class: 'btn-success' + = link_to t('helpers.links.back'), admin_users_path, class: 'btn btn-default' diff --git a/app/views/web/admin/users/edit.html.haml b/app/views/web/admin/users/edit.html.haml index a16a1587..51cb957a 100644 --- a/app/views/web/admin/users/edit.html.haml +++ b/app/views/web/admin/users/edit.html.haml @@ -1,4 +1 @@ -.page-header - %h1= page_title(:update, User.model_name.human) = render partial: 'form', locals: { action: :update } -= link_to t('.back'), admin_users_path, class: 'btn' diff --git a/app/views/web/admin/users/new.html.haml b/app/views/web/admin/users/new.html.haml index 12cd0d5a..02017918 100644 --- a/app/views/web/admin/users/new.html.haml +++ b/app/views/web/admin/users/new.html.haml @@ -1,4 +1 @@ -.page-header - %h1= t('.title') = render partial: 'form', locals: { action: :create } -= link_to t('.back'), admin_users_path, class: "btn" diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index cf2d20bf..1fda9340 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -10,6 +10,8 @@ ru: edit: Изменить destroy: Удалить new: Добавить + save: Сохранить + back: Назад web: users: new: From 094f0a1c895466efd509a14e65d2646dc9fbb976 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 22:21:10 +0300 Subject: [PATCH 046/227] add state to users --- app/models/user.rb | 17 +++++++++++++++++ db/migrate/20150301191940_add_state_to_users.rb | 5 +++++ db/schema.rb | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150301191940_add_state_to_users.rb diff --git a/app/models/user.rb b/app/models/user.rb index 322f2911..3b6f2f69 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,4 +9,21 @@ class User < ActiveRecord::Base enumerize :role, in: [ :user, :admin ], default: :user scope :admins, -> { where role: :admin } + + state_machine initilize: :not_confirmed do + state :not_confirmed + state :confirmed + state :declined + state :deleted + + event :confirm do + transition all => :confirmed + end + event :decline do + transition all => :declined + end + event :delete do + transition all => :delete + end + end end diff --git a/db/migrate/20150301191940_add_state_to_users.rb b/db/migrate/20150301191940_add_state_to_users.rb new file mode 100644 index 00000000..17f92ac3 --- /dev/null +++ b/db/migrate/20150301191940_add_state_to_users.rb @@ -0,0 +1,5 @@ +class AddStateToUsers < ActiveRecord::Migration + def change + add_column :users, :state, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 7e7a2b03..19e8aad2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150301164922) do +ActiveRecord::Schema.define(version: 20150301191940) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -32,6 +32,7 @@ t.text "role" t.text "first_name" t.text "last_name" + t.text "state" end end From d739b181e4b57818720be61f70dff27dfc5670e2 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 22:25:10 +0300 Subject: [PATCH 047/227] add state_machine and fix tests --- Gemfile | 1 + Gemfile.lock | 7 +++++++ app/models/user.rb | 2 +- app/views/web/users/new.html.haml | 1 - test/controllers/web/users_controller_test.rb | 2 +- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 3e3078a8..574ac13b 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,7 @@ gem 'bcrypt', '~> 3.1.7' gem 'active_form', github: 'rails/actionform', ref: '41ec958' gem 'simple_form' gem 'bootstrap-sass' +gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' diff --git a/Gemfile.lock b/Gemfile.lock index 56b2ae10..c1277175 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,6 +16,12 @@ GIT chattyproc (~> 1.0.0) term-ansicolor (~> 1.0.7) +GIT + remote: https://github.com/seuros/state_machine.git + revision: b5baef856b170f18dc2fdc36def2411c255335b9 + specs: + state_machine (1.2.0) + GEM remote: https://rubygems.org/ specs: @@ -297,6 +303,7 @@ DEPENDENCIES simplecov spring sqlite3 + state_machine! tconsole! turbolinks uglifier (>= 1.3.0) diff --git a/app/models/user.rb b/app/models/user.rb index 3b6f2f69..473c4071 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,7 +10,7 @@ class User < ActiveRecord::Base scope :admins, -> { where role: :admin } - state_machine initilize: :not_confirmed do + state_machine initial: :not_confirmed do state :not_confirmed state :confirmed state :declined diff --git a/app/views/web/users/new.html.haml b/app/views/web/users/new.html.haml index 6f4117b6..9c97ed71 100644 --- a/app/views/web/users/new.html.haml +++ b/app/views/web/users/new.html.haml @@ -1,6 +1,5 @@ = simple_form_for @user, url: { controller: 'web/users', action: :create } do |f| = f.input :first_name, as: :string - = f.input :patronymic, as: :string = f.input :last_name, as: :string = f.input :email, as: :string = f.input :password, as: :password diff --git a/test/controllers/web/users_controller_test.rb b/test/controllers/web/users_controller_test.rb index 763e2d63..b0d075a2 100644 --- a/test/controllers/web/users_controller_test.rb +++ b/test/controllers/web/users_controller_test.rb @@ -14,7 +14,7 @@ class Web::UsersControllerTest < ActionController::TestCase attributes = attributes_for :user post :create, user: attributes assert_response :redirect, @response.body - assert_redirected_to admin_users_path + assert_redirected_to root_path assert_equal attributes[:email], User.last.email end end From faf26d9bbbab5ed05d7dfe7fbb227d2b393c894d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 22:32:04 +0300 Subject: [PATCH 048/227] remove users --- app/controllers/web/admin/users_controller.rb | 2 +- app/models/user.rb | 8 ++++---- test/controllers/web/admin/users_controller_test.rb | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/controllers/web/admin/users_controller.rb b/app/controllers/web/admin/users_controller.rb index 0c041342..14567f40 100644 --- a/app/controllers/web/admin/users_controller.rb +++ b/app/controllers/web/admin/users_controller.rb @@ -37,7 +37,7 @@ def update def destroy @user = User.find params[:id] - @user.destroy + @user.remove redirect_to admin_users_path end end diff --git a/app/models/user.rb b/app/models/user.rb index 473c4071..366078a7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,11 +10,11 @@ class User < ActiveRecord::Base scope :admins, -> { where role: :admin } - state_machine initial: :not_confirmed do + state_machine :state, initial: :not_confirmed do state :not_confirmed state :confirmed state :declined - state :deleted + state :removed event :confirm do transition all => :confirmed @@ -22,8 +22,8 @@ class User < ActiveRecord::Base event :decline do transition all => :declined end - event :delete do - transition all => :delete + event :remove do + transition all => :removed end end end diff --git a/test/controllers/web/admin/users_controller_test.rb b/test/controllers/web/admin/users_controller_test.rb index 8fa94177..43b3f5e8 100644 --- a/test/controllers/web/admin/users_controller_test.rb +++ b/test/controllers/web/admin/users_controller_test.rb @@ -35,6 +35,7 @@ class Web::Admin::UsersControllerTest < ActionController::TestCase test 'should delete destroy' do count = User.count delete :destroy, id: @user - assert_equal count - 1, User.count + @user.reload + assert @user.removed? end end From 8e9cf3eac9af8776b0dc88dc5d5cd22a16ab5cd9 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 23:12:57 +0300 Subject: [PATCH 049/227] add trash --- Gemfile | 1 + Gemfile.lock | 7 +++++ app/assets/javascripts/web/admin/trash.coffee | 3 +++ app/assets/stylesheets/web/admin/trash.scss | 3 +++ app/controllers/web/admin/trash_controller.rb | 20 ++++++++++++++ app/decorators/user_decorator.rb | 8 ++++++ app/helpers/web/admin/trash_helper.rb | 2 ++ app/models/user.rb | 5 ++++ app/views/web/admin/trash/index.html.haml | 17 ++++++++++++ app/views/web/admin/users/index.html.haml | 2 +- config/routes.rb | 9 +++++++ .../web/admin/trash_controller_test.rb | 26 +++++++++++++++++++ test/decorators/user_decorator_test.rb | 4 +++ 13 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/web/admin/trash.coffee create mode 100644 app/assets/stylesheets/web/admin/trash.scss create mode 100644 app/controllers/web/admin/trash_controller.rb create mode 100644 app/decorators/user_decorator.rb create mode 100644 app/helpers/web/admin/trash_helper.rb create mode 100644 app/views/web/admin/trash/index.html.haml create mode 100644 test/controllers/web/admin/trash_controller_test.rb create mode 100644 test/decorators/user_decorator_test.rb diff --git a/Gemfile b/Gemfile index 574ac13b..5c99f311 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,7 @@ gem 'active_form', github: 'rails/actionform', ref: '41ec958' gem 'simple_form' gem 'bootstrap-sass' gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' +gem 'draper' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' diff --git a/Gemfile.lock b/Gemfile.lock index c1277175..ab2949ad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,6 +98,11 @@ GEM debug_inspector (0.0.2) debugger-linecache (1.2.0) docile (1.1.5) + draper (1.4.0) + actionpack (>= 3.0) + activemodel (>= 3.0) + activesupport (>= 3.0) + request_store (~> 1.0) enumerize (0.9.0) activesupport (>= 3.2) erubis (2.7.0) @@ -220,6 +225,7 @@ GEM rake (10.4.2) rdoc (4.2.0) json (~> 1.4) + request_store (1.1.0) rest-client (1.7.3) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) @@ -284,6 +290,7 @@ DEPENDENCIES byebug coffee-rails (~> 4.1.0) coveralls + draper enumerize factory_girl_rails haml-rails diff --git a/app/assets/javascripts/web/admin/trash.coffee b/app/assets/javascripts/web/admin/trash.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/admin/trash.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/web/admin/trash.scss b/app/assets/stylesheets/web/admin/trash.scss new file mode 100644 index 00000000..97b848db --- /dev/null +++ b/app/assets/stylesheets/web/admin/trash.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/admin/trash 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/web/admin/trash_controller.rb b/app/controllers/web/admin/trash_controller.rb new file mode 100644 index 00000000..193655b6 --- /dev/null +++ b/app/controllers/web/admin/trash_controller.rb @@ -0,0 +1,20 @@ +class Web::Admin::TrashController < Web::Admin::ApplicationController + def index + @type = params[:type].capitalize + @items = Object.const_get("#{@type}Decorator").decorate_collection Object.const_get(@type).removed + end + + def restore + type = params[:type] + item = Object.const_get(type.capitalize).find params[:id] + item.restore + redirect_to "/admin/trash/index/#{type}" + end + + def destroy + type = params[:type] + item = Object.const_get(type.capitalize).find params[:id] + item.destroy + redirect_to "/admin/trash/index/#{type}" + end +end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb new file mode 100644 index 00000000..d5bad13b --- /dev/null +++ b/app/decorators/user_decorator.rb @@ -0,0 +1,8 @@ +class UserDecorator < Draper::Decorator + delegate_all + + def name + "#{first_name} #{last_name}" + end + +end diff --git a/app/helpers/web/admin/trash_helper.rb b/app/helpers/web/admin/trash_helper.rb new file mode 100644 index 00000000..a986eae0 --- /dev/null +++ b/app/helpers/web/admin/trash_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::TrashHelper +end diff --git a/app/models/user.rb b/app/models/user.rb index 366078a7..e1ece55e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,6 +10,8 @@ class User < ActiveRecord::Base scope :admins, -> { where role: :admin } + scope :removed, -> { where state: :removed } + state_machine :state, initial: :not_confirmed do state :not_confirmed state :confirmed @@ -25,5 +27,8 @@ class User < ActiveRecord::Base event :remove do transition all => :removed end + event :restore do + transition :removed => :not_confirmed + end end end diff --git a/app/views/web/admin/trash/index.html.haml b/app/views/web/admin/trash/index.html.haml new file mode 100644 index 00000000..ad70831e --- /dev/null +++ b/app/views/web/admin/trash/index.html.haml @@ -0,0 +1,17 @@ +- model_class = Object.const_get(@type) +.page-header + %h1= model_class.model_name.human.pluralize(:ru) +%table.table.table-condensed.table-hover + %thead + %tr + %th ID + %th= t('web.admin.trash.name') + %th= t 'helpers.links.actions' + %tbody + - @items.each do |item| + %tr + %td= item.id + %td= item.name + %td + = link_to t('helpers.links.restore'), restore_admin_trash_path(item, type: @type), method: :patch, class: 'btn btn-primary btn-xs' + = link_to t('helpers.links.destroy'), admin_trash_path(item, type: @type), method: :delete, class: 'btn btn-xs btn-danger' diff --git a/app/views/web/admin/users/index.html.haml b/app/views/web/admin/users/index.html.haml index a22dffcd..cb4bb09e 100644 --- a/app/views/web/admin/users/index.html.haml +++ b/app/views/web/admin/users/index.html.haml @@ -11,7 +11,7 @@ %th= model_class.human_attribute_name(:first_name) %th= model_class.human_attribute_name(:last_name) %th= model_class.human_attribute_name(:email) - %th= t 'helpers.actions' + %th= t 'helpers.links.actions' %tbody - @users.each do |user| %tr.link{ data: { href: edit_admin_user_path(user) } } diff --git a/config/routes.rb b/config/routes.rb index fcc23fd7..b5249e96 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,15 @@ namespace :admin do root to: 'welcome#index' resources :users + resources :trash, only: [] do + collection do + get 'index/:type' => 'trash#index' + end + member do + patch 'restore' => 'trash#restore' + delete 'destroy' => 'trash#destroy' + end + end end end diff --git a/test/controllers/web/admin/trash_controller_test.rb b/test/controllers/web/admin/trash_controller_test.rb new file mode 100644 index 00000000..d45e09bb --- /dev/null +++ b/test/controllers/web/admin/trash_controller_test.rb @@ -0,0 +1,26 @@ +require 'test_helper' + +class Web::Admin::TrashControllerTest < ActionController::TestCase + setup do + @user = create :user + @user.remove + end + test 'should get index' do + get :index, type: :user + assert_response :success, @response.body + end + + test 'should patch restore' do + patch :restore, type: :user, id: @user + @user.reload + assert @user.not_confirmed? + assert_response :redirect, @response.body + end + + test 'should delete destroy' do + count = User.count + delete :destroy, type: :user, id: @user + assert_equal count - 1, User.count + assert_response :redirect, @response.body + end +end diff --git a/test/decorators/user_decorator_test.rb b/test/decorators/user_decorator_test.rb new file mode 100644 index 00000000..71c86d2e --- /dev/null +++ b/test/decorators/user_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class UserDecoratorTest < Draper::TestCase +end From 8ce3f6a7f869caf0a289bac8ea47cbc1b5f3c95c Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 1 Mar 2015 23:33:32 +0300 Subject: [PATCH 050/227] unworking patch and delete links --- app/controllers/web/admin/trash_controller.rb | 6 +++--- app/helpers/locals_helper.rb | 5 +++++ app/views/layouts/web/admin/_navbar.html.haml | 4 ++++ app/views/web/admin/trash/index.html.haml | 2 +- config/locales/ru/ru.yml | 5 +++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/controllers/web/admin/trash_controller.rb b/app/controllers/web/admin/trash_controller.rb index 193655b6..3ec77653 100644 --- a/app/controllers/web/admin/trash_controller.rb +++ b/app/controllers/web/admin/trash_controller.rb @@ -1,19 +1,19 @@ class Web::Admin::TrashController < Web::Admin::ApplicationController def index @type = params[:type].capitalize - @items = Object.const_get("#{@type}Decorator").decorate_collection Object.const_get(@type).removed + @items = "#{@type}Decorator".constantize.decorate_collection @type.constantize.removed end def restore type = params[:type] - item = Object.const_get(type.capitalize).find params[:id] + item = type.capitalize.constantize.find params[:id] item.restore redirect_to "/admin/trash/index/#{type}" end def destroy type = params[:type] - item = Object.const_get(type.capitalize).find params[:id] + item = type.capitalize.constantize.find params[:id] item.destroy redirect_to "/admin/trash/index/#{type}" end diff --git a/app/helpers/locals_helper.rb b/app/helpers/locals_helper.rb index 328d24ee..bbb2c9c9 100644 --- a/app/helpers/locals_helper.rb +++ b/app/helpers/locals_helper.rb @@ -6,4 +6,9 @@ module LocalsHelper def page_title(action, model_name) t("helpers.actions.#{action}") + ' ' + genitive(model_name) end + + def trash_page_title(model_class) + t('web.trash.deleted') + ' ' + model_class.model_name.human.pluralize(:ru).mb_chars.downcase.to_s + end + end diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 0679e4da..09ab6c96 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -10,3 +10,7 @@ %ul.nav.navbar-nav = menu_item admin_users_path do = User.model_name.human.pluralize(:ru) + %ul.nav.navbar-nav.navbar-right + = menu_item '/admin/trash/index/user' do + %span.glyphicon.glyphicon-trash + = t('web.trash.title') diff --git a/app/views/web/admin/trash/index.html.haml b/app/views/web/admin/trash/index.html.haml index ad70831e..aff9e653 100644 --- a/app/views/web/admin/trash/index.html.haml +++ b/app/views/web/admin/trash/index.html.haml @@ -1,6 +1,6 @@ - model_class = Object.const_get(@type) .page-header - %h1= model_class.model_name.human.pluralize(:ru) + %h1= trash_page_title(model_class) %table.table.table-condensed.table-hover %thead %tr diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 1fda9340..3bc0aca0 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -9,6 +9,7 @@ ru: actions: Действия edit: Изменить destroy: Удалить + restore: Восстановить new: Добавить save: Сохранить back: Назад @@ -16,3 +17,7 @@ ru: users: new: register: Регистрация + trash: + name: Имя + title: Корзина + deleted: Удалённые From c4bba0bb3e31b0c6bdd0f515b400cd1189bd7e26 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 2 Mar 2015 14:24:34 +0300 Subject: [PATCH 051/227] update Readme --- ReadMe.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReadMe.md b/ReadMe.md index 20490594..2b9363cd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -5,5 +5,6 @@ bundle /* CREATE ROLE mic IN PG */ rake db:create db:migrate cp config/secrets.yml.sample config/secrets.yml +cp config/oauth.yml.sample config/oauth.yml rails s ``` From 3b0f09e89255c62d0049792b01871068e663b93c Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Mon, 2 Mar 2015 15:18:08 +0300 Subject: [PATCH 052/227] Add unicorn-rails gem for more beautiful life. --- Gemfile | 2 +- Gemfile.lock | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 5c99f311..94fafb70 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ gem 'turbolinks' gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc - +gem 'unicorn-rails' gem 'haml-rails' gem 'enumerize' gem 'authority' diff --git a/Gemfile.lock b/Gemfile.lock index ab2949ad..0b5e4b99 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -141,6 +141,7 @@ GEM thor (>= 0.14, < 2.0) json (1.8.2) jwt (1.2.0) + kgio (2.9.3) loofah (2.0.1) nokogiri (>= 1.5.9) mail (2.6.3) @@ -222,6 +223,7 @@ GEM activesupport (= 4.2.0) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) + raindrops (0.13.0) rake (10.4.2) rdoc (4.2.0) json (~> 1.4) @@ -273,6 +275,13 @@ GEM uglifier (2.7.0) execjs (>= 0.3.0) json (>= 1.8.0) + unicorn (4.8.3) + kgio (~> 2.6) + rack + raindrops (~> 0.7) + unicorn-rails (2.2.0) + rack + unicorn web-console (2.0.0) activemodel (~> 4.0) binding_of_caller (>= 0.7.2) @@ -314,4 +323,5 @@ DEPENDENCIES tconsole! turbolinks uglifier (>= 1.3.0) + unicorn-rails web-console (~> 2.0) From 32c3f9bc7de3752900ced39312ff31a8b7b36341 Mon Sep 17 00:00:00 2001 From: Dmitry Davydov <haudvd@gmail.com> Date: Mon, 2 Mar 2015 21:57:37 +0300 Subject: [PATCH 053/227] some refactor in trash_controller --- app/controllers/web/admin/trash_controller.rb | 20 +++++++++++-------- app/views/web/admin/trash/index.html.haml | 2 +- config/routes.rb | 7 ++++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/controllers/web/admin/trash_controller.rb b/app/controllers/web/admin/trash_controller.rb index 3ec77653..ca85aaa0 100644 --- a/app/controllers/web/admin/trash_controller.rb +++ b/app/controllers/web/admin/trash_controller.rb @@ -1,20 +1,24 @@ class Web::Admin::TrashController < Web::Admin::ApplicationController def index - @type = params[:type].capitalize - @items = "#{@type}Decorator".constantize.decorate_collection @type.constantize.removed + @type = resource_type + @items = resource_type.removed.decorate end def restore - type = params[:type] - item = type.capitalize.constantize.find params[:id] + item = resource_type.find params[:id] item.restore - redirect_to "/admin/trash/index/#{type}" + redirect_to type_admin_trash_index_path(resource_type) end def destroy - type = params[:type] - item = type.capitalize.constantize.find params[:id] + item = resource_type.find params[:id] item.destroy - redirect_to "/admin/trash/index/#{type}" + redirect_to type_admin_trash_index_path(resource_type) + end + + private + + def resource_type + @_type ||= params[:type].capitalize.constantize end end diff --git a/app/views/web/admin/trash/index.html.haml b/app/views/web/admin/trash/index.html.haml index aff9e653..1f4268c5 100644 --- a/app/views/web/admin/trash/index.html.haml +++ b/app/views/web/admin/trash/index.html.haml @@ -1,4 +1,4 @@ -- model_class = Object.const_get(@type) +- model_class = @type .page-header %h1= trash_page_title(model_class) %table.table.table-condensed.table-hover diff --git a/config/routes.rb b/config/routes.rb index b5249e96..48963efe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- Rails.application.routes.draw do root to: 'web/welcome#index' @@ -11,11 +12,11 @@ resources :users resources :trash, only: [] do collection do - get 'index/:type' => 'trash#index' + get 'index/:type' => 'trash#index', as: :type end member do - patch 'restore' => 'trash#restore' - delete 'destroy' => 'trash#destroy' + patch 'restore' + delete 'destroy' end end end From bf782b5bf18dadfcece96732ea58218108e95f86 Mon Sep 17 00:00:00 2001 From: Dmitry Davydov <haudvd@gmail.com> Date: Mon, 2 Mar 2015 21:59:26 +0300 Subject: [PATCH 054/227] add database.yml to gitignore and add seeds --- .gitignore | 1 + ReadMe.md | 6 ++++-- config/{database.yml => database.yml.sample} | 5 ++--- db/seeds.rb | 4 ++++ 4 files changed, 11 insertions(+), 5 deletions(-) rename config/{database.yml => database.yml.sample} (88%) diff --git a/.gitignore b/.gitignore index d562746a..ece194c9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,6 @@ !/log/.keep /tmp config/secrets.yml +config/database.yml config/oauth.yml ulmic_test diff --git a/ReadMe.md b/ReadMe.md index 2b9363cd..fa4c4e2d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2,9 +2,11 @@ git clone git@github.com:ulmic/ulmicru.git cd ulmicru bundle -/* CREATE ROLE mic IN PG */ -rake db:create db:migrate +cp config/database.yml.sample config/database.yml +/* CREATE ROLE IN PG */ +rake db:create db:migrate db:seed cp config/secrets.yml.sample config/secrets.yml +/* ASK oauth.yml WITH REAL KEYS */ cp config/oauth.yml.sample config/oauth.yml rails s ``` diff --git a/config/database.yml b/config/database.yml.sample similarity index 88% rename from config/database.yml rename to config/database.yml.sample index 46495873..b3338409 100644 --- a/config/database.yml +++ b/config/database.yml.sample @@ -9,10 +9,9 @@ development: username: develop database: ulmic_development test: - adapter: sqlite3 + <<: *default + username: develop database: ulmic_test - pool: 5 - timeout: 5000 staging: <<: *default diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e85..8c76eb9f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,7 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) + + +admin = User.create({email: "admin@ulmic.ru", password: "admin", + state: :confirmed, role: :admin}) From 2465af54fdf06b06546951a665b7d5e687a4afe5 Mon Sep 17 00:00:00 2001 From: Dmitry Davydov <haudvd@gmail.com> Date: Mon, 2 Mar 2015 22:00:58 +0300 Subject: [PATCH 055/227] some fix --- app/assets/javascripts/web/admin/application.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index 02f745c1..99f54b08 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -1,4 +1,5 @@ #= require jquery +#= require jquery_ujs #= require bootstrap-sprockets $ -> From bd99020f26f7c14ba0351ae580134140b4261dd3 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Mon, 2 Mar 2015 22:11:32 +0300 Subject: [PATCH 056/227] generate new news_controller --- app/assets/javascripts/web/news.coffee | 3 +++ app/assets/stylesheets/web/news.scss | 3 +++ app/controllers/web/news_controller.rb | 2 ++ app/decorators/web/news_decorator.rb | 13 +++++++++++++ app/helpers/web/news_helper.rb | 2 ++ test/controllers/web/news_controller_test.rb | 7 +++++++ test/decorators/web/news_decorator_test.rb | 4 ++++ 7 files changed, 34 insertions(+) create mode 100644 app/assets/javascripts/web/news.coffee create mode 100644 app/assets/stylesheets/web/news.scss create mode 100644 app/controllers/web/news_controller.rb create mode 100644 app/decorators/web/news_decorator.rb create mode 100644 app/helpers/web/news_helper.rb create mode 100644 test/controllers/web/news_controller_test.rb create mode 100644 test/decorators/web/news_decorator_test.rb diff --git a/app/assets/javascripts/web/news.coffee b/app/assets/javascripts/web/news.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/news.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/web/news.scss b/app/assets/stylesheets/web/news.scss new file mode 100644 index 00000000..933ee767 --- /dev/null +++ b/app/assets/stylesheets/web/news.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/news 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/web/news_controller.rb b/app/controllers/web/news_controller.rb new file mode 100644 index 00000000..c56b1e2a --- /dev/null +++ b/app/controllers/web/news_controller.rb @@ -0,0 +1,2 @@ +class Web::NewsController < ApplicationController +end diff --git a/app/decorators/web/news_decorator.rb b/app/decorators/web/news_decorator.rb new file mode 100644 index 00000000..e420ad35 --- /dev/null +++ b/app/decorators/web/news_decorator.rb @@ -0,0 +1,13 @@ +class Web::NewsDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/helpers/web/news_helper.rb b/app/helpers/web/news_helper.rb new file mode 100644 index 00000000..f77ef808 --- /dev/null +++ b/app/helpers/web/news_helper.rb @@ -0,0 +1,2 @@ +module Web::NewsHelper +end diff --git a/test/controllers/web/news_controller_test.rb b/test/controllers/web/news_controller_test.rb new file mode 100644 index 00000000..1d10bbb1 --- /dev/null +++ b/test/controllers/web/news_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::NewsControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/decorators/web/news_decorator_test.rb b/test/decorators/web/news_decorator_test.rb new file mode 100644 index 00000000..135a4ebf --- /dev/null +++ b/test/decorators/web/news_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Web::NewsDecoratorTest < Draper::TestCase +end From 62773b01ab6a5ab196510bf22e34245d52ef30d2 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Mon, 2 Mar 2015 22:29:36 +0300 Subject: [PATCH 057/227] Add controller, model and migration for News --- app/controllers/web/news_controller.rb | 10 ++++++++++ app/models/news.rb | 2 ++ db/migrate/20150302192318_create_news.rb | 11 +++++++++++ db/schema.rb | 10 +++++++++- test/factories/news.rb | 8 ++++++++ test/models/news_test.rb | 7 +++++++ 6 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 app/models/news.rb create mode 100644 db/migrate/20150302192318_create_news.rb create mode 100644 test/factories/news.rb create mode 100644 test/models/news_test.rb diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index c56b1e2a..428c6d0c 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -1,2 +1,12 @@ class Web::NewsController < ApplicationController + def index + + end + + def show + @news = News.find(params[:id]).decorate + if !@news.is_published? + redirect_to not_found_errors_path + end + end end diff --git a/app/models/news.rb b/app/models/news.rb new file mode 100644 index 00000000..75904dae --- /dev/null +++ b/app/models/news.rb @@ -0,0 +1,2 @@ +class News < ActiveRecord::Base +end diff --git a/db/migrate/20150302192318_create_news.rb b/db/migrate/20150302192318_create_news.rb new file mode 100644 index 00000000..5f9f3361 --- /dev/null +++ b/db/migrate/20150302192318_create_news.rb @@ -0,0 +1,11 @@ +class CreateNews < ActiveRecord::Migration + def change + create_table :news do |t| + t.string :title + t.text :body + t.datetime :published_at + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 19e8aad2..4400b386 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150301191940) do +ActiveRecord::Schema.define(version: 20150302192318) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -24,6 +24,14 @@ t.datetime "updated_at", null: false end + create_table "news", force: :cascade do |t| + t.string "title" + t.text "body" + t.datetime "published_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" diff --git a/test/factories/news.rb b/test/factories/news.rb new file mode 100644 index 00000000..c5d9fbf6 --- /dev/null +++ b/test/factories/news.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :news do + title "MyString" +body "MyText" +published_at "" + end + +end diff --git a/test/models/news_test.rb b/test/models/news_test.rb new file mode 100644 index 00000000..a2ec1c0a --- /dev/null +++ b/test/models/news_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class NewsTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From f4799815c472cea3a0faa787cdf9e555c0a201b5 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 00:46:39 +0300 Subject: [PATCH 058/227] add gem carrierwave for uploaders --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 94fafb70..2ae1583e 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'simple_form' gem 'bootstrap-sass' gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' gem 'draper' +gem 'carrierwave' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' From 75311f2f05311d54f87acdf4d496f41bbbc84b21 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 00:48:38 +0300 Subject: [PATCH 059/227] Update .gitignore file --- .gitignore | 1 + Gemfile.lock | 327 --------------------------------------------------- 2 files changed, 1 insertion(+), 327 deletions(-) delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index d562746a..ef24e845 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ config/secrets.yml config/oauth.yml ulmic_test +Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 0b5e4b99..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,327 +0,0 @@ -GIT - remote: git://github.com/rails/actionform.git - revision: 41ec958375ce6eb0df45a7623541dcd72aea773e - ref: 41ec958 - specs: - active_form (0.0.1) - rails (~> 4.1) - -GIT - remote: git://github.com/ulmic/tconsole.git - revision: 73d91f7089f421a12193cc23d5d93c6cf51ab00b - branch: rails4 - specs: - tconsole (2.0.0.pre) - ansi - chattyproc (~> 1.0.0) - term-ansicolor (~> 1.0.7) - -GIT - remote: https://github.com/seuros/state_machine.git - revision: b5baef856b170f18dc2fdc36def2411c255335b9 - specs: - state_machine (1.2.0) - -GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.2.0) - actionpack (= 4.2.0) - actionview (= 4.2.0) - activejob (= 4.2.0) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.0) - actionview (= 4.2.0) - activesupport (= 4.2.0) - rack (~> 1.6.0) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - actionview (4.2.0) - activesupport (= 4.2.0) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - activejob (4.2.0) - activesupport (= 4.2.0) - globalid (>= 0.3.0) - activemodel (4.2.0) - activesupport (= 4.2.0) - builder (~> 3.1) - activerecord (4.2.0) - activemodel (= 4.2.0) - activesupport (= 4.2.0) - arel (~> 6.0) - activesupport (4.2.0) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - ansi (1.5.0) - arel (6.0.0) - authority (3.0.0) - activesupport (>= 3.0.0) - rake (>= 0.8.7) - autoprefixer-rails (5.1.7) - execjs - json - bcrypt (3.1.10) - binding_of_caller (0.7.2) - debug_inspector (>= 0.0.1) - bootstrap-sass (3.3.3) - autoprefixer-rails (>= 5.0.0.1) - sass (>= 3.2.19) - builder (3.2.2) - byebug (3.5.1) - columnize (~> 0.8) - debugger-linecache (~> 1.2) - slop (~> 3.6) - chattyproc (1.0.0) - coderay (1.1.0) - coffee-rails (4.1.0) - coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) - coffee-script (2.3.0) - coffee-script-source - execjs - coffee-script-source (1.9.1) - columnize (0.9.0) - coveralls (0.7.1) - multi_json (~> 1.3) - rest-client - simplecov (>= 0.7) - term-ansicolor - thor - debug_inspector (0.0.2) - debugger-linecache (1.2.0) - docile (1.1.5) - draper (1.4.0) - actionpack (>= 3.0) - activemodel (>= 3.0) - activesupport (>= 3.0) - request_store (~> 1.0) - enumerize (0.9.0) - activesupport (>= 3.2) - erubis (2.7.0) - execjs (2.3.0) - factory_girl (4.5.0) - activesupport (>= 3.0.0) - factory_girl_rails (4.5.0) - factory_girl (~> 4.5.0) - railties (>= 3.0.0) - faraday (0.9.0) - multipart-post (>= 1.2, < 3) - globalid (0.3.3) - activesupport (>= 4.1.0) - haml (4.0.6) - tilt - haml-rails (0.8.2) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - haml (>= 3.1, < 5.0) - html2haml (>= 1.0.1) - railties (>= 4.0.1) - hashie (3.3.2) - hike (1.2.3) - html2haml (2.0.0) - erubis (~> 2.7.0) - haml (~> 4.0.0) - nokogiri (~> 1.6.0) - ruby_parser (~> 3.5) - i18n (0.7.0) - jbuilder (2.2.7) - activesupport (>= 3.0.0, < 5) - multi_json (~> 1.2) - jquery-rails (4.0.3) - rails-dom-testing (~> 1.0) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - json (1.8.2) - jwt (1.2.0) - kgio (2.9.3) - loofah (2.0.1) - nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) - method_source (0.8.2) - mime-types (2.4.3) - mini_portile (0.6.2) - minitest (5.5.1) - multi_json (1.10.1) - multi_xml (0.5.5) - multipart-post (2.0.0) - netrc (0.10.2) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) - oauth (0.4.7) - oauth2 (1.0.0) - faraday (>= 0.8, < 0.10) - jwt (~> 1.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (~> 1.2) - omniauth (1.2.2) - hashie (>= 1.2, < 4) - rack (~> 1.0) - omniauth-facebook (2.0.0) - omniauth-oauth2 (~> 1.2) - omniauth-google-oauth2 (0.2.6) - omniauth (> 1.0) - omniauth-oauth2 (~> 1.1) - omniauth-oauth (1.0.1) - oauth - omniauth (~> 1.0) - omniauth-oauth2 (1.2.0) - faraday (>= 0.8, < 0.10) - multi_json (~> 1.3) - oauth2 (~> 1.0) - omniauth (~> 1.2) - omniauth-twitter (1.1.0) - multi_json (~> 1.3) - omniauth-oauth (~> 1.0) - omniauth-vkontakte (1.3.3) - multi_json - omniauth (~> 1.0) - omniauth-oauth2 (~> 1.1) - pg (0.18.1) - pry (0.10.1) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - pry-byebug (3.0.1) - byebug (~> 3.4) - pry (~> 0.10) - pry-rails (0.3.3) - pry (>= 0.9.10) - rack (1.6.0) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.0) - actionmailer (= 4.2.0) - actionpack (= 4.2.0) - actionview (= 4.2.0) - activejob (= 4.2.0) - activemodel (= 4.2.0) - activerecord (= 4.2.0) - activesupport (= 4.2.0) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.0) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.5) - activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.1) - loofah (~> 2.0) - railties (4.2.0) - actionpack (= 4.2.0) - activesupport (= 4.2.0) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - raindrops (0.13.0) - rake (10.4.2) - rdoc (4.2.0) - json (~> 1.4) - request_store (1.1.0) - rest-client (1.7.3) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) - ruby_parser (3.6.4) - sexp_processor (~> 4.1) - sass (3.4.12) - sass-rails (5.0.1) - railties (>= 4.0.0, < 5.0) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (~> 1.1) - sdoc (0.4.1) - json (~> 1.7, >= 1.7.7) - rdoc (~> 4.0) - sexp_processor (4.4.5) - simple_form (3.1.0) - actionpack (~> 4.0) - activemodel (~> 4.0) - simplecov (0.9.2) - docile (~> 1.1.0) - multi_json (~> 1.0) - simplecov-html (~> 0.9.0) - simplecov-html (0.9.0) - slop (3.6.0) - spring (1.3.2) - sprockets (2.12.3) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.2.4) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) - sqlite3 (1.3.10) - term-ansicolor (1.0.7) - thor (0.19.1) - thread_safe (0.3.4) - tilt (1.4.1) - turbolinks (2.5.3) - coffee-rails - tzinfo (1.2.2) - thread_safe (~> 0.1) - uglifier (2.7.0) - execjs (>= 0.3.0) - json (>= 1.8.0) - unicorn (4.8.3) - kgio (~> 2.6) - rack - raindrops (~> 0.7) - unicorn-rails (2.2.0) - rack - unicorn - web-console (2.0.0) - activemodel (~> 4.0) - binding_of_caller (>= 0.7.2) - railties (~> 4.0) - sprockets-rails (>= 2.0, < 4.0) - -PLATFORMS - ruby - -DEPENDENCIES - active_form! - authority - bcrypt (~> 3.1.7) - bootstrap-sass - byebug - coffee-rails (~> 4.1.0) - coveralls - draper - enumerize - factory_girl_rails - haml-rails - jbuilder (~> 2.0) - jquery-rails - omniauth-facebook - omniauth-google-oauth2 - omniauth-twitter - omniauth-vkontakte - pg - pry-byebug - pry-rails - rails (= 4.2.0) - sass-rails (~> 5.0) - sdoc (~> 0.4.0) - simple_form - simplecov - spring - sqlite3 - state_machine! - tconsole! - turbolinks - uglifier (>= 1.3.0) - unicorn-rails - web-console (~> 2.0) From 403227152975246cc4b87519f103eaad75ce1f85 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 01:07:21 +0300 Subject: [PATCH 060/227] NewsController inherited from Web::ApplicationController --- app/controllers/web/news_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index 428c6d0c..61d6683d 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -1,4 +1,4 @@ -class Web::NewsController < ApplicationController +class Web::NewsController < Web::ApplicationController def index end From 2a99e0f4357e3a104a4726bf97d8c30d8e57394d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 01:08:17 +0300 Subject: [PATCH 061/227] create News model --- app/models/news.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/models/news.rb b/app/models/news.rb index 75904dae..2b024517 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -1,2 +1,15 @@ class News < ActiveRecord::Base + mount_uploader :photo, PhotoUploader + validates :title, presence: true + validates :body, presence: true + validates :published_at, presence: true + validates :photo, presence: true + validates :author_id, presence: true + + include NewsRepository + + def is_published? + published_at <= DateTime.now + end + end From 4a178bc2ad56b9497c8a8cf0970be044184000ff Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 01:17:09 +0300 Subject: [PATCH 062/227] Add author_id to news model --- db/migrate/20150302204237_add_author_idto_news.rb | 4 ++++ db/schema.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150302204237_add_author_idto_news.rb diff --git a/db/migrate/20150302204237_add_author_idto_news.rb b/db/migrate/20150302204237_add_author_idto_news.rb new file mode 100644 index 00000000..644666bf --- /dev/null +++ b/db/migrate/20150302204237_add_author_idto_news.rb @@ -0,0 +1,4 @@ +class AddAuthorIdtoNews < ActiveRecord::Migration + def change + end +end diff --git a/db/schema.rb b/db/schema.rb index 4400b386..b8b8c673 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150302192318) do +ActiveRecord::Schema.define(version: 20150302204237) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From a4eda114cebbc0292385c95e4eafa9a4d423e097 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 01:18:29 +0300 Subject: [PATCH 063/227] Add some uploaders --- app/uploaders/application_uploader.rb | 10 ++++++++++ app/uploaders/image_defaults.rb | 12 ++++++++++++ app/uploaders/photo_uploader.rb | 22 ++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 app/uploaders/application_uploader.rb create mode 100644 app/uploaders/image_defaults.rb create mode 100644 app/uploaders/photo_uploader.rb diff --git a/app/uploaders/application_uploader.rb b/app/uploaders/application_uploader.rb new file mode 100644 index 00000000..1ddcf362 --- /dev/null +++ b/app/uploaders/application_uploader.rb @@ -0,0 +1,10 @@ +class ApplicationUploader < CarrierWave::Uploader::Base + # Choose what kind of storage to use for this uploader: + storage :file + + # 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 + "system/uploads/#{model.class.model_name.to_s.underscore}/#{mounted_as}/#{model.id}" + end +end diff --git a/app/uploaders/image_defaults.rb b/app/uploaders/image_defaults.rb new file mode 100644 index 00000000..face924d --- /dev/null +++ b/app/uploaders/image_defaults.rb @@ -0,0 +1,12 @@ +module ImageDefaults + include CarrierWave::MiniMagick + + def default_url + "/images/fallback/#{model.class.model_name.to_s.underscore}/" << [mounted_as, version_name].compact.join("_") << ".gif" + end + + def extension_white_list + %w(jpg jpeg gif png) + end + +end diff --git a/app/uploaders/photo_uploader.rb b/app/uploaders/photo_uploader.rb new file mode 100644 index 00000000..861d3f02 --- /dev/null +++ b/app/uploaders/photo_uploader.rb @@ -0,0 +1,22 @@ +# encoding: utf-8 + +class PhotoUploader < ApplicationUploader +include ImageDefaults + + def default_url + ActionController::Base.helpers.asset_path("images/default_news_photo.png") + end + + version :mediun do + process :resize_to_fill => [400, 400] + end + + version :mobile_thumb do + process :resize_to_fill => [225, 150] + end + + version :small do + process :resize_to_fill => [100, 100] + end + +end From a74da383a4757280b8fb1e24b7c056ca288404b9 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 2 Mar 2015 00:17:18 +0300 Subject: [PATCH 064/227] add members --- app/assets/javascripts/web/members.coffee | 3 +++ app/assets/stylesheets/web/members.scss | 3 +++ app/controllers/web/members_controller.rb | 19 +++++++++++++ app/decorators/web/member_decorator.rb | 13 +++++++++ app/forms/member_form.rb | 5 ++++ app/helpers/application_helper.rb | 2 ++ app/helpers/web/members_helper.rb | 2 ++ app/models/member.rb | 17 ++++++++++++ app/models/user.rb | 4 +++ app/views/layouts/application.html.haml | 16 +++++------ app/views/web/members/new.html.haml | 15 +++++++++++ config/application.rb | 3 +++ config/routes.rb | 1 + db/migrate/20150301204107_create_members.rb | 19 +++++++++++++ db/schema.rb | 18 ++++++++++++- lib/validators/human_name_validator.rb | 9 +++++++ lib/validators/phone_validator.rb | 8 ++++++ .../web/members_controller_test.rb | 27 +++++++++++++++++++ test/decorators/web/member_decorator_test.rb | 4 +++ test/factories/members.rb | 16 +++++++++++ test/models/member_test.rb | 7 +++++ 21 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/web/members.coffee create mode 100644 app/assets/stylesheets/web/members.scss create mode 100644 app/controllers/web/members_controller.rb create mode 100644 app/decorators/web/member_decorator.rb create mode 100644 app/forms/member_form.rb create mode 100644 app/helpers/web/members_helper.rb create mode 100644 app/models/member.rb create mode 100644 app/views/web/members/new.html.haml create mode 100644 db/migrate/20150301204107_create_members.rb create mode 100644 lib/validators/human_name_validator.rb create mode 100644 lib/validators/phone_validator.rb create mode 100644 test/controllers/web/members_controller_test.rb create mode 100644 test/decorators/web/member_decorator_test.rb create mode 100644 test/factories/members.rb create mode 100644 test/models/member_test.rb diff --git a/app/assets/javascripts/web/members.coffee b/app/assets/javascripts/web/members.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/members.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/web/members.scss b/app/assets/stylesheets/web/members.scss new file mode 100644 index 00000000..3b4b9f8a --- /dev/null +++ b/app/assets/stylesheets/web/members.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/members 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/web/members_controller.rb b/app/controllers/web/members_controller.rb new file mode 100644 index 00000000..a7d4af9a --- /dev/null +++ b/app/controllers/web/members_controller.rb @@ -0,0 +1,19 @@ +class Web::MembersController < Web::ApplicationController + before_filter :authenticate_user!, only: [ :new, :create ] + + def new + @member = Member.new + @member_form = MemberForm.new @member + end + + def create + @member = Member.new + @member_form = MemberForm.new @member + @member_form.submit params[:member] + if @member_form.save + redirect_to root_path + else + render action: :new + end + end +end diff --git a/app/decorators/web/member_decorator.rb b/app/decorators/web/member_decorator.rb new file mode 100644 index 00000000..5ebde151 --- /dev/null +++ b/app/decorators/web/member_decorator.rb @@ -0,0 +1,13 @@ +class Web::MemberDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb new file mode 100644 index 00000000..4b1c822f --- /dev/null +++ b/app/forms/member_form.rb @@ -0,0 +1,5 @@ +class MemberForm < ActiveForm::Base + self.main_model = :member + + attributes :patronymic, :user_id, :motto, :ticket_number, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b13b1f41..82ebedc7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,6 @@ module ApplicationHelper + include Concerns::AuthManagment + def menu_item(name = nil, path = '#', *args, &block) path = name || path if block_given? options = args.extract_options! diff --git a/app/helpers/web/members_helper.rb b/app/helpers/web/members_helper.rb new file mode 100644 index 00000000..34ffaf44 --- /dev/null +++ b/app/helpers/web/members_helper.rb @@ -0,0 +1,2 @@ +module Web::MembersHelper +end diff --git a/app/models/member.rb b/app/models/member.rb new file mode 100644 index 00000000..ce24e0cc --- /dev/null +++ b/app/models/member.rb @@ -0,0 +1,17 @@ +class Member < ActiveRecord::Base + belongs_to :user + belongs_to :parent, class_name: 'Member' + + validates :patronymic, presence: true, + human_name: true + validates :motto, presence: true + validates :ticket_number, presence: true, + uniqueness: true + validates :mobile_phone, presence: true, + phone: true + validates :birth_date, presence: true + validates :home_adress, presence: true + validates :municipality, presence: true + validates :locality, presence: true + validates :avatar, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index e1ece55e..707eb29c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,6 +4,10 @@ class User < ActiveRecord::Base has_many :authentications validates :email, uniqueness: true + validates :first_name, human_name: true, + allow_blank: true + validates :last_name, human_name: true, + allow_blank: true extend Enumerize enumerize :role, in: [ :user, :admin ], default: :user diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index c5d7b36e..660d4c54 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,9 +1,9 @@ %html -%head - %title - Ulmicru - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true - = javascript_include_tag 'application', 'data-turbolinks-track' => true - = csrf_meta_tags -%body - = yield + %head + %title + Ulmicru + = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true + = javascript_include_tag 'application', 'data-turbolinks-track' => true + = csrf_meta_tags + %body + = yield diff --git a/app/views/web/members/new.html.haml b/app/views/web/members/new.html.haml new file mode 100644 index 00000000..74e3de66 --- /dev/null +++ b/app/views/web/members/new.html.haml @@ -0,0 +1,15 @@ += simple_form_for @member, url: { controller: 'web/members', action: :create } do |f| + - f.simple_fields_for :user do |ff| + = ff.input :first_name, as: :string + = ff.input :last_name, as: :string + = f.input :patronymic, as: :string + = f.input :motto, as: :string + = f.input :ticket_number, as: :string + = f.input :mobile_phone, as: :string + = f.input :birth_date, as: :string + = f.input :home_adress, as: :string + = f.input :municipality, as: :string + = f.input :locality, as: :string + = f.input :avatar, as: :string + = f.input :user_id, as: :hidden, input_html: { value: current_user.id } + = f.button :submit, t('.register') diff --git a/config/application.rb b/config/application.rb index f8b08388..c039111c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -8,6 +8,9 @@ module Ulmicru class Application < Rails::Application + config.autoload_paths += Dir[ + "#{config.root}/lib/**/" + ] config.active_record.raise_in_transactional_callbacks = true config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] config.i18n.available_locales = :ru diff --git a/config/routes.rb b/config/routes.rb index 48963efe..7fc17e1f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,6 +7,7 @@ scope module: :web do resource :session, only: [:new, :create, :destroy] resources :users, only: [ :new, :create ] + resources :members, only: [ :new, :create ] namespace :admin do root to: 'welcome#index' resources :users diff --git a/db/migrate/20150301204107_create_members.rb b/db/migrate/20150301204107_create_members.rb new file mode 100644 index 00000000..fd7d75ab --- /dev/null +++ b/db/migrate/20150301204107_create_members.rb @@ -0,0 +1,19 @@ +class CreateMembers < ActiveRecord::Migration + def change + create_table :members do |t| + t.text :patronymic + t.integer :user_id + t.text :motto + t.integer :ticket_number + t.integer :parent_id + t.text :mobile_phone + t.datetime :birth_date + t.text :home_adress + t.text :municipality + t.text :locality + t.text :avatar + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 19e8aad2..67ab9e9e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150301191940) do +ActiveRecord::Schema.define(version: 20150301204107) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -24,6 +24,22 @@ t.datetime "updated_at", null: false end + create_table "members", force: :cascade do |t| + t.text "patronymic" + t.integer "user_id" + t.text "motto" + t.integer "ticket_number" + t.integer "parent_id" + t.text "mobile_phone" + t.datetime "birth_date" + t.text "home_adress" + t.text "municipality" + t.text "locality" + t.text "avatar" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" diff --git a/lib/validators/human_name_validator.rb b/lib/validators/human_name_validator.rb new file mode 100644 index 00000000..aedc3155 --- /dev/null +++ b/lib/validators/human_name_validator.rb @@ -0,0 +1,9 @@ +# coding: utf-8 + +class HumanNameValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unless value =~ /^[a-zA-Zа-яА-ЯёЁ][a-zA-Zа-яА-ЯёЁ '`-]{0,253}[a-zA-Zа-яА-ЯёЁ]{0,1}$/ + record.errors.add(attribute, :human_name, options.merge(value: value)) + end + end +end diff --git a/lib/validators/phone_validator.rb b/lib/validators/phone_validator.rb new file mode 100644 index 00000000..cc8112fa --- /dev/null +++ b/lib/validators/phone_validator.rb @@ -0,0 +1,8 @@ +class PhoneValidator < ActiveModel::EachValidator + + def validate_each(record, attribute, value) + unless value =~ /^\+\d{,3}[\s\(-]*\d{3}[\s\)-]*[\d\s-]*$/ + record.errors.add(attribute, :phone, options.merge(value: value)) + end + end +end diff --git a/test/controllers/web/members_controller_test.rb b/test/controllers/web/members_controller_test.rb new file mode 100644 index 00000000..ff51bcc2 --- /dev/null +++ b/test/controllers/web/members_controller_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class Web::MembersControllerTest < ActionController::TestCase + setup do + @member = create :member + sign_in @member.user + end + + test 'should not get new unsigned' do + sign_out + get :new + assert_response :redirect, @response.body + end + + test 'should get new' do + get :new + assert_response :success, @response.body + end + + test 'should create member' do + attributes = attributes_for :member + post :create, member: attributes + assert_response :redirect, @response.body + assert_redirected_to root_path + assert_equal attributes[:patronymic], Member.last.patronymic + end +end diff --git a/test/decorators/web/member_decorator_test.rb b/test/decorators/web/member_decorator_test.rb new file mode 100644 index 00000000..a7c42c4a --- /dev/null +++ b/test/decorators/web/member_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Web::MemberDecoratorTest < Draper::TestCase +end diff --git a/test/factories/members.rb b/test/factories/members.rb new file mode 100644 index 00000000..26eec5e8 --- /dev/null +++ b/test/factories/members.rb @@ -0,0 +1,16 @@ +FactoryGirl.define do + factory :member do + patronymic { generate :human_name } + association :user + user_id { User.last ? User.last.id : 1 } + motto { generate :string } + ticket_number { generate :integer } + parent_id 1 + mobile_phone { generate :phone } + birth_date { generate :date } + home_adress { generate :string } + municipality { generate :string } + locality { generate :string } + avatar { generate :string } + end +end diff --git a/test/models/member_test.rb b/test/models/member_test.rb new file mode 100644 index 00000000..7c0e92ca --- /dev/null +++ b/test/models/member_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class MemberTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From b727b55387f197b3d648d3ec7ac80a7fe7fff220 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 2 Mar 2015 00:32:15 +0300 Subject: [PATCH 065/227] add admin/member controller --- .../javascripts/web/admin/members.coffee | 3 ++ app/assets/stylesheets/web/admin/members.scss | 3 ++ .../web/admin/members_controller.rb | 43 +++++++++++++++++++ app/decorators/member_decorator.rb | 13 ++++++ app/decorators/web/admin/member_decorator.rb | 13 ++++++ app/helpers/web/admin/members_helper.rb | 2 + app/models/member.rb | 20 +++++++++ app/views/web/admin/members/_form.html.haml | 20 +++++++++ app/views/web/admin/members/edit.html.haml | 1 + app/views/web/admin/members/index.html.haml | 28 ++++++++++++ app/views/web/admin/members/new.html.haml | 1 + config/locales/ru/models.yml | 1 + config/routes.rb | 1 + .../20150301213120_add_state_to_member.rb | 5 +++ db/schema.rb | 3 +- lib/yaml/russian_cases.yml | 1 + .../web/admin/members_controller_test.rb | 41 ++++++++++++++++++ test/decorators/member_decorator_test.rb | 4 ++ .../web/admin/member_decorator_test.rb | 4 ++ 19 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/web/admin/members.coffee create mode 100644 app/assets/stylesheets/web/admin/members.scss create mode 100644 app/controllers/web/admin/members_controller.rb create mode 100644 app/decorators/member_decorator.rb create mode 100644 app/decorators/web/admin/member_decorator.rb create mode 100644 app/helpers/web/admin/members_helper.rb create mode 100644 app/views/web/admin/members/_form.html.haml create mode 100644 app/views/web/admin/members/edit.html.haml create mode 100644 app/views/web/admin/members/index.html.haml create mode 100644 app/views/web/admin/members/new.html.haml create mode 100644 db/migrate/20150301213120_add_state_to_member.rb create mode 100644 test/controllers/web/admin/members_controller_test.rb create mode 100644 test/decorators/member_decorator_test.rb create mode 100644 test/decorators/web/admin/member_decorator_test.rb diff --git a/app/assets/javascripts/web/admin/members.coffee b/app/assets/javascripts/web/admin/members.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/admin/members.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/web/admin/members.scss b/app/assets/stylesheets/web/admin/members.scss new file mode 100644 index 00000000..9ad4ec4e --- /dev/null +++ b/app/assets/stylesheets/web/admin/members.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/admin/members 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/web/admin/members_controller.rb b/app/controllers/web/admin/members_controller.rb new file mode 100644 index 00000000..43d09d2b --- /dev/null +++ b/app/controllers/web/admin/members_controller.rb @@ -0,0 +1,43 @@ +class Web::Admin::MembersController < Web::Admin::ApplicationController + def index + @members = Member.all + end + + def new + @member = Member.new + @member_form = MemberForm.new(@member) + end + + def edit + @member = Member.find params[:id] + @member_form = MemberForm.new(@member) + end + + def create + @member = Member.new + @member_form = MemberForm.new(@member) + @member_form.submit(params[:member]) + if @member_form.save + redirect_to admin_members_path + else + render action: :new + end + end + + def update + @member = Member.find params[:id] + @member_form = MemberForm.new(@member) + @member_form.submit(params[:member]) + if @member_form.save + redirect_to admin_members_path + else + render action: :edit + end + end + + def destroy + @member = Member.find params[:id] + @member.remove + redirect_to admin_members_path + end +end diff --git a/app/decorators/member_decorator.rb b/app/decorators/member_decorator.rb new file mode 100644 index 00000000..5c482c60 --- /dev/null +++ b/app/decorators/member_decorator.rb @@ -0,0 +1,13 @@ +class MemberDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/decorators/web/admin/member_decorator.rb b/app/decorators/web/admin/member_decorator.rb new file mode 100644 index 00000000..989e7c86 --- /dev/null +++ b/app/decorators/web/admin/member_decorator.rb @@ -0,0 +1,13 @@ +class Web::Admin::MemberDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/helpers/web/admin/members_helper.rb b/app/helpers/web/admin/members_helper.rb new file mode 100644 index 00000000..31c11c8f --- /dev/null +++ b/app/helpers/web/admin/members_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::MembersHelper +end diff --git a/app/models/member.rb b/app/models/member.rb index ce24e0cc..2284609b 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -14,4 +14,24 @@ class Member < ActiveRecord::Base validates :municipality, presence: true validates :locality, presence: true validates :avatar, presence: true + + state_machine :state, initial: :not_confirmed do + state :not_confirmed + state :confirmed + state :declined + state :removed + + event :confirm do + transition all => :confirmed + end + event :decline do + transition all => :declined + end + event :remove do + transition all => :removed + end + event :restore do + transition removed: :not_confirmed + end + end end diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml new file mode 100644 index 00000000..22f978d4 --- /dev/null +++ b/app/views/web/admin/members/_form.html.haml @@ -0,0 +1,20 @@ +.page-header + %h1= page_title(action, Member.model_name.human) +.row + .col-lg-6 + = simple_form_for @member, url: { controller: 'web/admin/members', action: action }, input_html: { class: 'form-horizontal' } do |f| + - f.simple_fields_for :user do |ff| + = ff.input :first_name, as: :string + = ff.input :last_name, as: :string + = f.input :patronymic, as: :string + = f.input :motto, as: :string + = f.input :ticket_number, as: :string + = f.input :mobile_phone, as: :string + = f.input :birth_date, as: :string + = f.input :home_adress, as: :string + = f.input :municipality, as: :string + = f.input :locality, as: :string + = f.input :avatar, as: :string + = f.association :user, label_value: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id + = f.button :submit, t('.register') + = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/app/views/web/admin/members/edit.html.haml b/app/views/web/admin/members/edit.html.haml new file mode 100644 index 00000000..51cb957a --- /dev/null +++ b/app/views/web/admin/members/edit.html.haml @@ -0,0 +1 @@ += render partial: 'form', locals: { action: :update } diff --git a/app/views/web/admin/members/index.html.haml b/app/views/web/admin/members/index.html.haml new file mode 100644 index 00000000..b6aa6dff --- /dev/null +++ b/app/views/web/admin/members/index.html.haml @@ -0,0 +1,28 @@ += javascript_include_tag :tabs += stylesheet_link_tag :tabs + +- model_class = Member +.page-header + %h1= model_class.model_name.human.pluralize(:ru) +%table.table.table-condensed.table-hover + %thead + %tr + %th= model_class.human_attribute_name(:id) + %th= model_class.human_attribute_name(:avatar) + %th= model_class.human_attribute_name(:full_name) + %th= model_class.human_attribute_name(:ticket_number) + %th= model_class.human_attribute_name(:place) + %th= t 'helpers.links.actions' + %tbody + - @members.each do |member| + %tr.link{ data: { href: edit_admin_member_path(member) } } + %td= member.id + %td= member.avatar + %td= member.full_name + %td= member.ticket_number + %td= member.place + %td + = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' + = link_to t('helpers.links.destroy'), admin_member_path(member), method: :delete, class: 'btn btn-xs btn-danger' + += link_to t('.new', default: t('helpers.links.new')), new_admin_member_path, class: 'btn btn-primary' diff --git a/app/views/web/admin/members/new.html.haml b/app/views/web/admin/members/new.html.haml new file mode 100644 index 00000000..02017918 --- /dev/null +++ b/app/views/web/admin/members/new.html.haml @@ -0,0 +1 @@ += render partial: 'form', locals: { action: :create } diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 6454f310..fdba543f 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -2,6 +2,7 @@ ru: activerecord: models: user: Пользователь + member: Член организации attributes: user: first_name: Имя diff --git a/config/routes.rb b/config/routes.rb index 7fc17e1f..fea5a7f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,7 @@ namespace :admin do root to: 'welcome#index' resources :users + resources :members resources :trash, only: [] do collection do get 'index/:type' => 'trash#index', as: :type diff --git a/db/migrate/20150301213120_add_state_to_member.rb b/db/migrate/20150301213120_add_state_to_member.rb new file mode 100644 index 00000000..00483ca9 --- /dev/null +++ b/db/migrate/20150301213120_add_state_to_member.rb @@ -0,0 +1,5 @@ +class AddStateToMember < ActiveRecord::Migration + def change + add_column :members, :state, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 67ab9e9e..25b526cf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150301204107) do +ActiveRecord::Schema.define(version: 20150301213120) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -38,6 +38,7 @@ t.text "avatar" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "state" end create_table "users", force: :cascade do |t| diff --git a/lib/yaml/russian_cases.yml b/lib/yaml/russian_cases.yml index 256cc0e1..469f3dd4 100644 --- a/lib/yaml/russian_cases.yml +++ b/lib/yaml/russian_cases.yml @@ -1,3 +1,4 @@ cases: genitive: 'пользователь': пользователя + 'член организации': члена организации diff --git a/test/controllers/web/admin/members_controller_test.rb b/test/controllers/web/admin/members_controller_test.rb new file mode 100644 index 00000000..5899bb66 --- /dev/null +++ b/test/controllers/web/admin/members_controller_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' + +class Web::Admin::MembersControllerTest < ActionController::TestCase + setup do + @member = create :member + end + + test 'should get new' do + get :new + assert_response :success, @response.body + end + + test 'should create member' do + attributes = attributes_for :member + post :create, member: attributes + assert_response :redirect, @response.body + assert_redirected_to admin_members_path + assert_equal attributes[:patronymic], Member.last.patronymic + end + + test 'should get edit' do + get :edit, id: @member + assert_response :success, @response.body + end + + test 'should patch update' do + attributes = attributes_for :member + patch :update, member: attributes, id: @member + assert_response :redirect, @response.body + assert_redirected_to admin_members_path + @member.reload + assert_equal attributes[:patronymic], @member.patronymic + end + + test 'should delete destroy' do + count = Member.count + delete :destroy, id: @member + @member.reload + assert @member.removed? + end +end diff --git a/test/decorators/member_decorator_test.rb b/test/decorators/member_decorator_test.rb new file mode 100644 index 00000000..c7ba3704 --- /dev/null +++ b/test/decorators/member_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class MemberDecoratorTest < Draper::TestCase +end diff --git a/test/decorators/web/admin/member_decorator_test.rb b/test/decorators/web/admin/member_decorator_test.rb new file mode 100644 index 00000000..258e3626 --- /dev/null +++ b/test/decorators/web/admin/member_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Web::Admin::MemberDecoratorTest < Draper::TestCase +end From 759887e3e39eb8c96cc3dbfadba47d86fc0759b3 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 2 Mar 2015 00:36:04 +0300 Subject: [PATCH 066/227] add inflections for members --- app/views/layouts/web/admin/_navbar.html.haml | 2 ++ config/initializers/inflections.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 09ab6c96..568334ef 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -10,6 +10,8 @@ %ul.nav.navbar-nav = menu_item admin_users_path do = User.model_name.human.pluralize(:ru) + = menu_item admin_members_path do + = Member.model_name.human.pluralize(:ru) %ul.nav.navbar-nav.navbar-right = menu_item '/admin/trash/index/user' do %span.glyphicon.glyphicon-trash diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 56bb147d..389274cd 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -5,4 +5,5 @@ # locales as you wish. All of these examples are active by default: ActiveSupport::Inflector.inflections(:ru) do |inflect| inflect.plural /ль$/i, 'ли' + inflect.plural /н /i, 'ны ' end From 002cd295965b1bde9d652dc4a58fcdd5f87e305d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 2 Mar 2015 00:40:48 +0300 Subject: [PATCH 067/227] add locales --- app/views/web/admin/members/_form.html.haml | 2 +- config/locales/ru/models.yml | 13 +++++++++++++ config/locales/ru/ru.yml | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index 22f978d4..148a79a6 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -16,5 +16,5 @@ = f.input :locality, as: :string = f.input :avatar, as: :string = f.association :user, label_value: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id - = f.button :submit, t('.register') + = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index fdba543f..741e900f 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -12,3 +12,16 @@ ru: email: E-mail password: Пароль password_confirmation: Подтверждение пароля + member: + avatar: Аватар + full_name: Ф.И.О. + ticket_number: Номер членского билета + place: Место проживания + patronymic: Отчество + motto: Девиз по жизни + mobile_phone: Мобильный телефон + birth_date: Дата рождения + home_adress: Домашний адрес + municipality: Муниципальное образование + locality: Населённый пункт + user: Прикреплённый Пользователь diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 3bc0aca0..573b5779 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -5,6 +5,7 @@ ru: enter: Войти actions: update: Редактировать + create: Создать links: actions: Действия edit: Изменить From 84c3cf32e23e08a753e53a9e3e22c376cd80e868 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 2 Mar 2015 22:36:00 +0300 Subject: [PATCH 068/227] remove turbolinks --- Gemfile | 2 -- Gemfile.lock | 3 --- 2 files changed, 5 deletions(-) diff --git a/Gemfile b/Gemfile index 5c99f311..6dd9b122 100644 --- a/Gemfile +++ b/Gemfile @@ -17,8 +17,6 @@ gem 'coffee-rails', '~> 4.1.0' # Use jquery as the JavaScript library gem 'jquery-rails' -# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks -gem 'turbolinks' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. diff --git a/Gemfile.lock b/Gemfile.lock index ab2949ad..a7ce4352 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -266,8 +266,6 @@ GEM thor (0.19.1) thread_safe (0.3.4) tilt (1.4.1) - turbolinks (2.5.3) - coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.0) @@ -312,6 +310,5 @@ DEPENDENCIES sqlite3 state_machine! tconsole! - turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) From d381ed9583b3449e2103e15e02c0896a93099a1c Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 02:32:39 +0300 Subject: [PATCH 069/227] Add mini_magick --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 2ae1583e..f8d071f0 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ gem 'bootstrap-sass' gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' gem 'draper' gem 'carrierwave' +gem 'mini_magick' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' From 9447707e77e211f579fb02eb386fbdf11bfe5f3c Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 02:33:15 +0300 Subject: [PATCH 070/227] delete repository --- app/models/news.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/news.rb b/app/models/news.rb index 2b024517..ee5e5b2f 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -6,8 +6,6 @@ class News < ActiveRecord::Base validates :photo, presence: true validates :author_id, presence: true - include NewsRepository - def is_published? published_at <= DateTime.now end From ec6c4d48ca909d9a83751259e09dce649b6f8c7c Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 02:33:25 +0300 Subject: [PATCH 071/227] Some tests --- db/schema.rb | 3 --- test/controllers/web/news_controller_test.rb | 16 +++++++++++++--- test/factories/news.rb | 8 ++++---- test/factories/sequences.rb | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index b8b8c673..20810440 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,9 +13,6 @@ ActiveRecord::Schema.define(version: 20150302204237) do - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" - create_table "authentications", force: :cascade do |t| t.text "provider" t.text "uid" diff --git a/test/controllers/web/news_controller_test.rb b/test/controllers/web/news_controller_test.rb index 1d10bbb1..9b8f2bf7 100644 --- a/test/controllers/web/news_controller_test.rb +++ b/test/controllers/web/news_controller_test.rb @@ -1,7 +1,17 @@ require 'test_helper' class Web::NewsControllerTest < ActionController::TestCase - # test "the truth" do - # assert true - # end + setup do + @news = create :news + end + + test "should get index" do + get :index + assert_response :success, @response.body + end + + test "should get show" do + get :show, id: @news + assert_response :success, @response.body + end end diff --git a/test/factories/news.rb b/test/factories/news.rb index c5d9fbf6..2bbd93d9 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -1,8 +1,8 @@ FactoryGirl.define do factory :news do - title "MyString" -body "MyText" -published_at "" + title { generate :string } + body { generate :string } + published_at { DateTime.now } + photo { generate :file } end - end diff --git a/test/factories/sequences.rb b/test/factories/sequences.rb index 3ee07c5d..cb6eff0d 100644 --- a/test/factories/sequences.rb +++ b/test/factories/sequences.rb @@ -23,7 +23,7 @@ Date.today + n.day end sequence :file do |n| - fixture_file_upload('app/assets/images/winners/0.png', 'image/png') + fixture_file_upload('app/assets/images/apps/logo-mic-square.png', 'image/png') end sequence :human_name do "Leopold" From 056f1b8be0bede7b5cfcfd907d2ae8f6459e616a Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 02:56:53 +0300 Subject: [PATCH 072/227] Add public/uploads dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ef24e845..84a2925d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ config/secrets.yml config/oauth.yml ulmic_test Gemfile.lock +public/uploads From 7cffcf7c03c3d63c564dcbb06c472081b90a5855 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 02:57:54 +0300 Subject: [PATCH 073/227] fix wrong migrations --- db/migrate/20150302204237_add_author_idto_news.rb | 4 ---- db/migrate/20150302234749_add_photo_to_news.rb | 5 +++++ db/migrate/20150302235007_add_author_id_to_news.rb | 5 +++++ db/schema.rb | 6 +++++- 4 files changed, 15 insertions(+), 5 deletions(-) delete mode 100644 db/migrate/20150302204237_add_author_idto_news.rb create mode 100644 db/migrate/20150302234749_add_photo_to_news.rb create mode 100644 db/migrate/20150302235007_add_author_id_to_news.rb diff --git a/db/migrate/20150302204237_add_author_idto_news.rb b/db/migrate/20150302204237_add_author_idto_news.rb deleted file mode 100644 index 644666bf..00000000 --- a/db/migrate/20150302204237_add_author_idto_news.rb +++ /dev/null @@ -1,4 +0,0 @@ -class AddAuthorIdtoNews < ActiveRecord::Migration - def change - end -end diff --git a/db/migrate/20150302234749_add_photo_to_news.rb b/db/migrate/20150302234749_add_photo_to_news.rb new file mode 100644 index 00000000..2ca771e8 --- /dev/null +++ b/db/migrate/20150302234749_add_photo_to_news.rb @@ -0,0 +1,5 @@ +class AddPhotoToNews < ActiveRecord::Migration + def change + add_column :news, :photo, :text + end +end diff --git a/db/migrate/20150302235007_add_author_id_to_news.rb b/db/migrate/20150302235007_add_author_id_to_news.rb new file mode 100644 index 00000000..b0bd3dcd --- /dev/null +++ b/db/migrate/20150302235007_add_author_id_to_news.rb @@ -0,0 +1,5 @@ +class AddAuthorIdToNews < ActiveRecord::Migration + def change + add_column :news, :author_id, :number + end +end diff --git a/db/schema.rb b/db/schema.rb index 20810440..7d560786 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150302204237) do +ActiveRecord::Schema.define(version: 20150302234749) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" create_table "authentications", force: :cascade do |t| t.text "provider" @@ -27,6 +30,7 @@ t.datetime "published_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "photo" end create_table "users", force: :cascade do |t| From 5eee11a154bf1d3007b59eb9a60d7e70df58de5d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 02:59:44 +0300 Subject: [PATCH 074/227] update db/schema.rb --- db/schema.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 7d560786..de22bdc6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,10 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150302234749) do - - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" +ActiveRecord::Schema.define(version: 20150302235007) do create_table "authentications", force: :cascade do |t| t.text "provider" @@ -31,6 +28,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "photo" + t.decimal "author_id" end create_table "users", force: :cascade do |t| From 2e982eb7fc965fc13089178fd817975720e94be5 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 03:16:21 +0300 Subject: [PATCH 075/227] Update locales config --- config/application.rb | 3 +-- config/environments/development.rb | 1 + config/environments/production.rb | 1 + config/environments/test.rb | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/application.rb b/config/application.rb index f8b08388..1805c7be 100644 --- a/config/application.rb +++ b/config/application.rb @@ -10,8 +10,7 @@ module Ulmicru class Application < Rails::Application config.active_record.raise_in_transactional_callbacks = true config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] - config.i18n.available_locales = :ru - config.i18n.default_locale = :ru + config.i18n.available_locales = [:en, :ru] config.assets.initialize_on_precompile = true # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers diff --git a/config/environments/development.rb b/config/environments/development.rb index b55e2144..5b7d2420 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -22,6 +22,7 @@ # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + config.i18n.default_locale = :en # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. diff --git a/config/environments/production.rb b/config/environments/production.rb index 5c1b32e4..81f0dc7b 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -74,6 +74,7 @@ # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new + config.i18n.default_locale = :ru # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index 1c19f08b..c0aade09 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -31,6 +31,7 @@ # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test + config.i18n.default_locale = :en # Randomize the order test cases are executed. config.active_support.test_order = :random From 20d3ba640494c305af90474418d0ab46939a8369 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 03:40:38 +0300 Subject: [PATCH 076/227] fix wrong factories --- test/factories/news.rb | 1 + test/factories/sequences.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/test/factories/news.rb b/test/factories/news.rb index 2bbd93d9..8793ef7e 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -4,5 +4,6 @@ body { generate :string } published_at { DateTime.now } photo { generate :file } + author_id { generate :number } end end diff --git a/test/factories/sequences.rb b/test/factories/sequences.rb index cb6eff0d..3e5cffb2 100644 --- a/test/factories/sequences.rb +++ b/test/factories/sequences.rb @@ -28,4 +28,7 @@ sequence :human_name do "Leopold" end + sequence :number do + Random.rand(2) + end end From bf253eae45e2d1001d900bbba7f9afa9781a140d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 03:46:00 +0300 Subject: [PATCH 077/227] fix gitignore file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 84a2925d..14477626 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ config/secrets.yml config/oauth.yml ulmic_test Gemfile.lock -public/uploads +/public From 6ec71823fc63225dc075eb9147823ebe5fa0e87e Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 03:46:35 +0300 Subject: [PATCH 078/227] fix wrong migration --- ..._to_news.rb => 20150303003907_add_author_id_to_news.rb} | 2 +- db/schema.rb | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) rename db/migrate/{20150302235007_add_author_id_to_news.rb => 20150303003907_add_author_id_to_news.rb} (62%) diff --git a/db/migrate/20150302235007_add_author_id_to_news.rb b/db/migrate/20150303003907_add_author_id_to_news.rb similarity index 62% rename from db/migrate/20150302235007_add_author_id_to_news.rb rename to db/migrate/20150303003907_add_author_id_to_news.rb index b0bd3dcd..00725db6 100644 --- a/db/migrate/20150302235007_add_author_id_to_news.rb +++ b/db/migrate/20150303003907_add_author_id_to_news.rb @@ -1,5 +1,5 @@ class AddAuthorIdToNews < ActiveRecord::Migration def change - add_column :news, :author_id, :number + add_column :news, :author_id, :integer end end diff --git a/db/schema.rb b/db/schema.rb index de22bdc6..15982847 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150302235007) do +ActiveRecord::Schema.define(version: 20150303003907) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" create_table "authentications", force: :cascade do |t| t.text "provider" @@ -28,7 +31,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "photo" - t.decimal "author_id" + t.integer "author_id" end create_table "users", force: :cascade do |t| From 8d81255a6633c9c0c5365133fa0d23f2a70cc9d9 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 3 Mar 2015 03:51:10 +0300 Subject: [PATCH 079/227] Index show all news --- app/controllers/web/news_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index 61d6683d..80de3f2c 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -1,12 +1,12 @@ class Web::NewsController < Web::ApplicationController def index - + @news = NewsDecorator.decorate_collection News.published.order('created_at DESC') end def show @news = News.find(params[:id]).decorate if !@news.is_published? - redirect_to not_found_errors_path +#FIXME there 404 error path end end end From b9554fbad3edb18f2ab2e6a723391506e8a0f4cb Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 02:23:06 +0300 Subject: [PATCH 080/227] remove turbolinks --- app/assets/javascripts/application.js | 1 - app/views/layouts/application.html.haml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4a2ae1d9..3e5bd214 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,5 +12,4 @@ // //= require jquery //= require jquery_ujs -//= require turbolinks //= require_tree . diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 660d4c54..8c6be3a4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -2,8 +2,8 @@ %head %title Ulmicru - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true - = javascript_include_tag 'application', 'data-turbolinks-track' => true + = stylesheet_link_tag 'application', media: 'all' + = javascript_include_tag 'application' = csrf_meta_tags %body = yield From fcdc90044f824f6fb6c6e566d2034506dffe34fd Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 02:43:05 +0300 Subject: [PATCH 081/227] fix form --- .../web/admin/members_controller.rb | 2 +- app/decorators/member_decorator.rb | 26 +++++++++++++------ app/forms/member_form.rb | 2 +- app/models/member.rb | 7 +++++ app/views/web/admin/members/_form.html.haml | 15 +++++++---- config/locales/ru/models.yml | 4 ++- 6 files changed, 40 insertions(+), 16 deletions(-) diff --git a/app/controllers/web/admin/members_controller.rb b/app/controllers/web/admin/members_controller.rb index 43d09d2b..88db91a5 100644 --- a/app/controllers/web/admin/members_controller.rb +++ b/app/controllers/web/admin/members_controller.rb @@ -1,6 +1,6 @@ class Web::Admin::MembersController < Web::Admin::ApplicationController def index - @members = Member.all + @members = ::MemberDecorator.decorate_collection Member.all end def new diff --git a/app/decorators/member_decorator.rb b/app/decorators/member_decorator.rb index 5c482c60..22411cc4 100644 --- a/app/decorators/member_decorator.rb +++ b/app/decorators/member_decorator.rb @@ -1,13 +1,23 @@ class MemberDecorator < Draper::Decorator delegate_all - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end + def first_name + user.first_name + end + def last_name + user.last_name + end + + def full_name + "#{first_name} #{patronymic} #{last_name}" + end + + def short_name + "#{first_name} #{last_name}" + end + + def place + "#{municipality}, #{locality}" + end end diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb index 4b1c822f..2d9c9afa 100644 --- a/app/forms/member_form.rb +++ b/app/forms/member_form.rb @@ -1,5 +1,5 @@ class MemberForm < ActiveForm::Base self.main_model = :member - attributes :patronymic, :user_id, :motto, :ticket_number, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar + attributes :first_name, :last_name, :patronymic, :user_id, :motto, :ticket_number, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar end diff --git a/app/models/member.rb b/app/models/member.rb index 2284609b..43dad8b7 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,4 +1,6 @@ class Member < ActiveRecord::Base + after_save :update_user_name + belongs_to :user belongs_to :parent, class_name: 'Member' @@ -34,4 +36,9 @@ class Member < ActiveRecord::Base transition removed: :not_confirmed end end + attr_accessor :first_name, :last_name + + def update_user_name + User.update user_id, first_name: first_name, last_name: last_name if user_id + end end diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index 148a79a6..b421e728 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -3,10 +3,15 @@ .row .col-lg-6 = simple_form_for @member, url: { controller: 'web/admin/members', action: action }, input_html: { class: 'form-horizontal' } do |f| - - f.simple_fields_for :user do |ff| - = ff.input :first_name, as: :string - = ff.input :last_name, as: :string + - if @member.user.present? + = f.input :first_name, as: :string, required: true, input_html: { value: @member.user.first_name } + - else + = f.input :first_name, as: :string, required: true = f.input :patronymic, as: :string + - if @member.user.present? + = f.input :last_name, as: :string, required: true, input_html: { value: @member.user.last_name } + - else + = f.input :last_name, as: :string, required: true = f.input :motto, as: :string = f.input :ticket_number, as: :string = f.input :mobile_phone, as: :string @@ -14,7 +19,7 @@ = f.input :home_adress, as: :string = f.input :municipality, as: :string = f.input :locality, as: :string - = f.input :avatar, as: :string - = f.association :user, label_value: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id + = f.input :avatar, as: :file + = f.association :user, label_method: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id, required: true = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 741e900f..8ff6fa1c 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -13,11 +13,13 @@ ru: password: Пароль password_confirmation: Подтверждение пароля member: + first_name: Имя + patronymic: Отчество + last_name: Фамилия avatar: Аватар full_name: Ф.И.О. ticket_number: Номер членского билета place: Место проживания - patronymic: Отчество motto: Девиз по жизни mobile_phone: Мобильный телефон birth_date: Дата рождения From a8395b39404b9b42a22bbc462d587ce68c9947e7 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 02:55:51 +0300 Subject: [PATCH 082/227] fix form --- Gemfile.lock | 26 +++++++++---------- .../web/admin/members_controller.rb | 2 +- app/controllers/web/admin/users_controller.rb | 2 +- app/decorators/member_decorator.rb | 4 +++ app/models/member.rb | 8 +++++- app/models/user.rb | 1 + app/views/web/admin/members/index.html.haml | 3 --- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a7ce4352..84899320 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,7 +103,7 @@ GEM activemodel (>= 3.0) activesupport (>= 3.0) request_store (~> 1.0) - enumerize (0.9.0) + enumerize (0.10.0) activesupport (>= 3.2) erubis (2.7.0) execjs (2.3.0) @@ -112,7 +112,7 @@ GEM factory_girl_rails (4.5.0) factory_girl (~> 4.5.0) railties (>= 3.0.0) - faraday (0.9.0) + faraday (0.9.1) multipart-post (>= 1.2, < 3) globalid (0.3.3) activesupport (>= 4.1.0) @@ -124,7 +124,7 @@ GEM haml (>= 3.1, < 5.0) html2haml (>= 1.0.1) railties (>= 4.0.1) - hashie (3.3.2) + hashie (3.4.0) hike (1.2.3) html2haml (2.0.0) erubis (~> 2.7.0) @@ -132,7 +132,7 @@ GEM nokogiri (~> 1.6.0) ruby_parser (~> 3.5) i18n (0.7.0) - jbuilder (2.2.7) + jbuilder (2.2.8) activesupport (>= 3.0.0, < 5) multi_json (~> 1.2) jquery-rails (4.0.3) @@ -140,7 +140,7 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.2) - jwt (1.2.0) + jwt (1.3.0) loofah (2.0.1) nokogiri (>= 1.5.9) mail (2.6.3) @@ -152,7 +152,7 @@ GEM multi_json (1.10.1) multi_xml (0.5.5) multipart-post (2.0.0) - netrc (0.10.2) + netrc (0.10.3) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) oauth (0.4.7) @@ -165,7 +165,7 @@ GEM omniauth (1.2.2) hashie (>= 1.2, < 4) rack (~> 1.0) - omniauth-facebook (2.0.0) + omniauth-facebook (2.0.1) omniauth-oauth2 (~> 1.2) omniauth-google-oauth2 (0.2.6) omniauth (> 1.0) @@ -231,7 +231,7 @@ GEM netrc (~> 0.7) ruby_parser (3.6.4) sexp_processor (~> 4.1) - sass (3.4.12) + sass (3.4.13) sass-rails (5.0.1) railties (>= 4.0.0, < 5.0) sass (~> 3.1) @@ -251,7 +251,7 @@ GEM simplecov-html (~> 0.9.0) simplecov-html (0.9.0) slop (3.6.0) - spring (1.3.2) + spring (1.3.3) sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) @@ -268,13 +268,13 @@ GEM tilt (1.4.1) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.7.0) + uglifier (2.7.1) execjs (>= 0.3.0) json (>= 1.8.0) - web-console (2.0.0) - activemodel (~> 4.0) + web-console (2.1.0) + activemodel (>= 4.0) binding_of_caller (>= 0.7.2) - railties (~> 4.0) + railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) PLATFORMS diff --git a/app/controllers/web/admin/members_controller.rb b/app/controllers/web/admin/members_controller.rb index 88db91a5..99cf9739 100644 --- a/app/controllers/web/admin/members_controller.rb +++ b/app/controllers/web/admin/members_controller.rb @@ -1,6 +1,6 @@ class Web::Admin::MembersController < Web::Admin::ApplicationController def index - @members = ::MemberDecorator.decorate_collection Member.all + @members = ::MemberDecorator.decorate_collection Member.presented end def new diff --git a/app/controllers/web/admin/users_controller.rb b/app/controllers/web/admin/users_controller.rb index 14567f40..88938dfe 100644 --- a/app/controllers/web/admin/users_controller.rb +++ b/app/controllers/web/admin/users_controller.rb @@ -1,6 +1,6 @@ class Web::Admin::UsersController < Web::Admin::ApplicationController def index - @users = User.all + @users = User.presented end def new diff --git a/app/decorators/member_decorator.rb b/app/decorators/member_decorator.rb index 22411cc4..c1d53cce 100644 --- a/app/decorators/member_decorator.rb +++ b/app/decorators/member_decorator.rb @@ -13,6 +13,10 @@ def full_name "#{first_name} #{patronymic} #{last_name}" end + def name + full_name + end + def short_name "#{first_name} #{last_name}" end diff --git a/app/models/member.rb b/app/models/member.rb index 43dad8b7..fd6df742 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -17,6 +17,9 @@ class Member < ActiveRecord::Base validates :locality, presence: true validates :avatar, presence: true + scope :presented, -> { where.not(state: :removed) } + scope :removed, -> { where state: :removed } + state_machine :state, initial: :not_confirmed do state :not_confirmed state :confirmed @@ -39,6 +42,9 @@ class Member < ActiveRecord::Base attr_accessor :first_name, :last_name def update_user_name - User.update user_id, first_name: first_name, last_name: last_name if user_id + if user_id + User.update user_id, first_name: first_name if first_name.present? + User.update user_id, last_name: last_name if last_name.present? + end end end diff --git a/app/models/user.rb b/app/models/user.rb index 707eb29c..6f19de70 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -14,6 +14,7 @@ class User < ActiveRecord::Base scope :admins, -> { where role: :admin } + scope :presented, -> { where.not(state: :removed) } scope :removed, -> { where state: :removed } state_machine :state, initial: :not_confirmed do diff --git a/app/views/web/admin/members/index.html.haml b/app/views/web/admin/members/index.html.haml index b6aa6dff..87f9eb76 100644 --- a/app/views/web/admin/members/index.html.haml +++ b/app/views/web/admin/members/index.html.haml @@ -1,6 +1,3 @@ -= javascript_include_tag :tabs -= stylesheet_link_tag :tabs - - model_class = Member .page-header %h1= model_class.model_name.human.pluralize(:ru) From 07672c964472d99916e47fdc5cb1fb744f871ddd Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 02:58:36 +0300 Subject: [PATCH 083/227] add dropdown menu --- app/views/layouts/web/admin/_navbar.html.haml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 568334ef..d13ee0b3 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -13,6 +13,13 @@ = menu_item admin_members_path do = Member.model_name.human.pluralize(:ru) %ul.nav.navbar-nav.navbar-right - = menu_item '/admin/trash/index/user' do - %span.glyphicon.glyphicon-trash - = t('web.trash.title') + %li.dropdown + %a.dropdown-toggle{ href: '#', data: { toggle: :dropdown } } + %span.glyphicon.glyphicon-trash + = t('web.trash.title') + %ul.dropdown-menu + = menu_item '/admin/trash/index/user' do + = User.model_name.human.pluralize(:ru) + = menu_item '/admin/trash/index/member' do + = Member.model_name.human.pluralize(:ru) + From 28f173a0aff927a2df0339909828dccafed04682 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:06:28 +0300 Subject: [PATCH 084/227] functional after create user --- app/controllers/web/users_controller.rb | 3 ++- app/views/web/members/new.html.haml | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/web/users_controller.rb b/app/controllers/web/users_controller.rb index 384a0d49..9e00e70d 100644 --- a/app/controllers/web/users_controller.rb +++ b/app/controllers/web/users_controller.rb @@ -9,7 +9,8 @@ def create @user_form = UserForm.new(@user) @user_form.submit(params[:user]) if @user_form.save - redirect_to root_path + sign_in @user + redirect_to new_member_path else render action: :new end diff --git a/app/views/web/members/new.html.haml b/app/views/web/members/new.html.haml index 74e3de66..118991b9 100644 --- a/app/views/web/members/new.html.haml +++ b/app/views/web/members/new.html.haml @@ -1,8 +1,7 @@ = simple_form_for @member, url: { controller: 'web/members', action: :create } do |f| - - f.simple_fields_for :user do |ff| - = ff.input :first_name, as: :string - = ff.input :last_name, as: :string + = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } = f.input :patronymic, as: :string + = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } = f.input :motto, as: :string = f.input :ticket_number, as: :string = f.input :mobile_phone, as: :string @@ -12,4 +11,5 @@ = f.input :locality, as: :string = f.input :avatar, as: :string = f.input :user_id, as: :hidden, input_html: { value: current_user.id } - = f.button :submit, t('.register') + = f.button :submit, t('helpers.links.save'), class: 'btn-success' + = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' From 0c3aef229357239c91ed7a49fc13bdf4bf9e797e Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:07:22 +0300 Subject: [PATCH 085/227] fix test --- test/controllers/web/users_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/web/users_controller_test.rb b/test/controllers/web/users_controller_test.rb index b0d075a2..d7d8fb72 100644 --- a/test/controllers/web/users_controller_test.rb +++ b/test/controllers/web/users_controller_test.rb @@ -14,7 +14,7 @@ class Web::UsersControllerTest < ActionController::TestCase attributes = attributes_for :user post :create, user: attributes assert_response :redirect, @response.body - assert_redirected_to root_path + assert_redirected_to new_member_path assert_equal attributes[:email], User.last.email end end From ca52b4d4327856f690adcf4d462f4dfae77b81d9 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:13:21 +0300 Subject: [PATCH 086/227] rename ticket_number to ticket --- app/forms/member_form.rb | 2 +- app/models/member.rb | 2 +- app/views/web/admin/members/_form.html.haml | 2 +- app/views/web/members/new.html.haml | 2 +- db/migrate/20150301204107_create_members.rb | 2 +- db/schema.rb | 2 +- test/factories/members.rb | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb index 2d9c9afa..cbf56a78 100644 --- a/app/forms/member_form.rb +++ b/app/forms/member_form.rb @@ -1,5 +1,5 @@ class MemberForm < ActiveForm::Base self.main_model = :member - attributes :first_name, :last_name, :patronymic, :user_id, :motto, :ticket_number, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar + attributes :first_name, :last_name, :patronymic, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar end diff --git a/app/models/member.rb b/app/models/member.rb index fd6df742..70693e9a 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -7,7 +7,7 @@ class Member < ActiveRecord::Base validates :patronymic, presence: true, human_name: true validates :motto, presence: true - validates :ticket_number, presence: true, + validates :ticket, presence: true, uniqueness: true validates :mobile_phone, presence: true, phone: true diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index b421e728..06dc7bb4 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -13,7 +13,7 @@ - else = f.input :last_name, as: :string, required: true = f.input :motto, as: :string - = f.input :ticket_number, as: :string + = f.input :ticket, as: :string = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string diff --git a/app/views/web/members/new.html.haml b/app/views/web/members/new.html.haml index 118991b9..ef776086 100644 --- a/app/views/web/members/new.html.haml +++ b/app/views/web/members/new.html.haml @@ -3,7 +3,7 @@ = f.input :patronymic, as: :string = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } = f.input :motto, as: :string - = f.input :ticket_number, as: :string + = f.input :ticket, as: :string = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string diff --git a/db/migrate/20150301204107_create_members.rb b/db/migrate/20150301204107_create_members.rb index fd7d75ab..ecc4ca29 100644 --- a/db/migrate/20150301204107_create_members.rb +++ b/db/migrate/20150301204107_create_members.rb @@ -4,7 +4,7 @@ def change t.text :patronymic t.integer :user_id t.text :motto - t.integer :ticket_number + t.integer :ticket t.integer :parent_id t.text :mobile_phone t.datetime :birth_date diff --git a/db/schema.rb b/db/schema.rb index 25b526cf..5d7e1840 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -28,7 +28,7 @@ t.text "patronymic" t.integer "user_id" t.text "motto" - t.integer "ticket_number" + t.integer "ticket" t.integer "parent_id" t.text "mobile_phone" t.datetime "birth_date" diff --git a/test/factories/members.rb b/test/factories/members.rb index 26eec5e8..4ebf6de7 100644 --- a/test/factories/members.rb +++ b/test/factories/members.rb @@ -4,7 +4,7 @@ association :user user_id { User.last ? User.last.id : 1 } motto { generate :string } - ticket_number { generate :integer } + ticket { generate :integer } parent_id 1 mobile_phone { generate :phone } birth_date { generate :date } From b64bc25719648f6af43e554848f2f4cb40641f8d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:14:04 +0300 Subject: [PATCH 087/227] add show by ticket --- app/controllers/web/members_controller.rb | 4 ++++ app/views/web/members/show.html.haml | 0 config/routes.rb | 6 +++++- db/schema.rb | 4 ++-- test/controllers/web/members_controller_test.rb | 5 +++++ 5 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 app/views/web/members/show.html.haml diff --git a/app/controllers/web/members_controller.rb b/app/controllers/web/members_controller.rb index a7d4af9a..944b5b8d 100644 --- a/app/controllers/web/members_controller.rb +++ b/app/controllers/web/members_controller.rb @@ -16,4 +16,8 @@ def create render action: :new end end + + def show + @member = Member.find_by_ticket params[:ticket] + end end diff --git a/app/views/web/members/show.html.haml b/app/views/web/members/show.html.haml new file mode 100644 index 00000000..e69de29b diff --git a/config/routes.rb b/config/routes.rb index fea5a7f9..01c5d77a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,7 +7,11 @@ scope module: :web do resource :session, only: [:new, :create, :destroy] resources :users, only: [ :new, :create ] - resources :members, only: [ :new, :create ] + resources :members, only: [ :new, :create ] do + collection do + get '/:ticket' => 'members#show' + end + end namespace :admin do root to: 'welcome#index' resources :users diff --git a/db/schema.rb b/db/schema.rb index 5d7e1840..a1af2bbd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -36,8 +36,8 @@ t.text "municipality" t.text "locality" t.text "avatar" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "state" end diff --git a/test/controllers/web/members_controller_test.rb b/test/controllers/web/members_controller_test.rb index ff51bcc2..26ce010f 100644 --- a/test/controllers/web/members_controller_test.rb +++ b/test/controllers/web/members_controller_test.rb @@ -24,4 +24,9 @@ class Web::MembersControllerTest < ActionController::TestCase assert_redirected_to root_path assert_equal attributes[:patronymic], Member.last.patronymic end + + test 'should get show' do + get :show, ticket: @member.ticket + assert_response :success, @response.body + end end From ca2841ae9f1c5a3c7022dc53e59f463217917f2a Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:33:27 +0300 Subject: [PATCH 088/227] add unviewed controller --- .../javascripts/web/admin/unviewed.coffee | 3 +++ .../stylesheets/web/admin/unviewed.scss | 3 +++ app/controllers/web/admin/trash_controller.rb | 1 - .../web/admin/unviewed_controller.rb | 5 ++++ .../web/admin/unviewed_decorator.rb | 13 ++++++++++ app/helpers/web/admin/application_helper.rb | 3 +++ app/helpers/web/admin/unviewed_helper.rb | 2 ++ app/models/member.rb | 1 + app/views/layouts/web/admin/_navbar.html.haml | 4 +++ app/views/web/admin/members/index.html.haml | 4 +-- app/views/web/admin/unviewed/index.html.haml | 25 +++++++++++++++++++ config/routes.rb | 1 + .../web/admin/trash_controller_test.rb | 4 +++ .../web/admin/unviewed_controller_test.rb | 12 +++++++++ .../web/admin/unviewed_decorator_test.rb | 4 +++ 15 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/web/admin/unviewed.coffee create mode 100644 app/assets/stylesheets/web/admin/unviewed.scss create mode 100644 app/controllers/web/admin/unviewed_controller.rb create mode 100644 app/decorators/web/admin/unviewed_decorator.rb create mode 100644 app/helpers/web/admin/unviewed_helper.rb create mode 100644 app/views/web/admin/unviewed/index.html.haml create mode 100644 test/controllers/web/admin/unviewed_controller_test.rb create mode 100644 test/decorators/web/admin/unviewed_decorator_test.rb diff --git a/app/assets/javascripts/web/admin/unviewed.coffee b/app/assets/javascripts/web/admin/unviewed.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/admin/unviewed.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/web/admin/unviewed.scss b/app/assets/stylesheets/web/admin/unviewed.scss new file mode 100644 index 00000000..9dacc529 --- /dev/null +++ b/app/assets/stylesheets/web/admin/unviewed.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/admin/unviewed 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/web/admin/trash_controller.rb b/app/controllers/web/admin/trash_controller.rb index ca85aaa0..1b276194 100644 --- a/app/controllers/web/admin/trash_controller.rb +++ b/app/controllers/web/admin/trash_controller.rb @@ -17,7 +17,6 @@ def destroy end private - def resource_type @_type ||= params[:type].capitalize.constantize end diff --git a/app/controllers/web/admin/unviewed_controller.rb b/app/controllers/web/admin/unviewed_controller.rb new file mode 100644 index 00000000..55309e37 --- /dev/null +++ b/app/controllers/web/admin/unviewed_controller.rb @@ -0,0 +1,5 @@ +class Web::Admin::UnviewedController < Web::Admin::ApplicationController + def index + @members = ::MemberDecorator.decorate_collection Member.not_confirmed + end +end diff --git a/app/decorators/web/admin/unviewed_decorator.rb b/app/decorators/web/admin/unviewed_decorator.rb new file mode 100644 index 00000000..cae715d6 --- /dev/null +++ b/app/decorators/web/admin/unviewed_decorator.rb @@ -0,0 +1,13 @@ +class Web::Admin::UnviewedDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/helpers/web/admin/application_helper.rb b/app/helpers/web/admin/application_helper.rb index 8cdd567e..b596ce73 100644 --- a/app/helpers/web/admin/application_helper.rb +++ b/app/helpers/web/admin/application_helper.rb @@ -1,2 +1,5 @@ module Web::Admin::ApplicationHelper + def notification_count + Member.not_confirmed.count + end end diff --git a/app/helpers/web/admin/unviewed_helper.rb b/app/helpers/web/admin/unviewed_helper.rb new file mode 100644 index 00000000..00bc5a9d --- /dev/null +++ b/app/helpers/web/admin/unviewed_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::UnviewedHelper +end diff --git a/app/models/member.rb b/app/models/member.rb index 70693e9a..78698f21 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -19,6 +19,7 @@ class Member < ActiveRecord::Base scope :presented, -> { where.not(state: :removed) } scope :removed, -> { where state: :removed } + scope :not_confirmed, -> { where state: :not_confirmed } state_machine :state, initial: :not_confirmed do state :not_confirmed diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index d13ee0b3..feba5c5f 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -13,6 +13,10 @@ = menu_item admin_members_path do = Member.model_name.human.pluralize(:ru) %ul.nav.navbar-nav.navbar-right + %li + %a{ href: '#' } + %span.glyphicon.glyphicon-info-sign + = notification_count %li.dropdown %a.dropdown-toggle{ href: '#', data: { toggle: :dropdown } } %span.glyphicon.glyphicon-trash diff --git a/app/views/web/admin/members/index.html.haml b/app/views/web/admin/members/index.html.haml index 87f9eb76..14955376 100644 --- a/app/views/web/admin/members/index.html.haml +++ b/app/views/web/admin/members/index.html.haml @@ -7,7 +7,7 @@ %th= model_class.human_attribute_name(:id) %th= model_class.human_attribute_name(:avatar) %th= model_class.human_attribute_name(:full_name) - %th= model_class.human_attribute_name(:ticket_number) + %th= model_class.human_attribute_name(:ticket) %th= model_class.human_attribute_name(:place) %th= t 'helpers.links.actions' %tbody @@ -16,7 +16,7 @@ %td= member.id %td= member.avatar %td= member.full_name - %td= member.ticket_number + %td= member.ticket %td= member.place %td = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' diff --git a/app/views/web/admin/unviewed/index.html.haml b/app/views/web/admin/unviewed/index.html.haml new file mode 100644 index 00000000..14955376 --- /dev/null +++ b/app/views/web/admin/unviewed/index.html.haml @@ -0,0 +1,25 @@ +- model_class = Member +.page-header + %h1= model_class.model_name.human.pluralize(:ru) +%table.table.table-condensed.table-hover + %thead + %tr + %th= model_class.human_attribute_name(:id) + %th= model_class.human_attribute_name(:avatar) + %th= model_class.human_attribute_name(:full_name) + %th= model_class.human_attribute_name(:ticket) + %th= model_class.human_attribute_name(:place) + %th= t 'helpers.links.actions' + %tbody + - @members.each do |member| + %tr.link{ data: { href: edit_admin_member_path(member) } } + %td= member.id + %td= member.avatar + %td= member.full_name + %td= member.ticket + %td= member.place + %td + = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' + = link_to t('helpers.links.destroy'), admin_member_path(member), method: :delete, class: 'btn btn-xs btn-danger' + += link_to t('.new', default: t('helpers.links.new')), new_admin_member_path, class: 'btn btn-primary' diff --git a/config/routes.rb b/config/routes.rb index 01c5d77a..453f106e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,6 +16,7 @@ root to: 'welcome#index' resources :users resources :members + resources :unviewed, only: :index resources :trash, only: [] do collection do get 'index/:type' => 'trash#index', as: :type diff --git a/test/controllers/web/admin/trash_controller_test.rb b/test/controllers/web/admin/trash_controller_test.rb index d45e09bb..63829d2c 100644 --- a/test/controllers/web/admin/trash_controller_test.rb +++ b/test/controllers/web/admin/trash_controller_test.rb @@ -4,10 +4,14 @@ class Web::Admin::TrashControllerTest < ActionController::TestCase setup do @user = create :user @user.remove + @member = create :member + @member.remove end test 'should get index' do get :index, type: :user assert_response :success, @response.body + get :index, type: :member + assert_response :success, @response.body end test 'should patch restore' do diff --git a/test/controllers/web/admin/unviewed_controller_test.rb b/test/controllers/web/admin/unviewed_controller_test.rb new file mode 100644 index 00000000..5b88efc7 --- /dev/null +++ b/test/controllers/web/admin/unviewed_controller_test.rb @@ -0,0 +1,12 @@ +require 'test_helper' + +class Web::Admin::UnviewedControllerTest < ActionController::TestCase + setup do + @member = create :member + end + + test 'should get index' do + get :index + assert_response :success, @response.body + end +end diff --git a/test/decorators/web/admin/unviewed_decorator_test.rb b/test/decorators/web/admin/unviewed_decorator_test.rb new file mode 100644 index 00000000..0d2eb043 --- /dev/null +++ b/test/decorators/web/admin/unviewed_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Web::Admin::UnviewedDecoratorTest < Draper::TestCase +end From 83c8f4e2ea11a1799bf6d1a38a22b92088ac5cc8 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:53:38 +0300 Subject: [PATCH 089/227] update creating member --- app/forms/member_form.rb | 2 +- app/models/member.rb | 6 +++++- app/views/layouts/web/admin/_navbar.html.haml | 11 +++++------ app/views/web/admin/members/_form.html.haml | 4 +++- config/locales/ru/models.yml | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb index cbf56a78..f67305bf 100644 --- a/app/forms/member_form.rb +++ b/app/forms/member_form.rb @@ -1,5 +1,5 @@ class MemberForm < ActiveForm::Base self.main_model = :member - attributes :first_name, :last_name, :patronymic, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar + attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar end diff --git a/app/models/member.rb b/app/models/member.rb index 78698f21..a3a0ca20 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -40,12 +40,16 @@ class Member < ActiveRecord::Base transition removed: :not_confirmed end end - attr_accessor :first_name, :last_name + attr_accessor :first_name, :last_name, :email def update_user_name if user_id User.update user_id, first_name: first_name if first_name.present? User.update user_id, last_name: last_name if last_name.present? + User.update user_id, email: email if email.present? + else + user = User.create(first_name: first_name, last_name: last_name, email: email) + update user_id: User.last.id end end end diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index feba5c5f..3bf86671 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -13,17 +13,16 @@ = menu_item admin_members_path do = Member.model_name.human.pluralize(:ru) %ul.nav.navbar-nav.navbar-right - %li - %a{ href: '#' } - %span.glyphicon.glyphicon-info-sign - = notification_count + = menu_item admin_unviewed_index_path do + %span.glyphicon.glyphicon-info-sign + = notification_count %li.dropdown %a.dropdown-toggle{ href: '#', data: { toggle: :dropdown } } %span.glyphicon.glyphicon-trash = t('web.trash.title') %ul.dropdown-menu - = menu_item '/admin/trash/index/user' do + = menu_item type_admin_trash_index_path('user') do = User.model_name.human.pluralize(:ru) - = menu_item '/admin/trash/index/member' do + = menu_item type_admin_trash_index_path('member') do = Member.model_name.human.pluralize(:ru) diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index 06dc7bb4..f9b5721b 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -10,8 +10,10 @@ = f.input :patronymic, as: :string - if @member.user.present? = f.input :last_name, as: :string, required: true, input_html: { value: @member.user.last_name } + = f.input :email, as: :string, required: true, input_html: { value: @member.user.email } - else = f.input :last_name, as: :string, required: true + = f.input :email, as: :string, required: true = f.input :motto, as: :string = f.input :ticket, as: :string = f.input :mobile_phone, as: :string @@ -19,7 +21,7 @@ = f.input :home_adress, as: :string = f.input :municipality, as: :string = f.input :locality, as: :string - = f.input :avatar, as: :file + = f.input :avatar, as: :string = f.association :user, label_method: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id, required: true = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 8ff6fa1c..61c81d04 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -18,7 +18,7 @@ ru: last_name: Фамилия avatar: Аватар full_name: Ф.И.О. - ticket_number: Номер членского билета + ticket: Номер членского билета place: Место проживания motto: Девиз по жизни mobile_phone: Мобильный телефон From 9f58527459334edcc6c35e72f4f128b76d4b3781 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 03:59:08 +0300 Subject: [PATCH 090/227] add approve and decline to unviewed --- app/forms/member_form.rb | 2 +- app/views/web/admin/unviewed/index.html.haml | 8 +++----- config/locales/ru/ru.yml | 2 ++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb index f67305bf..c7ae503c 100644 --- a/app/forms/member_form.rb +++ b/app/forms/member_form.rb @@ -1,5 +1,5 @@ class MemberForm < ActiveForm::Base self.main_model = :member - attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar + attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state end diff --git a/app/views/web/admin/unviewed/index.html.haml b/app/views/web/admin/unviewed/index.html.haml index 14955376..dd310517 100644 --- a/app/views/web/admin/unviewed/index.html.haml +++ b/app/views/web/admin/unviewed/index.html.haml @@ -1,6 +1,6 @@ - model_class = Member .page-header - %h1= model_class.model_name.human.pluralize(:ru) + %h3= model_class.model_name.human.pluralize(:ru) %table.table.table-condensed.table-hover %thead %tr @@ -19,7 +19,5 @@ %td= member.ticket %td= member.place %td - = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' - = link_to t('helpers.links.destroy'), admin_member_path(member), method: :delete, class: 'btn btn-xs btn-danger' - -= link_to t('.new', default: t('helpers.links.new')), new_admin_member_path, class: 'btn btn-primary' + = link_to t('helpers.links.approve'), admin_member_path(member, member: { state: :confirmed }), method: :patch, class: 'btn btn-primary btn-xs' + = link_to t('helpers.links.decline'), admin_member_path(member, member: { state: :declined }), method: :patch, class: 'btn btn-xs btn-danger' diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 573b5779..0a2655aa 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -14,6 +14,8 @@ ru: new: Добавить save: Сохранить back: Назад + approve: Подтвердить + decline: Отклонить web: users: new: From 55eeeb3b4eabceb86a2e9c04df696e4b0b84f5f8 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:04:10 +0300 Subject: [PATCH 091/227] add color to new member --- app/helpers/web/admin/application_helper.rb | 3 +++ app/views/web/admin/members/index.html.haml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/helpers/web/admin/application_helper.rb b/app/helpers/web/admin/application_helper.rb index b596ce73..41872da5 100644 --- a/app/helpers/web/admin/application_helper.rb +++ b/app/helpers/web/admin/application_helper.rb @@ -2,4 +2,7 @@ module Web::Admin::ApplicationHelper def notification_count Member.not_confirmed.count end + def state_color(item) + :success if item.not_confirmed? + end end diff --git a/app/views/web/admin/members/index.html.haml b/app/views/web/admin/members/index.html.haml index 14955376..29cde00f 100644 --- a/app/views/web/admin/members/index.html.haml +++ b/app/views/web/admin/members/index.html.haml @@ -12,7 +12,7 @@ %th= t 'helpers.links.actions' %tbody - @members.each do |member| - %tr.link{ data: { href: edit_admin_member_path(member) } } + %tr.link{ class: state_color(member), data: { href: edit_admin_member_path(member) } } %td= member.id %td= member.avatar %td= member.full_name From 12a63363169b0e94a8b26c8d827de53700648bcb Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:09:50 +0300 Subject: [PATCH 092/227] add codeclimate badge --- ReadMe.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReadMe.md b/ReadMe.md index fa4c4e2d..6fead0f4 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,3 +1,4 @@ +[](https://codeclimate.com/github/ulmic/ulmicru) ```shell git clone git@github.com:ulmic/ulmicru.git cd ulmicru From 833b6b78882d79c6218db998cdc6f2c2cd7ed65b Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:13:18 +0300 Subject: [PATCH 093/227] add travis badge --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 6fead0f4..58e80541 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ -[](https://codeclimate.com/github/ulmic/ulmicru) +[](https://travis-ci.org/ulmic/ulmicru) [](https://codeclimate.com/github/ulmic/ulmicru) ```shell git clone git@github.com:ulmic/ulmicru.git cd ulmicru From dac596166e32c36886d8ff3ded984bf3a8fa2dbd Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:15:22 +0300 Subject: [PATCH 094/227] add travis.yml --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..576cd221 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: ruby +rvm: + - 2.1 +env: + - DB=postgres +script: + - gem install tilt -v '1.4.1' + - gem install byebug -v '3.5.1' + - cp config/secrets.yml.sample config/secrets.yml + - RAILS_ENV=test bundle exec rake db:create db:migrate test +services: + - redis-server From 0c80c78e33409ef58aee93f564bbf2827ab0dcb5 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:20:11 +0300 Subject: [PATCH 095/227] add coveralls --- .coveralls.yml | 1 + ReadMe.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .coveralls.yml diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 00000000..610f0744 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +repo_token: tmDKzfJz4n6s5dTpUALNDoi1jRXFWykPg diff --git a/ReadMe.md b/ReadMe.md index 58e80541..64bf2f7b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ -[](https://travis-ci.org/ulmic/ulmicru) [](https://codeclimate.com/github/ulmic/ulmicru) +[](https://travis-ci.org/ulmic/ulmicru) [](https://coveralls.io/r/ulmic/ulmicru) [](https://codeclimate.com/github/ulmic/ulmicru) ```shell git clone git@github.com:ulmic/ulmicru.git cd ulmicru From b26cd64131bff711aec5b36e359a963760d42421 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:20:59 +0300 Subject: [PATCH 096/227] update travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 576cd221..6484d25d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ env: script: - gem install tilt -v '1.4.1' - gem install byebug -v '3.5.1' + - gem install sdoc -v '0.4.1' - cp config/secrets.yml.sample config/secrets.yml - RAILS_ENV=test bundle exec rake db:create db:migrate test services: From e8e0d31ba00b23630c2462971d09556a318c7987 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 3 Mar 2015 04:20:59 +0300 Subject: [PATCH 097/227] update travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6484d25d..3bffcd8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ script: - gem install byebug -v '3.5.1' - gem install sdoc -v '0.4.1' - cp config/secrets.yml.sample config/secrets.yml + - cp config/database.yml.sample config/database.yml - RAILS_ENV=test bundle exec rake db:create db:migrate test services: - redis-server From 513da9bba987174482f4145c06b99d0f8df69547 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:00:49 +0300 Subject: [PATCH 098/227] add routes to news --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index b5249e96..796f4543 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,7 @@ scope module: :web do resource :session, only: [:new, :create, :destroy] + resources :news, only: [:index, :show] resources :users, only: [ :new, :create ] namespace :admin do root to: 'welcome#index' From 16a8c1b210929fe24152203b1bfb7f38ec849372 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:01:30 +0300 Subject: [PATCH 099/227] add view for news --- app/views/web/news/index.html.haml | 8 ++++++++ app/views/web/news/show.html.haml | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 app/views/web/news/index.html.haml create mode 100644 app/views/web/news/show.html.haml diff --git a/app/views/web/news/index.html.haml b/app/views/web/news/index.html.haml new file mode 100644 index 00000000..f22bd6a9 --- /dev/null +++ b/app/views/web/news/index.html.haml @@ -0,0 +1,8 @@ +.index + - @news.each do |news| + .news.link{ data: { href: news_path(news) } } + .title + = news.title + .body + = image_tag news.photo + != strip_tags news.long_lead diff --git a/app/views/web/news/show.html.haml b/app/views/web/news/show.html.haml new file mode 100644 index 00000000..9cc0182e --- /dev/null +++ b/app/views/web/news/show.html.haml @@ -0,0 +1,17 @@ +- content_for :meta do + %meta{ property: 'og:description', content: "#{@news.description_lead} #ЯЛидер" } + %meta{ property:'og:image', content: @news.photo } + %meta{ name:'description', content: "#{@news.description_lead} #ЯЛидер" } + %link{ rel: 'image_src', href: @news.photo } +.show + .title + = @news.title + .date + = @news.publish_date_time + .body + - if mobile_device? + = image_tag @news.photo.mobile_thumb, class: 'main_photo' + - else + = image_tag @news.photo, class: 'main_photo' + + != @news.body From 229d8e8182e7b9b7b3899af7f912f599851ed9b0 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:38:38 +0300 Subject: [PATCH 100/227] published and unpublished news --- app/models/news.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/news.rb b/app/models/news.rb index ee5e5b2f..794ff827 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -3,11 +3,17 @@ class News < ActiveRecord::Base validates :title, presence: true validates :body, presence: true validates :published_at, presence: true - validates :photo, presence: true + validates :photo, presence: false validates :author_id, presence: true def is_published? published_at <= DateTime.now end + + extend Enumerize + + scope :published, -> { where "published_at <= ?", DateTime.now} + scope :unpublished, -> { where "published_at > ?", DateTime.now} + end From cb8c1e36109f455d6b1740918a8354ff718a23e9 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:49:51 +0300 Subject: [PATCH 101/227] add admin controller for news --- app/controllers/web/admin/news_controller.rb | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/controllers/web/admin/news_controller.rb diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb new file mode 100644 index 00000000..643ed617 --- /dev/null +++ b/app/controllers/web/admin/news_controller.rb @@ -0,0 +1,33 @@ +class Web::NewsController < Web::ApplicationController + def index + @published_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') + @unpublished_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') + end + + def show + @news = News.find(params[:id]).decorate #FIXME I'm don't know how to include decorate there into code + if !@news.is_published? + #FIXME there 404 error path + end + end + + def create + + end + + def new + + end + + def edit + + end + + def update + + end + + def destroy + + end +end From 1ab1626b31125bb5c1d8ee9ba26d4055e12bf1a4 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:50:11 +0300 Subject: [PATCH 102/227] Add routes for Web::Admin::NewsController --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index 796f4543..2c7ac764 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ namespace :admin do root to: 'welcome#index' resources :users + resources :news resources :trash, only: [] do collection do get 'index/:type' => 'trash#index' From 6a3adab6b09ecba29cbca243c462d87542cfd6e0 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:51:25 +0300 Subject: [PATCH 103/227] Add decorators for news --- app/decorators/web/news_decorator.rb | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/decorators/web/news_decorator.rb b/app/decorators/web/news_decorator.rb index e420ad35..4031a1c4 100644 --- a/app/decorators/web/news_decorator.rb +++ b/app/decorators/web/news_decorator.rb @@ -1,13 +1,20 @@ class Web::NewsDecorator < Draper::Decorator delegate_all - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end + def lead + "#{model.body.first(250)}..." + end + + def description_lead + "#{model.body.first(200)}..." + end + + def long_lead + "#{model.body.first(600)}..." + end + + def publish_date_time + l(object.published_at)[0..22] + end end From 13dfa3761289c1ba5310e2eeb7018715e35c3bb8 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 4 Mar 2015 23:53:04 +0300 Subject: [PATCH 104/227] Update namespace for NewsDecorator --- app/controllers/web/news_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index 80de3f2c..a6460678 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -1,12 +1,12 @@ class Web::NewsController < Web::ApplicationController def index - @news = NewsDecorator.decorate_collection News.published.order('created_at DESC') + @news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') end def show - @news = News.find(params[:id]).decorate + @news = News.find(params[:id]).decorate #FIXME I'm don't know how to include decorate there into code if !@news.is_published? -#FIXME there 404 error path + #FIXME there 404 error path end end end From fb9c8fae5b5a1c2194cf395f0c8b97288997ccc0 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 00:11:59 +0300 Subject: [PATCH 105/227] starnnii kostil --- app/controllers/web/news_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index a6460678..a31a06aa 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -4,7 +4,7 @@ def index end def show - @news = News.find(params[:id]).decorate #FIXME I'm don't know how to include decorate there into code + @news = Web::NewsDecorator.decorate News.find params[:id] #FIXME I'm don't know how to include decorate there into code if !@news.is_published? #FIXME there 404 error path end From 243238bb6e33881961db848efac90393ab349bdb Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 00:12:58 +0300 Subject: [PATCH 106/227] delete mobile photo --- app/views/web/news/show.html.haml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/views/web/news/show.html.haml b/app/views/web/news/show.html.haml index 9cc0182e..3f0cd0ca 100644 --- a/app/views/web/news/show.html.haml +++ b/app/views/web/news/show.html.haml @@ -9,9 +9,5 @@ .date = @news.publish_date_time .body - - if mobile_device? - = image_tag @news.photo.mobile_thumb, class: 'main_photo' - - else - = image_tag @news.photo, class: 'main_photo' - + = image_tag @news.photo, class: 'main_photo' != @news.body From a3196ac9904d74774937ef5af927e42f79fa2153 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 02:57:28 +0300 Subject: [PATCH 107/227] Web -> Web::Admin in NewsController in Admin + db/schema modified: app/controllers/web/admin/news_controller.rb modified: db/schema.rb --- app/controllers/web/admin/news_controller.rb | 2 +- db/schema.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index 643ed617..b5ff2484 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -1,4 +1,4 @@ -class Web::NewsController < Web::ApplicationController +class Web::Admin::NewsController < Web::Admin::ApplicationController def index @published_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') @unpublished_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') diff --git a/db/schema.rb b/db/schema.rb index 15982847..84506820 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,9 +13,6 @@ ActiveRecord::Schema.define(version: 20150303003907) do - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" - create_table "authentications", force: :cascade do |t| t.text "provider" t.text "uid" From 8cf527e8ca76636ce8309c3f99e55527892213f0 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 10:07:58 +0300 Subject: [PATCH 108/227] Created News admin controller --- app/controllers/web/admin/news_controller.rb | 29 ++++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index b5ff2484..c927e0b1 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -12,22 +12,39 @@ def show end def create - + @news = News.new + @news_form = NewsForm.new @news + @news_form.submit params[:user] + if @news_form.save + redirect_to admin_news_path + else + redirect_to action: :new + end end - def new - + @news = News.new + @news_form = NewsForm.new @news end def edit - + @news = News.find params[:id] + @news_form = NewsForm.new @news end def update - + @news = News.find params[:id] + @news_form = NewsForm.new(@news) + @news_form.submit params[:user] + if @news_form.save + redirect_to admin_news_path + else + redirect_to action: :edit + end end def destroy - + @news = News.find params[:id] + @news.remove + redirect_to admin_news_path end end From bcb78554e70006309892ea3d92f58cdd49620d52 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 10:09:21 +0300 Subject: [PATCH 109/227] Add news form --- app/forms/news_form.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/forms/news_form.rb diff --git a/app/forms/news_form.rb b/app/forms/news_form.rb new file mode 100644 index 00000000..175b9a66 --- /dev/null +++ b/app/forms/news_form.rb @@ -0,0 +1,5 @@ +class NewsForm < ApplicationForm + self.main_model = :news + + attributes :title, :body, :published_at, :photo, :author_id +end From e1613557e60fb1bc7c7ad1f53d526e94b3514947 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 10:27:46 +0300 Subject: [PATCH 110/227] Rails generate controller --- app/assets/javascripts/web/admin/news.coffee | 3 +++ app/assets/stylesheets/web/admin/news.scss | 3 +++ app/decorators/web/admin/news_decorator.rb | 13 +++++++++++++ app/helpers/web/admin/news_helper.rb | 2 ++ test/decorators/web/admin/news_decorator_test.rb | 4 ++++ 5 files changed, 25 insertions(+) create mode 100644 app/assets/javascripts/web/admin/news.coffee create mode 100644 app/assets/stylesheets/web/admin/news.scss create mode 100644 app/decorators/web/admin/news_decorator.rb create mode 100644 app/helpers/web/admin/news_helper.rb create mode 100644 test/decorators/web/admin/news_decorator_test.rb diff --git a/app/assets/javascripts/web/admin/news.coffee b/app/assets/javascripts/web/admin/news.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/admin/news.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/web/admin/news.scss b/app/assets/stylesheets/web/admin/news.scss new file mode 100644 index 00000000..051716f0 --- /dev/null +++ b/app/assets/stylesheets/web/admin/news.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Web::Admin::News controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/decorators/web/admin/news_decorator.rb b/app/decorators/web/admin/news_decorator.rb new file mode 100644 index 00000000..7690608a --- /dev/null +++ b/app/decorators/web/admin/news_decorator.rb @@ -0,0 +1,13 @@ +class Web::Admin::NewsDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/helpers/web/admin/news_helper.rb b/app/helpers/web/admin/news_helper.rb new file mode 100644 index 00000000..61dd8854 --- /dev/null +++ b/app/helpers/web/admin/news_helper.rb @@ -0,0 +1,2 @@ +module Web::Admin::NewsHelper +end diff --git a/test/decorators/web/admin/news_decorator_test.rb b/test/decorators/web/admin/news_decorator_test.rb new file mode 100644 index 00000000..b022f465 --- /dev/null +++ b/test/decorators/web/admin/news_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Web::Admin::NewsDecoratorTest < Draper::TestCase +end From 9598514cd12c4e6fbad424c60b6f6be26c6a2be6 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 11:28:15 +0300 Subject: [PATCH 111/227] Add test for NewsController --- .../web/admin/news_controller_test.rb | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/controllers/web/admin/news_controller_test.rb diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb new file mode 100644 index 00000000..ebaa71ed --- /dev/null +++ b/test/controllers/web/admin/news_controller_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' + +class Web::Admin::NewsControllerTest < ActionController::TestCase + setup do + @news = create :news + end + + test 'should get index' do + get :index + assert_response :success, @response.body + end + + test 'should get show' do + get :show, id: @news + assert_response :success, @response.body + end + + test 'should get new' do + get :new + assert_response :success, @response.body + end + + test 'should create news' do + attributes = attributes_for :news + post :create, user: attributes + assert_response :redirect, @response.body + assert_redirected_to admin_news_index_path + assert_equal attributes[:body], News.last.body + end + + +end From 52b8c8eff3f20059d2e57bbaa5c06335f8d5324e Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 12:17:48 +0300 Subject: [PATCH 112/227] Update namespaces --- app/controllers/web/admin/news_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index c927e0b1..9d2dc0e4 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -1,11 +1,11 @@ class Web::Admin::NewsController < Web::Admin::ApplicationController def index - @published_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') - @unpublished_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') + @published_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') #FIXME using another decorator in that file + @unpublished_news = Web::NewsDecorator.decorate_collection News.unpublished.order('created_at DESC') end def show - @news = News.find(params[:id]).decorate #FIXME I'm don't know how to include decorate there into code + @news = Web::NewsDecorator.decorate News.find params[:id]#FIXME I'm don't know how to include decorate there into code if !@news.is_published? #FIXME there 404 error path end From 3cf1c79bfe82248bff1246072165494be316ff7a Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 12:18:15 +0300 Subject: [PATCH 113/227] Fix wrong paths --- app/controllers/web/admin/news_controller.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index 9d2dc0e4..d04094ac 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -14,10 +14,10 @@ def show def create @news = News.new @news_form = NewsForm.new @news - @news_form.submit params[:user] + @news_form.submit params[:news] if @news_form.save - redirect_to admin_news_path - else + redirect_to admin_news_index_path + else redirect_to action: :new end end @@ -36,7 +36,7 @@ def update @news_form = NewsForm.new(@news) @news_form.submit params[:user] if @news_form.save - redirect_to admin_news_path + redirect_to admin_news_index_path else redirect_to action: :edit end @@ -45,6 +45,6 @@ def update def destroy @news = News.find params[:id] @news.remove - redirect_to admin_news_path + redirect_to admin_news_index_path end end From fabab1064c4725a6ed2af98e8fcfc1281a34d250 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 12:19:10 +0300 Subject: [PATCH 114/227] Add admin decorator for news and add short_lead to News --- app/decorators/web/admin/news_decorator.rb | 23 ++++++++++++++-------- app/decorators/web/news_decorator.rb | 4 ++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/decorators/web/admin/news_decorator.rb b/app/decorators/web/admin/news_decorator.rb index 7690608a..4a3d47a8 100644 --- a/app/decorators/web/admin/news_decorator.rb +++ b/app/decorators/web/admin/news_decorator.rb @@ -1,13 +1,20 @@ class Web::Admin::NewsDecorator < Draper::Decorator delegate_all - # Define presentation-specific methods here. Helpers are accessed through - # `helpers` (aka `h`). You can override attributes, for example: - # - # def created_at - # helpers.content_tag :span, class: 'time' do - # object.created_at.strftime("%a %m/%d/%y") - # end - # end + def lead + "#{model.body.first(250)}..." + end + + def description_lead + "#{model.body.first(200)}..." + end + + def long_lead + "#{model.body.first(600)}..." + end + + def publish_date_time + l(object.published_at)[0..22] + end end diff --git a/app/decorators/web/news_decorator.rb b/app/decorators/web/news_decorator.rb index 4031a1c4..17093be0 100644 --- a/app/decorators/web/news_decorator.rb +++ b/app/decorators/web/news_decorator.rb @@ -1,6 +1,10 @@ class Web::NewsDecorator < Draper::Decorator delegate_all + def short_lead + "#{model.body.first(50)}..." + end + def lead "#{model.body.first(250)}..." end From ad354a0f43c63a24b5e1190167e7deeb588504ef Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 12:19:42 +0300 Subject: [PATCH 115/227] Add Views For News --- app/views/web/admin/news/_form.html.haml | 11 +++++++++ app/views/web/admin/news/_news_list.html.haml | 24 +++++++++++++++++++ app/views/web/admin/news/edit.html.haml | 1 + app/views/web/admin/news/index.html.haml | 15 ++++++++++++ app/views/web/admin/news/new.html.haml | 1 + app/views/web/admin/news/show.html.haml | 13 ++++++++++ 6 files changed, 65 insertions(+) create mode 100644 app/views/web/admin/news/_form.html.haml create mode 100644 app/views/web/admin/news/_news_list.html.haml create mode 100644 app/views/web/admin/news/edit.html.haml create mode 100644 app/views/web/admin/news/index.html.haml create mode 100644 app/views/web/admin/news/new.html.haml create mode 100644 app/views/web/admin/news/show.html.haml diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml new file mode 100644 index 00000000..bc6eafb4 --- /dev/null +++ b/app/views/web/admin/news/_form.html.haml @@ -0,0 +1,11 @@ +.page-header + %h1= "TITLE" #FIXME Wrong title :) +.row + .col-lg-6 + = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| + = f.input :title, as: :string + = f.input :body, as: :string + /FIXME add datetime_picker= f.input :published_at, as: :datetime_picker + /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } + = f.button :submit, class: 'btn-success' + = link_to t('helpers.links.back'), admin_news_index_path, class: 'btn btn-default' diff --git a/app/views/web/admin/news/_news_list.html.haml b/app/views/web/admin/news/_news_list.html.haml new file mode 100644 index 00000000..0384c042 --- /dev/null +++ b/app/views/web/admin/news/_news_list.html.haml @@ -0,0 +1,24 @@ += link_to t('.new', default: t("helpers.links.new")), new_admin_news_path, class: 'btn btn-primary' +- model_class = News +%table.table.table-striped.table-condensed + %thead + %tr + %th= model_class.human_attribute_name(:id) + %th= model_class.human_attribute_name(:title) + %th= model_class.human_attribute_name(:body) + %th= model_class.human_attribute_name(:published_at) + %th= model_class.human_attribute_name(:created_at) + %th=t '.actions', default: t("helpers.actions") + %tbody + - news.each do |news| + %tr + %td= link_to news.id, edit_admin_news_path(news) + %td= link_to news.title, edit_admin_news_path(news) + %td= news.lead + %td=l news.published_at + %td=l news.created_at + %td + = link_to t('.edit', default: t("helpers.links.edit")), edit_admin_news_path(news), class: 'btn btn-warning btn-xs' + = link_to t('.destroy', default: t("helpers.links.destroy")), admin_news_path(news), method: :delete, data: { confirm: t('.confirm', default: t("helpers.links.confirm", default: 'Are you sure?')) }, class: 'btn btn-xs btn-danger' + += link_to t('.new', default: t("helpers.links.new")), new_admin_news_path, class: 'btn btn-primary' diff --git a/app/views/web/admin/news/edit.html.haml b/app/views/web/admin/news/edit.html.haml new file mode 100644 index 00000000..51cb957a --- /dev/null +++ b/app/views/web/admin/news/edit.html.haml @@ -0,0 +1 @@ += render partial: 'form', locals: { action: :update } diff --git a/app/views/web/admin/news/index.html.haml b/app/views/web/admin/news/index.html.haml new file mode 100644 index 00000000..db5b9c10 --- /dev/null +++ b/app/views/web/admin/news/index.html.haml @@ -0,0 +1,15 @@ += javascript_include_tag :tabs += stylesheet_link_tag :tabs + +.page-header + %h1=t '.title' +#tabs + %ul.nav.nav-tabs{ role: :tablist } + %li + = link_to t('.published'), '#published' + %li + = link_to t('.unpublished'), '#unpublished'#{@unpublished_news.count}", '#unpublished' + #published + = render 'news_list', news: @published_news + #unpublished + = render 'news_list', news: @unpublished_news diff --git a/app/views/web/admin/news/new.html.haml b/app/views/web/admin/news/new.html.haml new file mode 100644 index 00000000..02017918 --- /dev/null +++ b/app/views/web/admin/news/new.html.haml @@ -0,0 +1 @@ += render partial: 'form', locals: { action: :create } diff --git a/app/views/web/admin/news/show.html.haml b/app/views/web/admin/news/show.html.haml new file mode 100644 index 00000000..9b414df7 --- /dev/null +++ b/app/views/web/admin/news/show.html.haml @@ -0,0 +1,13 @@ +- content_for :meta do + %meta{ property: 'og:description', content: "#{@news.description_lead} #ЯЛидер" } + %meta{ property:'og:image', content: @news.photo } + %meta{ name:'description', content: "#{@news.description_lead} #ЯЛидер" } + %link{ rel: 'image_src', href: @news.photo } +.show + .title + = @news.title + .date + = @news.publish_date_time + .body + = image_tag @news.photo, class: 'main_photo' + = @news.body From 8933a3c1571273519f5a78cb6ebc1e5ec14ac85d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 13:10:49 +0300 Subject: [PATCH 116/227] Add datatimepicker without glyphicons --- Gemfile | 4 +- app/assets/javascripts/application.js | 3 + .../javascripts/web/admin/application.coffee | 3 + app/assets/stylesheets/application.css.sass | 2 + .../stylesheets/web/admin/application.sass | 1 + app/inputs/date_picker_input.rb | 32 + app/inputs/datetime_picker_input.rb | 32 + app/inputs/time_picker_input.rb | 30 + app/views/web/admin/news/_form.html.haml | 2 +- .../initializers/ranged_datetime_wrapper.rb | 13 + .../javascripts/bootstrap-datetimepicker.js | 1941 +++++++++++++++++ vendor/assets/javascripts/pickers.js | 47 + .../stylesheets/bootstrap-datetimepicker.css | 366 ++++ .../bootstrap-datetimepicker.min.css | 366 ++++ 14 files changed, 2839 insertions(+), 3 deletions(-) create mode 100644 app/inputs/date_picker_input.rb create mode 100644 app/inputs/datetime_picker_input.rb create mode 100644 app/inputs/time_picker_input.rb create mode 100644 config/initializers/ranged_datetime_wrapper.rb create mode 100644 vendor/assets/javascripts/bootstrap-datetimepicker.js create mode 100644 vendor/assets/javascripts/pickers.js create mode 100644 vendor/assets/stylesheets/bootstrap-datetimepicker.css create mode 100644 vendor/assets/stylesheets/bootstrap-datetimepicker.min.css diff --git a/Gemfile b/Gemfile index f8d071f0..3eaeff49 100644 --- a/Gemfile +++ b/Gemfile @@ -35,12 +35,12 @@ gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' gem 'draper' gem 'carrierwave' gem 'mini_magick' - +gem 'datetimepicker-rails', git: 'git://github.com/zpaulovics/datetimepicker-rails.git', branch: 'master', submodules: true gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' gem 'omniauth-twitter' gem 'omniauth-facebook' - +gem 'momentjs-rails', '>= 2.8.1', :github => 'derekprior/momentjs-rails' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4a2ae1d9..2eb17533 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,7 @@ //= require jquery //= require jquery_ujs //= require turbolinks +//= require moment +//= require bootstrap-datetimepicker +//= require pickers //= require_tree . diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index 02f745c1..164fd2cb 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -1,5 +1,8 @@ #= require jquery #= require bootstrap-sprockets +#= require moment +#= require bootstrap-datetimepicker +#= require pickers $ -> $('.link').click -> diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass index 4083d867..1aa6345f 100644 --- a/app/assets/stylesheets/application.css.sass +++ b/app/assets/stylesheets/application.css.sass @@ -1,4 +1,6 @@ /* + *= require_self + *= require bootstrap-datetimepicker */ diff --git a/app/assets/stylesheets/web/admin/application.sass b/app/assets/stylesheets/web/admin/application.sass index 12f9c1e1..59e1f182 100644 --- a/app/assets/stylesheets/web/admin/application.sass +++ b/app/assets/stylesheets/web/admin/application.sass @@ -1,5 +1,6 @@ @import 'bootstrap-sprockets' @import 'bootstrap' +@import 'bootstrap-datetimepicker' .link cursor: pointer diff --git a/app/inputs/date_picker_input.rb b/app/inputs/date_picker_input.rb new file mode 100644 index 00000000..7a34ed9e --- /dev/null +++ b/app/inputs/date_picker_input.rb @@ -0,0 +1,32 @@ +class DatePickerInput < SimpleForm::Inputs::StringInput + def input(wrapper_options) + value = object.send(attribute_name) if object.respond_to? attribute_name + display_pattern = I18n.t('datepicker.dformat', :default => '%d/%m/%Y') + input_html_options[:value] ||= I18n.localize(value, :format => display_pattern) if value.present? + + input_html_options[:type] = 'text' + picker_pattern = I18n.t('datepicker.pformat', :default => 'DD/MM/YYYY') + dayViewHeaderFormat = I18n.t('dayViewHeaderFormat', :default => 'MMMM YYYY') + date_options = { + locale: I18n.locale.to_s, + dayViewHeaderFormat: dayViewHeaderFormat, + format: picker_pattern + } + input_html_options[:data] ||= {} + input_html_options[:data].merge!({date_options: date_options }) + + template.content_tag :div, class: 'input-group date datepicker' do + input = super(wrapper_options) # leave StringInput do the real rendering + input += template.content_tag :span, class: 'input-group-btn' do + template.content_tag :button, class: 'btn btn-default', type: 'button' do + template.content_tag :span, '', class: 'fa fa-calendar' + end + end + input + end + end + + def input_html_classes + super.push '' # 'form-control' + end +end diff --git a/app/inputs/datetime_picker_input.rb b/app/inputs/datetime_picker_input.rb new file mode 100644 index 00000000..40430088 --- /dev/null +++ b/app/inputs/datetime_picker_input.rb @@ -0,0 +1,32 @@ +class DatetimePickerInput < SimpleForm::Inputs::StringInput + def input(wrapper_options) + value = object.send(attribute_name) if object.respond_to? attribute_name + display_pattern = I18n.t('datepicker.dformat', :default => '%d/%m/%Y') + ' ' + I18n.t('timepicker.dformat', :default => '%R') + input_html_options[:value] ||= I18n.localize(value, :format => display_pattern) if value.present? + + input_html_options[:type] = 'text' + picker_pattern = I18n.t('datepicker.pformat', :default => 'DD/MM/YYYY') + ' ' + I18n.t('timepicker.pformat', :default => 'HH:mm') + dayViewHeaderFormat = I18n.t('dayViewHeaderFormat', :default => 'MMMM YYYY') + date_options = { + locale: I18n.locale.to_s, + dayViewHeaderFormat: dayViewHeaderFormat, + format: picker_pattern + } + input_html_options[:data] ||= {} + input_html_options[:data].merge!({date_options: date_options }) + + template.content_tag :div, class: 'input-group date datetimepicker' do + input = super(wrapper_options) # leave StringInput do the real rendering + input += template.content_tag :span, class: 'input-group-btn' do + template.content_tag :button, class: 'btn btn-default', type: 'button' do + template.content_tag :span, '', class: 'fa fa-calendar' + end + end + input + end + end + + def input_html_classes + super.push '' # 'form-control' + end +end diff --git a/app/inputs/time_picker_input.rb b/app/inputs/time_picker_input.rb new file mode 100644 index 00000000..57b32e90 --- /dev/null +++ b/app/inputs/time_picker_input.rb @@ -0,0 +1,30 @@ +class TimePickerInput < SimpleForm::Inputs::StringInput + def input(wrapper_options) + value = object.send(attribute_name) if object.respond_to? attribute_name + display_pattern = I18n.t('timepicker.dformat', :default => '%R') + input_html_options[:value] ||= I18n.localize(value, :format => display_pattern) if value.present? + + input_html_options[:type] = 'text' + picker_pattern = I18n.t('timepicker.pformat', :default => 'HH:mm') + date_options = { + locale: I18n.locale.to_s, + format: picker_pattern + } + input_html_options[:data] ||= {} + input_html_options[:data].merge!({date_options: date_options }) + + template.content_tag :div, class: 'input-group date timepicker' do + input = super(wrapper_options) # leave StringInput do the real rendering + input += template.content_tag :span, class: 'input-group-btn' do + template.content_tag :button, class: 'btn btn-default', type: 'button' do + template.content_tag :span, '', class: 'fa fa-clock-o' + end + end + input + end + end + + def input_html_classes + super.push '' # 'form-control' + end +end diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index bc6eafb4..f0ea14af 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -5,7 +5,7 @@ = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :title, as: :string = f.input :body, as: :string - /FIXME add datetime_picker= f.input :published_at, as: :datetime_picker + = f.input :published_at, as: :datetime_picker /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } = f.button :submit, class: 'btn-success' = link_to t('helpers.links.back'), admin_news_index_path, class: 'btn btn-default' diff --git a/config/initializers/ranged_datetime_wrapper.rb b/config/initializers/ranged_datetime_wrapper.rb new file mode 100644 index 00000000..c70a7681 --- /dev/null +++ b/config/initializers/ranged_datetime_wrapper.rb @@ -0,0 +1,13 @@ +SimpleForm.setup do |config| + config.wrappers :ranged_datetime, tag: 'div', class: 'form-group col-md-6', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + 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 +end diff --git a/vendor/assets/javascripts/bootstrap-datetimepicker.js b/vendor/assets/javascripts/bootstrap-datetimepicker.js new file mode 100644 index 00000000..02e0ff14 --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-datetimepicker.js @@ -0,0 +1,1941 @@ +/* + //! version : 4.3.5 + ========================================================= + bootstrap-datetimejs + https://github.com/Eonasdan/bootstrap-datetimepicker + ========================================================= + The MIT License (MIT) + + Copyright (c) 2015 Jonathan Peterson + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +/*global define:false */ +/*global exports:false */ +/*global require:false */ +/*global jQuery:false */ +/*global moment:false */ +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // AMD is used - Register as an anonymous module. + define(['jquery', 'moment'], factory); + } else if (typeof exports === 'object') { + factory(require('jquery'), require('moment')); + } else { + // Neither AMD nor CommonJS used. Use global variables. + if (typeof jQuery === 'undefined') { + throw 'bootstrap-datetimepicker requires jQuery to be loaded first'; + } + if (typeof moment === 'undefined') { + throw 'bootstrap-datetimepicker requires Moment.js to be loaded first'; + } + factory(jQuery, moment); + } +}(function ($, moment) { + 'use strict'; + if (!moment) { + throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first'); + } + + var dateTimePicker = function (element, options) { + var picker = {}, + date = moment(), + viewDate = date.clone(), + unset = true, + input, + component = false, + widget = false, + use24Hours, + minViewModeNumber = 0, + actualFormat, + parseFormats, + currentViewMode, + datePickerModes = [ + { + clsName: 'days', + navFnc: 'M', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'y', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'y', + navStep: 10 + } + ], + viewModes = ['days', 'months', 'years'], + verticalModes = ['top', 'bottom', 'auto'], + horizontalModes = ['left', 'right', 'auto'], + toolbarPlacements = ['default', 'top', 'bottom'], + keyMap = { + 'up': 38, + 38: 'up', + 'down': 40, + 40: 'down', + 'left': 37, + 37: 'left', + 'right': 39, + 39: 'right', + 'tab': 9, + 9: 'tab', + 'escape': 27, + 27: 'escape', + 'enter': 13, + 13: 'enter', + 'pageUp': 33, + 33: 'pageUp', + 'pageDown': 34, + 34: 'pageDown', + 'shift': 16, + 16: 'shift', + 'control': 17, + 17: 'control', + 'space': 32, + 32: 'space', + 't': 84, + 84: 't', + 'delete': 46, + 46: 'delete' + }, + keyState = {}, + + /******************************************************************************** + * + * Private functions + * + ********************************************************************************/ + isEnabled = function (granularity) { + if (typeof granularity !== 'string' || granularity.length > 1) { + throw new TypeError('isEnabled expects a single character string parameter'); + } + switch (granularity) { + case 'y': + return actualFormat.indexOf('Y') !== -1; + case 'M': + return actualFormat.indexOf('M') !== -1; + case 'd': + return actualFormat.toLowerCase().indexOf('d') !== -1; + case 'h': + case 'H': + return actualFormat.toLowerCase().indexOf('h') !== -1; + case 'm': + return actualFormat.indexOf('m') !== -1; + case 's': + return actualFormat.indexOf('s') !== -1; + default: + return false; + } + }, + + hasTime = function () { + return (isEnabled('h') || isEnabled('m') || isEnabled('s')); + }, + + hasDate = function () { + return (isEnabled('y') || isEnabled('M') || isEnabled('d')); + }, + + getDatePickerTemplate = function () { + var headTemplate = $('<thead>') + .append($('<tr>') + .append($('<th>').addClass('prev').attr('data-action', 'previous') + .append($('<span>').addClass(options.icons.previous)) + ) + .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5'))) + .append($('<th>').addClass('next').attr('data-action', 'next') + .append($('<span>').addClass(options.icons.next)) + ) + ), + contTemplate = $('<tbody>') + .append($('<tr>') + .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7'))) + ); + + return [ + $('<div>').addClass('datepicker-days') + .append($('<table>').addClass('table-condensed') + .append(headTemplate) + .append($('<tbody>')) + ), + $('<div>').addClass('datepicker-months') + .append($('<table>').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ), + $('<div>').addClass('datepicker-years') + .append($('<table>').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) + ) + ]; + }, + + getTimePickerMainTemplate = function () { + var topRow = $('<tr>'), + middleRow = $('<tr>'), + bottomRow = $('<tr>'); + + if (isEnabled('h')) { + topRow.append($('<td>') + .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'incrementHours') + .append($('<span>').addClass(options.icons.up)))); + middleRow.append($('<td>') + .append($('<span>').addClass('timepicker-hour').attr('data-time-component', 'hours').attr('data-action', 'showHours'))); + bottomRow.append($('<td>') + .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'decrementHours') + .append($('<span>').addClass(options.icons.down)))); + } + if (isEnabled('m')) { + if (isEnabled('h')) { + topRow.append($('<td>').addClass('separator')); + middleRow.append($('<td>').addClass('separator').html(':')); + bottomRow.append($('<td>').addClass('separator')); + } + topRow.append($('<td>') + .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'incrementMinutes') + .append($('<span>').addClass(options.icons.up)))); + middleRow.append($('<td>') + .append($('<span>').addClass('timepicker-minute').attr('data-time-component', 'minutes').attr('data-action', 'showMinutes'))); + bottomRow.append($('<td>') + .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'decrementMinutes') + .append($('<span>').addClass(options.icons.down)))); + } + if (isEnabled('s')) { + if (isEnabled('m')) { + topRow.append($('<td>').addClass('separator')); + middleRow.append($('<td>').addClass('separator').html(':')); + bottomRow.append($('<td>').addClass('separator')); + } + topRow.append($('<td>') + .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'incrementSeconds') + .append($('<span>').addClass(options.icons.up)))); + middleRow.append($('<td>') + .append($('<span>').addClass('timepicker-second').attr('data-time-component', 'seconds').attr('data-action', 'showSeconds'))); + bottomRow.append($('<td>') + .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'decrementSeconds') + .append($('<span>').addClass(options.icons.down)))); + } + + if (!use24Hours) { + topRow.append($('<td>').addClass('separator')); + middleRow.append($('<td>') + .append($('<button>').addClass('btn btn-primary').attr('data-action', 'togglePeriod'))); + bottomRow.append($('<td>').addClass('separator')); + } + + return $('<div>').addClass('timepicker-picker') + .append($('<table>').addClass('table-condensed') + .append([topRow, middleRow, bottomRow])); + }, + + getTimePickerTemplate = function () { + var hoursView = $('<div>').addClass('timepicker-hours') + .append($('<table>').addClass('table-condensed')), + minutesView = $('<div>').addClass('timepicker-minutes') + .append($('<table>').addClass('table-condensed')), + secondsView = $('<div>').addClass('timepicker-seconds') + .append($('<table>').addClass('table-condensed')), + ret = [getTimePickerMainTemplate()]; + + if (isEnabled('h')) { + ret.push(hoursView); + } + if (isEnabled('m')) { + ret.push(minutesView); + } + if (isEnabled('s')) { + ret.push(secondsView); + } + + return ret; + }, + + getToolbar = function () { + var row = []; + if (options.showTodayButton) { + row.push($('<td>').append($('<a>').attr('data-action', 'today').append($('<span>').addClass(options.icons.today)))); + } + if (!options.sideBySide && hasDate() && hasTime()) { + row.push($('<td>').append($('<a>').attr('data-action', 'togglePicker').append($('<span>').addClass(options.icons.time)))); + } + if (options.showClear) { + row.push($('<td>').append($('<a>').attr('data-action', 'clear').append($('<span>').addClass(options.icons.clear)))); + } + return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row))); + }, + + getTemplate = function () { + var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'), + dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()), + timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()), + content = $('<ul>').addClass('list-unstyled'), + toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar()); + + if (options.inline) { + template.removeClass('dropdown-menu'); + } + + if (use24Hours) { + template.addClass('usetwentyfour'); + } + if (options.sideBySide && hasDate() && hasTime()) { + template.addClass('timepicker-sbs'); + template.append( + $('<div>').addClass('row') + .append(dateView.addClass('col-sm-6')) + .append(timeView.addClass('col-sm-6')) + ); + template.append(toolbar); + return template; + } + + if (options.toolbarPlacement === 'top') { + content.append(toolbar); + } + if (hasDate()) { + content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView)); + } + if (options.toolbarPlacement === 'default') { + content.append(toolbar); + } + if (hasTime()) { + content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView)); + } + if (options.toolbarPlacement === 'bottom') { + content.append(toolbar); + } + return template.append(content); + }, + + dataToOptions = function () { + var eData, + dataOptions = {}; + + if (element.is('input')) { + eData = element.data(); + } else { + eData = element.find('input').data(); + } + + if (eData.dateOptions && eData.dateOptions instanceof Object) { + dataOptions = $.extend(true, dataOptions, eData.dateOptions); + } + + $.each(options, function (key) { + var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1); + if (eData[attributeName] !== undefined) { + dataOptions[key] = eData[attributeName]; + } + }); + return dataOptions; + }, + + place = function () { + var position = (component || element).position(), + offset = (component || element).offset(), + vertical = options.widgetPositioning.vertical, + horizontal = options.widgetPositioning.horizontal, + parent; + + if (options.widgetParent) { + parent = options.widgetParent.append(widget); + } else if (element.is('input')) { + parent = element.parent().append(widget); + } else if (options.inline) { + parent = element.append(widget); + return; + } else { + parent = element; + element.children().first().after(widget); + } + + // Top and bottom logic + if (vertical === 'auto') { + if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() && + widget.height() + element.outerHeight() < offset.top) { + vertical = 'top'; + } else { + vertical = 'bottom'; + } + } + + // Left and right logic + if (horizontal === 'auto') { + if (parent.width() < offset.left + widget.outerWidth() / 2 && + offset.left + widget.outerWidth() > $(window).width()) { + horizontal = 'right'; + } else { + horizontal = 'left'; + } + } + + if (vertical === 'top') { + widget.addClass('top').removeClass('bottom'); + } else { + widget.addClass('bottom').removeClass('top'); + } + + if (horizontal === 'right') { + widget.addClass('pull-right'); + } else { + widget.removeClass('pull-right'); + } + + // find the first parent element that has a relative css positioning + if (parent.css('position') !== 'relative') { + parent = parent.parents().filter(function () { + return $(this).css('position') === 'relative'; + }).first(); + } + + if (parent.length === 0) { + throw new Error('datetimepicker component should be placed within a relative positioned container'); + } + + widget.css({ + top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(), + bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto', + left: horizontal === 'left' ? parent.css('padding-left') : 'auto', + right: horizontal === 'left' ? 'auto' : parent.width() - element.outerWidth() + }); + }, + + notifyEvent = function (e) { + if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) { + return; + } + element.trigger(e); + }, + + showMode = function (dir) { + if (!widget) { + return; + } + if (dir) { + currentViewMode = Math.max(minViewModeNumber, Math.min(2, currentViewMode + dir)); + } + widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show(); + }, + + fillDow = function () { + var row = $('<tr>'), + currentDate = viewDate.clone().startOf('w'); + + if (options.calendarWeeks === true) { + row.append($('<th>').addClass('cw').text('#')); + } + + while (currentDate.isBefore(viewDate.clone().endOf('w'))) { + row.append($('<th>').addClass('dow').text(currentDate.format('dd'))); + currentDate.add(1, 'd'); + } + widget.find('.datepicker-days thead').append(row); + }, + + isInDisabledDates = function (testDate) { + return options.disabledDates[testDate.format('YYYY-MM-DD')] === true; + }, + + isInEnabledDates = function (testDate) { + return options.enabledDates[testDate.format('YYYY-MM-DD')] === true; + }, + + isValid = function (targetMoment, granularity) { + if (!targetMoment.isValid()) { + return false; + } + if (options.disabledDates && isInDisabledDates(targetMoment)) { + return false; + } + if (options.enabledDates) { + if (isInEnabledDates(targetMoment)) { + return true; + } else { + return false; + } + } + if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) { + return false; + } + if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) { + return false; + } + if (granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { + return false; + } + return true; + }, + + fillMonths = function () { + var spans = [], + monthsShort = viewDate.clone().startOf('y').hour(12); // hour is changed to avoid DST issues in some browsers + while (monthsShort.isSame(viewDate, 'y')) { + spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); + monthsShort.add(1, 'M'); + } + widget.find('.datepicker-months td').empty().append(spans); + }, + + updateMonths = function () { + var monthsView = widget.find('.datepicker-months'), + monthsViewHeader = monthsView.find('th'), + months = monthsView.find('tbody').find('span'); + + monthsView.find('.disabled').removeClass('disabled'); + + if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) { + monthsViewHeader.eq(0).addClass('disabled'); + } + + monthsViewHeader.eq(1).text(viewDate.year()); + + if (!isValid(viewDate.clone().add(1, 'y'), 'y')) { + monthsViewHeader.eq(2).addClass('disabled'); + } + + months.removeClass('active'); + if (date.isSame(viewDate, 'y')) { + months.eq(date.month()).addClass('active'); + } + + months.each(function (index) { + if (!isValid(viewDate.clone().month(index), 'M')) { + $(this).addClass('disabled'); + } + }); + }, + + updateYears = function () { + var yearsView = widget.find('.datepicker-years'), + yearsViewHeader = yearsView.find('th'), + startYear = viewDate.clone().subtract(5, 'y'), + endYear = viewDate.clone().add(6, 'y'), + html = ''; + + yearsView.find('.disabled').removeClass('disabled'); + + if (options.minDate && options.minDate.isAfter(startYear, 'y')) { + yearsViewHeader.eq(0).addClass('disabled'); + } + + yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year()); + + if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) { + yearsViewHeader.eq(2).addClass('disabled'); + } + + while (!startYear.isAfter(endYear, 'y')) { + html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>'; + startYear.add(1, 'y'); + } + + yearsView.find('td').html(html); + }, + + fillDate = function () { + var daysView = widget.find('.datepicker-days'), + daysViewHeader = daysView.find('th'), + currentDate, + html = [], + row, + clsName; + + if (!hasDate()) { + return; + } + + daysView.find('.disabled').removeClass('disabled'); + daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat)); + + if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) { + daysViewHeader.eq(0).addClass('disabled'); + } + if (!isValid(viewDate.clone().add(1, 'M'), 'M')) { + daysViewHeader.eq(2).addClass('disabled'); + } + + currentDate = viewDate.clone().startOf('M').startOf('week'); + + while (!viewDate.clone().endOf('M').endOf('w').isBefore(currentDate, 'd')) { + if (currentDate.weekday() === 0) { + row = $('<tr>'); + if (options.calendarWeeks) { + row.append('<td class="cw">' + currentDate.week() + '</td>'); + } + html.push(row); + } + clsName = ''; + if (currentDate.isBefore(viewDate, 'M')) { + clsName += ' old'; + } + if (currentDate.isAfter(viewDate, 'M')) { + clsName += ' new'; + } + if (currentDate.isSame(date, 'd') && !unset) { + clsName += ' active'; + } + if (!isValid(currentDate, 'd')) { + clsName += ' disabled'; + } + if (currentDate.isSame(moment(), 'd')) { + clsName += ' today'; + } + if (currentDate.day() === 0 || currentDate.day() === 6) { + clsName += ' weekend'; + } + row.append('<td data-action="selectDay" class="day' + clsName + '">' + currentDate.date() + '</td>'); + currentDate.add(1, 'd'); + } + + daysView.find('tbody').empty().append(html); + + updateMonths(); + + updateYears(); + }, + + fillHours = function () { + var table = widget.find('.timepicker-hours table'), + currentHour = viewDate.clone().startOf('d'), + html = [], + row = $('<tr>'); + + if (viewDate.hour() > 11 && !use24Hours) { + currentHour.hour(12); + } + while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) { + if (currentHour.hour() % 4 === 0) { + row = $('<tr>'); + html.push(row); + } + row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>'); + currentHour.add(1, 'h'); + } + table.empty().append(html); + }, + + fillMinutes = function () { + var table = widget.find('.timepicker-minutes table'), + currentMinute = viewDate.clone().startOf('h'), + html = [], + row = $('<tr>'), + step = options.stepping === 1 ? 5 : options.stepping; + + while (viewDate.isSame(currentMinute, 'h')) { + if (currentMinute.minute() % (step * 4) === 0) { + row = $('<tr>'); + html.push(row); + } + row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>'); + currentMinute.add(step, 'm'); + } + table.empty().append(html); + }, + + fillSeconds = function () { + var table = widget.find('.timepicker-seconds table'), + currentSecond = viewDate.clone().startOf('m'), + html = [], + row = $('<tr>'); + + while (viewDate.isSame(currentSecond, 'm')) { + if (currentSecond.second() % 20 === 0) { + row = $('<tr>'); + html.push(row); + } + row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>'); + currentSecond.add(5, 's'); + } + + table.empty().append(html); + }, + + fillTime = function () { + var timeComponents = widget.find('.timepicker span[data-time-component]'); + if (!use24Hours) { + widget.find('.timepicker [data-action=togglePeriod]').text(date.format('A')); + } + timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh')); + timeComponents.filter('[data-time-component=minutes]').text(date.format('mm')); + timeComponents.filter('[data-time-component=seconds]').text(date.format('ss')); + + fillHours(); + fillMinutes(); + fillSeconds(); + }, + + update = function () { + if (!widget) { + return; + } + fillDate(); + fillTime(); + }, + + setValue = function (targetMoment) { + var oldDate = unset ? null : date; + + // case of calling setValue(null or false) + if (!targetMoment) { + unset = true; + input.val(''); + element.data('date', ''); + notifyEvent({ + type: 'dp.change', + date: null, + oldDate: oldDate + }); + update(); + return; + } + + targetMoment = targetMoment.clone().locale(options.locale); + + if (options.stepping !== 1) { + targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0); + } + + if (isValid(targetMoment)) { + date = targetMoment; + viewDate = date.clone(); + input.val(date.format(actualFormat)); + element.data('date', date.format(actualFormat)); + update(); + unset = false; + notifyEvent({ + type: 'dp.change', + date: date.clone(), + oldDate: oldDate + }); + } else { + input.val(unset ? '' : date.format(actualFormat)); + notifyEvent({ + type: 'dp.error', + date: targetMoment + }); + } + }, + + hide = function () { + var transitioning = false; + if (!widget) { + return picker; + } + // Ignore event if in the middle of a picker transition + widget.find('.collapse').each(function () { + var collapseData = $(this).data('collapse'); + if (collapseData && collapseData.transitioning) { + transitioning = true; + return false; + } + return true; + }); + if (transitioning) { + return picker; + } + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + widget.hide(); + + $(window).off('resize', place); + widget.off('click', '[data-action]'); + widget.off('mousedown', false); + + widget.remove(); + widget = false; + + notifyEvent({ + type: 'dp.hide', + date: date.clone() + }); + return picker; + }, + + clear = function () { + setValue(null); + }, + + /******************************************************************************** + * + * Widget UI interaction functions + * + ********************************************************************************/ + actions = { + next: function () { + viewDate.add(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc); + fillDate(); + }, + + previous: function () { + viewDate.subtract(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc); + fillDate(); + }, + + pickerSwitch: function () { + showMode(1); + }, + + selectMonth: function (e) { + var month = $(e.target).closest('tbody').find('span').index($(e.target)); + viewDate.month(month); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year()).month(viewDate.month())); + hide(); + } else { + showMode(-1); + fillDate(); + } + }, + + selectYear: function (e) { + var year = parseInt($(e.target).text(), 10) || 0; + viewDate.year(year); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year())); + hide(); + } else { + showMode(-1); + fillDate(); + } + }, + + selectDay: function (e) { + var day = viewDate.clone(); + if ($(e.target).is('.old')) { + day.subtract(1, 'M'); + } + if ($(e.target).is('.new')) { + day.add(1, 'M'); + } + setValue(day.date(parseInt($(e.target).text(), 10))); + if (!hasTime() && !options.keepOpen) { + hide(); + } + }, + + incrementHours: function () { + setValue(date.clone().add(1, 'h')); + }, + + incrementMinutes: function () { + setValue(date.clone().add(options.stepping, 'm')); + }, + + incrementSeconds: function () { + setValue(date.clone().add(1, 's')); + }, + + decrementHours: function () { + setValue(date.clone().subtract(1, 'h')); + }, + + decrementMinutes: function () { + setValue(date.clone().subtract(options.stepping, 'm')); + }, + + decrementSeconds: function () { + setValue(date.clone().subtract(1, 's')); + }, + + togglePeriod: function () { + setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h')); + }, + + togglePicker: function (e) { + var $this = $(e.target), + $parent = $this.closest('ul'), + expanded = $parent.find('.in'), + closed = $parent.find('.collapse:not(.in)'), + collapseData; + + if (expanded && expanded.length) { + collapseData = expanded.data('collapse'); + if (collapseData && collapseData.transitioning) { + return; + } + if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it + expanded.collapse('hide'); + closed.collapse('show'); + } else { // otherwise just toggle in class on the two views + expanded.removeClass('in'); + closed.addClass('in'); + } + if ($this.is('span')) { + $this.toggleClass(options.icons.time + ' ' + options.icons.date); + } else { + $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); + } + + // NOTE: uncomment if toggled state will be restored in show() + //if (component) { + // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date); + //} + } + }, + + showPicker: function () { + widget.find('.timepicker > div:not(.timepicker-picker)').hide(); + widget.find('.timepicker .timepicker-picker').show(); + }, + + showHours: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-hours').show(); + }, + + showMinutes: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-minutes').show(); + }, + + showSeconds: function () { + widget.find('.timepicker .timepicker-picker').hide(); + widget.find('.timepicker .timepicker-seconds').show(); + }, + + selectHour: function (e) { + var hour = parseInt($(e.target).text(), 10); + + if (!use24Hours) { + if (date.hours() >= 12) { + if (hour !== 12) { + hour += 12; + } + } else { + if (hour === 12) { + hour = 0; + } + } + } + setValue(date.clone().hours(hour)); + actions.showPicker.call(picker); + }, + + selectMinute: function (e) { + setValue(date.clone().minutes(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + + selectSecond: function (e) { + setValue(date.clone().seconds(parseInt($(e.target).text(), 10))); + actions.showPicker.call(picker); + }, + + clear: clear, + + today: function () { + setValue(moment()); + } + }, + + doAction = function (e) { + if ($(e.currentTarget).is('.disabled')) { + return false; + } + actions[$(e.currentTarget).data('action')].apply(picker, arguments); + return false; + }, + + show = function () { + var currentMoment, + useCurrentGranularity = { + 'year': function (m) { + return m.month(0).date(1).hours(0).seconds(0).minutes(0); + }, + 'month': function (m) { + return m.date(1).hours(0).seconds(0).minutes(0); + }, + 'day': function (m) { + return m.hours(0).seconds(0).minutes(0); + }, + 'hour': function (m) { + return m.seconds(0).minutes(0); + }, + 'minute': function (m) { + return m.seconds(0); + } + }; + + if ((options.disallowReadOnly && (input.prop('disabled') || input.prop('readonly'))) || widget) { + return picker; + } + if (options.useCurrent && unset && (input.is('input') && input.val().trim().length === 0)) { + currentMoment = moment(); + if (typeof options.useCurrent === 'string') { + currentMoment = useCurrentGranularity[options.useCurrent](currentMoment); + } + setValue(currentMoment); + } + + widget = getTemplate(); + + fillDow(); + fillMonths(); + + widget.find('.timepicker-hours').hide(); + widget.find('.timepicker-minutes').hide(); + widget.find('.timepicker-seconds').hide(); + + update(); + showMode(); + + $(window).on('resize', place); + widget.on('click', '[data-action]', doAction); // this handles clicks on the widget + widget.on('mousedown', false); + + if (component && component.hasClass('btn')) { + component.toggleClass('active'); + } + widget.show(); + place(); + + if (!input.is(':focus')) { + input.focus(); + } + + notifyEvent({ + type: 'dp.show' + }); + return picker; + }, + + toggle = function () { + return (widget ? hide() : show()); + }, + + parseInputDate = function (inputDate) { + if (moment.isMoment(inputDate) || inputDate instanceof Date) { + inputDate = moment(inputDate); + } else { + inputDate = moment(inputDate, parseFormats, options.useStrict); + } + inputDate.locale(options.locale); + return inputDate; + }, + + keydown = function (e) { + //if (e.keyCode === 27 && widget) { // allow escape to hide picker + // hide(); + // return false; + //} + //if (e.keyCode === 40 && !widget) { // allow down to show picker + // show(); + // e.preventDefault(); + //} + //return true; + + var handler = null, + index, + index2, + pressedKeys = [], + pressedModifiers = {}, + currentKey = e.which, + keyBindKeys, + allModifiersPressed, + pressed = 'p'; + + keyState[currentKey] = pressed; + + for (index in keyState) { + if (keyState.hasOwnProperty(index) && keyState[index] === pressed) { + pressedKeys.push(index); + if (parseInt(index, 10) !== currentKey) { + pressedModifiers[index] = true; + } + } + } + + for (index in options.keyBinds) { + if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') { + keyBindKeys = index.split(' '); + if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) { + allModifiersPressed = true; + for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) { + if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) { + allModifiersPressed = false; + break; + } + } + if (allModifiersPressed) { + handler = options.keyBinds[index]; + break; + } + } + } + } + + if (handler) { + handler.call(picker, widget); + e.stopPropagation(); + e.preventDefault(); + } + }, + + keyup = function (e) { + keyState[e.which] = 'r'; + e.stopPropagation(); + e.preventDefault(); + }, + + change = function (e) { + var val = $(e.target).val().trim(), + parsedDate = val ? parseInputDate(val) : null; + setValue(parsedDate); + e.stopImmediatePropagation(); + return false; + }, + + attachDatePickerElementEvents = function () { + input.on({ + 'change': change, + 'blur': hide, + 'keydown': keydown, + 'keyup': keyup + }); + + if (element.is('input')) { + input.on({ + 'focus': show + }); + } else if (component) { + component.on('click', toggle); + component.on('mousedown', false); + } + }, + + detachDatePickerElementEvents = function () { + input.off({ + 'change': change, + 'blur': hide, + 'keydown': keydown, + 'keyup': keyup + }); + + if (element.is('input')) { + input.off({ + 'focus': show + }); + } else if (component) { + component.off('click', toggle); + component.off('mousedown', false); + } + }, + + indexGivenDates = function (givenDatesArray) { + // Store given enabledDates and disabledDates as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledDates['2014-02-27'] === true) + var givenDatesIndexed = {}; + $.each(givenDatesArray, function () { + var dDate = parseInputDate(this); + if (dDate.isValid()) { + givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true; + } + }); + return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false; + }, + + initFormatting = function () { + var format = options.format || 'L LT'; + + actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { + var newinput = moment().localeData().longDateFormat(formatInput) || formatInput; + return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740 + return moment().localeData().longDateFormat(formatInput2) || formatInput2; + }); + }); + + + parseFormats = options.extraFormats ? options.extraFormats.slice() : []; + if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) { + parseFormats.push(actualFormat); + } + + use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.indexOf('h') < 1); + + if (isEnabled('y')) { + minViewModeNumber = 2; + } + if (isEnabled('M')) { + minViewModeNumber = 1; + } + if (isEnabled('d')) { + minViewModeNumber = 0; + } + + currentViewMode = Math.max(minViewModeNumber, currentViewMode); + + if (!unset) { + setValue(date); + } + }; + + /******************************************************************************** + * + * Public API functions + * ===================== + * + * Important: Do not expose direct references to private objects or the options + * object to the outer world. Always return a clone when returning values or make + * a clone when setting a private variable. + * + ********************************************************************************/ + picker.destroy = function () { + hide(); + detachDatePickerElementEvents(); + element.removeData('DateTimePicker'); + element.removeData('date'); + }; + + picker.toggle = toggle; + + picker.show = show; + + picker.hide = hide; + + picker.disable = function () { + hide(); + if (component && component.hasClass('btn')) { + component.addClass('disabled'); + } + input.prop('disabled', true); + return picker; + }; + + picker.enable = function () { + if (component && component.hasClass('btn')) { + component.removeClass('disabled'); + } + input.prop('disabled', false); + return picker; + }; + + picker.disallowReadOnly = function (disallowReadOnly) { + if (arguments.length === 0) { + return options.disallowReadOnly; + } + if (typeof disallowReadOnly !== 'boolean') { + throw new TypeError('disallowReadOnly() expects a boolean parameter'); + } + options.disallowReadOnly = disallowReadOnly; + return picker; + }; + + picker.options = function (newOptions) { + if (arguments.length === 0) { + return $.extend(true, {}, options); + } + + if (!(newOptions instanceof Object)) { + throw new TypeError('options() options parameter should be an object'); + } + $.extend(true, options, newOptions); + $.each(options, function (key, value) { + if (picker[key] !== undefined) { + picker[key](value); + } else { + throw new TypeError('option ' + key + ' is not recognized!'); + } + }); + return picker; + }; + + picker.date = function (newDate) { + if (arguments.length === 0) { + if (unset) { + return null; + } + return date.clone(); + } + + if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('date() parameter must be one of [null, string, moment or Date]'); + } + + setValue(newDate === null ? null : parseInputDate(newDate)); + return picker; + }; + + picker.format = function (newFormat) { + if (arguments.length === 0) { + return options.format; + } + + if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) { + throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat); + } + + options.format = newFormat; + if (actualFormat) { + initFormatting(); // reinit formatting + } + return picker; + }; + + picker.dayViewHeaderFormat = function (newFormat) { + if (arguments.length === 0) { + return options.dayViewHeaderFormat; + } + + if (typeof newFormat !== 'string') { + throw new TypeError('dayViewHeaderFormat() expects a string parameter'); + } + + options.dayViewHeaderFormat = newFormat; + return picker; + }; + + picker.extraFormats = function (formats) { + if (arguments.length === 0) { + return options.extraFormats; + } + + if (formats !== false && !(formats instanceof Array)) { + throw new TypeError('extraFormats() expects an array or false parameter'); + } + + options.extraFormats = formats; + if (parseFormats) { + initFormatting(); // reinit formatting + } + return picker; + }; + + picker.disabledDates = function (dates) { + if (arguments.length === 0) { + return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates); + } + + if (!dates) { + options.disabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('disabledDates() expects an array parameter'); + } + options.disabledDates = indexGivenDates(dates); + options.enabledDates = false; + update(); + return picker; + }; + + picker.enabledDates = function (dates) { + if (arguments.length === 0) { + return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates); + } + + if (!dates) { + options.enabledDates = false; + update(); + return picker; + } + if (!(dates instanceof Array)) { + throw new TypeError('enabledDates() expects an array parameter'); + } + options.enabledDates = indexGivenDates(dates); + options.disabledDates = false; + update(); + return picker; + }; + + picker.daysOfWeekDisabled = function (daysOfWeekDisabled) { + if (arguments.length === 0) { + return options.daysOfWeekDisabled.splice(0); + } + + if (!(daysOfWeekDisabled instanceof Array)) { + throw new TypeError('daysOfWeekDisabled() expects an array parameter'); + } + options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) { + currentValue = parseInt(currentValue, 10); + if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) { + return previousValue; + } + if (previousValue.indexOf(currentValue) === -1) { + previousValue.push(currentValue); + } + return previousValue; + }, []).sort(); + update(); + return picker; + }; + + picker.maxDate = function (maxDate) { + if (arguments.length === 0) { + return options.maxDate ? options.maxDate.clone() : options.maxDate; + } + + if ((typeof maxDate === 'boolean') && maxDate === false) { + options.maxDate = false; + update(); + return picker; + } + + var parsedDate = parseInputDate(maxDate); + + if (!parsedDate.isValid()) { + throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate); + } + if (options.minDate && parsedDate.isBefore(options.minDate)) { + throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat)); + } + options.maxDate = parsedDate; + if (options.maxDate.isBefore(maxDate)) { + setValue(options.maxDate); + } + if (viewDate.isAfter(parsedDate)) { + viewDate = parsedDate; + } + update(); + return picker; + }; + + picker.minDate = function (minDate) { + if (arguments.length === 0) { + return options.minDate ? options.minDate.clone() : options.minDate; + } + + if ((typeof minDate === 'boolean') && minDate === false) { + options.minDate = false; + update(); + return picker; + } + + var parsedDate = parseInputDate(minDate); + + if (!parsedDate.isValid()) { + throw new TypeError('minDate() Could not parse date parameter: ' + minDate); + } + if (options.maxDate && parsedDate.isAfter(options.maxDate)) { + throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat)); + } + options.minDate = parsedDate; + if (options.minDate.isAfter(minDate)) { + setValue(options.minDate); + } + if (viewDate.isBefore(parsedDate)) { + viewDate = parsedDate; + } + update(); + return picker; + }; + + picker.defaultDate = function (defaultDate) { + if (arguments.length === 0) { + return options.defaultDate ? options.defaultDate.clone() : options.defaultDate; + } + if (!defaultDate) { + options.defaultDate = false; + return picker; + } + var parsedDate = parseInputDate(defaultDate); + if (!parsedDate.isValid()) { + throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate); + } + if (!isValid(parsedDate)) { + throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); + } + + options.defaultDate = parsedDate; + + if (options.defaultDate && input.val().trim() === '') { + setValue(options.defaultDate); + } + return picker; + }; + + picker.locale = function (locale) { + if (arguments.length === 0) { + return options.locale; + } + + if (!moment.localeData(locale)) { + throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!'); + } + + options.locale = locale; + date.locale(options.locale); + viewDate.locale(options.locale); + + if (actualFormat) { + initFormatting(); // reinit formatting + } + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.stepping = function (stepping) { + if (arguments.length === 0) { + return options.stepping; + } + + stepping = parseInt(stepping, 10); + if (isNaN(stepping) || stepping < 1) { + stepping = 1; + } + options.stepping = stepping; + return picker; + }; + + picker.useCurrent = function (useCurrent) { + var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute']; + if (arguments.length === 0) { + return options.useCurrent; + } + + if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) { + throw new TypeError('useCurrent() expects a boolean or string parameter'); + } + if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) { + throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', ')); + } + options.useCurrent = useCurrent; + return picker; + }; + + picker.collapse = function (collapse) { + if (arguments.length === 0) { + return options.collapse; + } + + if (typeof collapse !== 'boolean') { + throw new TypeError('collapse() expects a boolean parameter'); + } + if (options.collapse === collapse) { + return picker; + } + options.collapse = collapse; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.icons = function (icons) { + if (arguments.length === 0) { + return $.extend({}, options.icons); + } + + if (!(icons instanceof Object)) { + throw new TypeError('icons() expects parameter to be an Object'); + } + $.extend(options.icons, icons); + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.useStrict = function (useStrict) { + if (arguments.length === 0) { + return options.useStrict; + } + + if (typeof useStrict !== 'boolean') { + throw new TypeError('useStrict() expects a boolean parameter'); + } + options.useStrict = useStrict; + return picker; + }; + + picker.sideBySide = function (sideBySide) { + if (arguments.length === 0) { + return options.sideBySide; + } + + if (typeof sideBySide !== 'boolean') { + throw new TypeError('sideBySide() expects a boolean parameter'); + } + options.sideBySide = sideBySide; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.viewMode = function (viewMode) { + if (arguments.length === 0) { + return options.viewMode; + } + + if (typeof viewMode !== 'string') { + throw new TypeError('viewMode() expects a string parameter'); + } + + if (viewModes.indexOf(viewMode) === -1) { + throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value'); + } + + options.viewMode = viewMode; + currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber); + + showMode(); + return picker; + }; + + picker.toolbarPlacement = function (toolbarPlacement) { + if (arguments.length === 0) { + return options.toolbarPlacement; + } + + if (typeof toolbarPlacement !== 'string') { + throw new TypeError('toolbarPlacement() expects a string parameter'); + } + if (toolbarPlacements.indexOf(toolbarPlacement) === -1) { + throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value'); + } + options.toolbarPlacement = toolbarPlacement; + + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.widgetPositioning = function (widgetPositioning) { + if (arguments.length === 0) { + return $.extend({}, options.widgetPositioning); + } + + if (({}).toString.call(widgetPositioning) !== '[object Object]') { + throw new TypeError('widgetPositioning() expects an object variable'); + } + if (widgetPositioning.horizontal) { + if (typeof widgetPositioning.horizontal !== 'string') { + throw new TypeError('widgetPositioning() horizontal variable must be a string'); + } + widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase(); + if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) { + throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')'); + } + options.widgetPositioning.horizontal = widgetPositioning.horizontal; + } + if (widgetPositioning.vertical) { + if (typeof widgetPositioning.vertical !== 'string') { + throw new TypeError('widgetPositioning() vertical variable must be a string'); + } + widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase(); + if (verticalModes.indexOf(widgetPositioning.vertical) === -1) { + throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')'); + } + options.widgetPositioning.vertical = widgetPositioning.vertical; + } + update(); + return picker; + }; + + picker.calendarWeeks = function (calendarWeeks) { + if (arguments.length === 0) { + return options.calendarWeeks; + } + + if (typeof calendarWeeks !== 'boolean') { + throw new TypeError('calendarWeeks() expects parameter to be a boolean value'); + } + + options.calendarWeeks = calendarWeeks; + update(); + return picker; + }; + + picker.showTodayButton = function (showTodayButton) { + if (arguments.length === 0) { + return options.showTodayButton; + } + + if (typeof showTodayButton !== 'boolean') { + throw new TypeError('showTodayButton() expects a boolean parameter'); + } + + options.showTodayButton = showTodayButton; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.showClear = function (showClear) { + if (arguments.length === 0) { + return options.showClear; + } + + if (typeof showClear !== 'boolean') { + throw new TypeError('showClear() expects a boolean parameter'); + } + + options.showClear = showClear; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.widgetParent = function (widgetParent) { + if (arguments.length === 0) { + return options.widgetParent; + } + + if (typeof widgetParent === 'string') { + widgetParent = $(widgetParent); + } + + if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) { + throw new TypeError('widgetParent() expects a string or a jQuery object parameter'); + } + + options.widgetParent = widgetParent; + if (widget) { + hide(); + show(); + } + return picker; + }; + + picker.keepOpen = function (keepOpen) { + if (arguments.length === 0) { + return options.keepOpen; + } + + if (typeof keepOpen !== 'boolean') { + throw new TypeError('keepOpen() expects a boolean parameter'); + } + + options.keepOpen = keepOpen; + return picker; + }; + + picker.inline = function (inline) { + if (arguments.length === 0) { + return options.inline; + } + + if (typeof inline !== 'boolean') { + throw new TypeError('inline() expects a boolean parameter'); + } + + options.inline = inline; + return picker; + }; + + picker.clear = function () { + clear(); + return picker; + }; + + picker.keyBinds = function (keyBinds) { + options.keyBinds = keyBinds; + return picker; + }; + + // initializing element and component attributes + if (element.is('input')) { + input = element; + } else { + input = element.find('.datepickerinput'); + if (input.size() === 0) { + input = element.find('input'); + } else if (!input.is('input')) { + throw new Error('CSS class "datepickerinput" cannot be applied to non input element'); + } + } + + if (element.hasClass('input-group')) { + // in case there is more then one 'input-group-addon' Issue #48 + if (element.find('.datepickerbutton').size() === 0) { + component = element.find('[class^="input-group-"]'); + } else { + component = element.find('.datepickerbutton'); + } + } + + if (!options.inline && !input.is('input')) { + throw new Error('Could not initialize DateTimePicker without an input element'); + } + + $.extend(true, options, dataToOptions()); + + picker.options(options); + + initFormatting(); + + attachDatePickerElementEvents(); + + if (input.prop('disabled')) { + picker.disable(); + } + + if (input.is('input') && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } else if (options.defaultDate) { + setValue(options.defaultDate); + } + if (options.inline) { + show(); + } + return picker; + }; + + /******************************************************************************** + * + * jQuery plugin constructor and defaults object + * + ********************************************************************************/ + + $.fn.datetimepicker = function (options) { + return this.each(function () { + var $this = $(this); + if (!$this.data('DateTimePicker')) { + // create a private copy of the defaults object + options = $.extend(true, {}, $.fn.datetimepicker.defaults, options); + $this.data('DateTimePicker', dateTimePicker($this, options)); + } + }); + }; + + $.fn.datetimepicker.defaults = { + format: false, + dayViewHeaderFormat: 'MMMM YYYY', + extraFormats: false, + stepping: 1, + minDate: false, + maxDate: false, + useCurrent: true, + collapse: true, + locale: moment.locale(), + defaultDate: false, + disabledDates: false, + enabledDates: false, + icons: { + time: 'glyphicon glyphicon-time', + date: 'glyphicon glyphicon-calendar', + up: 'glyphicon glyphicon-chevron-up', + down: 'glyphicon glyphicon-chevron-down', + previous: 'glyphicon glyphicon-chevron-left', + next: 'glyphicon glyphicon-chevron-right', + today: 'glyphicon glyphicon-screenshot', + clear: 'glyphicon glyphicon-trash' + }, + useStrict: false, + sideBySide: false, + daysOfWeekDisabled: [], + calendarWeeks: false, + viewMode: 'days', + toolbarPlacement: 'default', + showTodayButton: false, + showClear: false, + widgetPositioning: { + horizontal: 'auto', + vertical: 'auto' + }, + widgetParent: null, + disallowReadOnly: true, + keepOpen: false, + inline: false, + keyBinds: { + up: function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().subtract(7, 'd')); + } else { + this.date(this.date().clone().add(1, 'm')); + } + }, + down: function (widget) { + if (!widget) { + this.show(); + } + else if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().add(7, 'd')); + } else { + this.date(this.date().clone().subtract(1, 'm')); + } + }, + 'control up': function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().subtract(1, 'y')); + } else { + this.date(this.date().clone().add(1, 'h')); + } + }, + 'control down': function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().add(1, 'y')); + } else { + this.date(this.date().clone().subtract(1, 'h')); + } + }, + left: function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().subtract(1, 'd')); + } + }, + right: function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().add(1, 'd')); + } + }, + pageUp: function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().subtract(1, 'M')); + } + }, + pageDown: function (widget) { + if (widget.find('.datepicker').is(':visible')) { + this.date(this.date().clone().add(1, 'M')); + } + }, + enter: function () { + this.hide(); + }, + escape: function () { + this.hide(); + }, + tab: function (widget) { + widget.find('.picker-switch a[data-action="togglePicker"]').click(); + }, + 'control space': function (widget) { + if (widget.find('.timepicker').is(':visible')) { + widget.find('.btn[data-action="togglePeriod"]').click(); + } + }, + t: function () { + this.date(moment()); + }, + 'delete': function () { + this.clear(); + } + + } + }; +})); diff --git a/vendor/assets/javascripts/pickers.js b/vendor/assets/javascripts/pickers.js new file mode 100644 index 00000000..b1b57d1d --- /dev/null +++ b/vendor/assets/javascripts/pickers.js @@ -0,0 +1,47 @@ +var default_picker_options = { + icons: { + date: 'fa fa-calendar', + time: 'fa fa-clock-o', + up: 'fa fa-chevron-up', + down: 'fa fa-chevron-down', + previous: 'fa fa-chevron-left', + next: 'fa fa-chevron-right', + today: 'fa fa-crosshairs', + clear: 'fa fa-trash-o' + } + +} + +$(document).on('ready page:change', function() { + $('.datetimepicker').datetimepicker(default_picker_options); + + $('.timepicker').datetimepicker(default_picker_options); + + $('.datepicker').datetimepicker(default_picker_options); + + $('.datetimerange').each(function(){ + var $this = $(this) + var range1 = $($this.find('.input-group')[0]) + var range2 = $($this.find('.input-group')[1]) + + if(range1.data("DateTimePicker").date() != null) + range2.data("DateTimePicker").minDate(range1.data("DateTimePicker").date()); + + if(range2.data("DateTimePicker").date() != null) + range1.data("DateTimePicker").maxDate(range2.data("DateTimePicker").date()); + + range1.on("dp.change",function (e) { + if(e.date) + range2.data("DateTimePicker").minDate(e.date); + else + range2.data("DateTimePicker").minDate(false); + }); + + range2.on("dp.change",function (e) { + if(e.date) + range1.data("DateTimePicker").maxDate(e.date); + else + range1.data("DateTimePicker").maxDate(false); + }); + }) +}); diff --git a/vendor/assets/stylesheets/bootstrap-datetimepicker.css b/vendor/assets/stylesheets/bootstrap-datetimepicker.css new file mode 100644 index 00000000..f810a7dd --- /dev/null +++ b/vendor/assets/stylesheets/bootstrap-datetimepicker.css @@ -0,0 +1,366 @@ +/*! + * Datetimepicker for Bootstrap 3 +//! version : 4.3.5 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: 0 0 7px 7px solid transparent; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/vendor/assets/stylesheets/bootstrap-datetimepicker.min.css b/vendor/assets/stylesheets/bootstrap-datetimepicker.min.css new file mode 100644 index 00000000..f810a7dd --- /dev/null +++ b/vendor/assets/stylesheets/bootstrap-datetimepicker.min.css @@ -0,0 +1,366 @@ +/*! + * Datetimepicker for Bootstrap 3 +//! version : 4.3.5 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: 0 0 7px 7px solid transparent; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} From 8410224f476b36ea73151d6cf75db64bab09fd33 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 13:41:30 +0300 Subject: [PATCH 117/227] fix wrong form for create news --- app/views/web/admin/news/_form.html.haml | 2 ++ test/factories/news.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index f0ea14af..0e8e738e 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -6,6 +6,8 @@ = f.input :title, as: :string = f.input :body, as: :string = f.input :published_at, as: :datetime_picker + = f.input :author_id, as: :string /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } = f.button :submit, class: 'btn-success' + = link_to t('helpers.links.back'), admin_news_index_path, class: 'btn btn-default' diff --git a/test/factories/news.rb b/test/factories/news.rb index 8793ef7e..30303fa2 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -4,6 +4,6 @@ body { generate :string } published_at { DateTime.now } photo { generate :file } - author_id { generate :number } + author_id { "1" } end end From fdafd7a9af26892242b3d82a35e9fde37a9ae110 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 5 Mar 2015 13:48:52 +0300 Subject: [PATCH 118/227] AHAHHAHAHAH I'M WIN WRONG TEST! AHAHHAHA --- test/controllers/web/admin/news_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb index ebaa71ed..5e16c00c 100644 --- a/test/controllers/web/admin/news_controller_test.rb +++ b/test/controllers/web/admin/news_controller_test.rb @@ -22,7 +22,7 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase test 'should create news' do attributes = attributes_for :news - post :create, user: attributes + post :create, news: attributes assert_response :redirect, @response.body assert_redirected_to admin_news_index_path assert_equal attributes[:body], News.last.body From 3f28fe89fd7708e2f42dc5a60f48da2e4bb09a84 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 00:49:27 +0400 Subject: [PATCH 119/227] update gitignore --- .gitignore | 28 +++++++++++++++++++++++++--- db/schema.rb | 3 +++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 14477626..f8741e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,31 @@ # Ignore all logfiles and tempfiles. /log/* !/log/.keep -/tmp config/secrets.yml config/oauth.yml ulmic_test -Gemfile.lock -/public +/.bundle/db/*.sqlite3 +/log/*.log +/tmp +*.rbc +*.sassc +.sass-cache +capybara-*.html +.rspec +.rvmrc +/.bundle +/vendor/bundle +/tmp/* +/db/*.sqlite3 +/public/system/* +/public/uploads/* +/public/assets/* +/coverage/ +/spec/tmp/* +**.orig +rerun.txt +pickle-email-*.html +.project +config/initializers/secret_token.rb +config/database.yml +Gemfile.lock diff --git a/db/schema.rb b/db/schema.rb index 84506820..15982847 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,6 +13,9 @@ ActiveRecord::Schema.define(version: 20150303003907) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + create_table "authentications", force: :cascade do |t| t.text "provider" t.text "uid" From bf39d446d47f918c95499d6ac9e233b09347fd91 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 6 Mar 2015 03:29:39 +0300 Subject: [PATCH 120/227] add join controller --- app/assets/javascripts/web/join.coffee | 3 +++ app/assets/stylesheets/web/join.scss | 3 +++ app/controllers/web/join_controller.rb | 19 ++++++++++++++++++ app/decorators/web/join_decorator.rb | 13 ++++++++++++ app/helpers/web/join_helper.rb | 2 ++ app/models/member.rb | 1 + app/views/web/join/new.html.haml | 15 ++++++++++++++ config/routes.rb | 1 + test/controllers/web/join_controller_test.rb | 21 ++++++++++++++++++++ test/decorators/web/join_decorator_test.rb | 4 ++++ 10 files changed, 82 insertions(+) create mode 100644 app/assets/javascripts/web/join.coffee create mode 100644 app/assets/stylesheets/web/join.scss create mode 100644 app/controllers/web/join_controller.rb create mode 100644 app/decorators/web/join_decorator.rb create mode 100644 app/helpers/web/join_helper.rb create mode 100644 app/views/web/join/new.html.haml create mode 100644 test/controllers/web/join_controller_test.rb create mode 100644 test/decorators/web/join_decorator_test.rb diff --git a/app/assets/javascripts/web/join.coffee b/app/assets/javascripts/web/join.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/web/join.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/web/join.scss b/app/assets/stylesheets/web/join.scss new file mode 100644 index 00000000..a423627f --- /dev/null +++ b/app/assets/stylesheets/web/join.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the web/join 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/web/join_controller.rb b/app/controllers/web/join_controller.rb new file mode 100644 index 00000000..54bb8e15 --- /dev/null +++ b/app/controllers/web/join_controller.rb @@ -0,0 +1,19 @@ +class Web::JoinController < Web::ApplicationController + before_filter :authenticate_user!, only: [ :new, :create ] + + def new + @member = Member.new + @member_form = MemberForm.new @member + end + + def create + @member = Member.new + @member_form = MemberForm.new @member + @member_form.submit params[:member] + if @member_form.save + redirect_to root_path + else + render action: :new + end + end +end diff --git a/app/decorators/web/join_decorator.rb b/app/decorators/web/join_decorator.rb new file mode 100644 index 00000000..4d10affa --- /dev/null +++ b/app/decorators/web/join_decorator.rb @@ -0,0 +1,13 @@ +class Web::JoinDecorator < Draper::Decorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end + +end diff --git a/app/helpers/web/join_helper.rb b/app/helpers/web/join_helper.rb new file mode 100644 index 00000000..7df32ca5 --- /dev/null +++ b/app/helpers/web/join_helper.rb @@ -0,0 +1,2 @@ +module Web::JoinHelper +end diff --git a/app/models/member.rb b/app/models/member.rb index a3a0ca20..2999c34d 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -26,6 +26,7 @@ class Member < ActiveRecord::Base state :confirmed state :declined state :removed + state :wants_to_join event :confirm do transition all => :confirmed diff --git a/app/views/web/join/new.html.haml b/app/views/web/join/new.html.haml new file mode 100644 index 00000000..e68617bb --- /dev/null +++ b/app/views/web/join/new.html.haml @@ -0,0 +1,15 @@ += simple_form_for @member, url: { controller: 'web/join', action: :create } do |f| + = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } + = f.input :patronymic, as: :string + = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } + = f.input :motto, as: :string + = f.input :ticket, as: :string + = f.input :mobile_phone, as: :string + = f.input :birth_date, as: :string + = f.input :home_adress, as: :string + = f.input :municipality, as: :string + = f.input :locality, as: :string + = f.input :avatar, as: :string + = f.input :user_id, as: :hidden, input_html: { value: current_user.id } + = f.button :submit, t('helpers.links.save'), class: 'btn-success' + = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/config/routes.rb b/config/routes.rb index 453f106e..6cd07c2c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,7 @@ get '/:ticket' => 'members#show' end end + resources :join, only: [ :new, :create ] namespace :admin do root to: 'welcome#index' resources :users diff --git a/test/controllers/web/join_controller_test.rb b/test/controllers/web/join_controller_test.rb new file mode 100644 index 00000000..87afec2e --- /dev/null +++ b/test/controllers/web/join_controller_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +class Web::JoinControllerTest < ActionController::TestCase + setup do + @member = create :member + sign_in @member.user + end + + test 'should get new' do + get :new + assert_response :success, @response.body + end + + test 'should post create' do + attributes = attributes_for :member + post :create, member: attributes + assert_response :redirect, @response.body + assert_redirected_to root_path + assert_equal attributes[:patronymic], Member.last.patronymic + end +end diff --git a/test/decorators/web/join_decorator_test.rb b/test/decorators/web/join_decorator_test.rb new file mode 100644 index 00000000..b3b413f2 --- /dev/null +++ b/test/decorators/web/join_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Web::JoinDecoratorTest < Draper::TestCase +end From 4020e3abb1d40e4b27061c2aeadbdf8c65c47359 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 6 Mar 2015 03:30:14 +0300 Subject: [PATCH 121/227] update generators --- config/application.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/application.rb b/config/application.rb index c039111c..4850c3c3 100644 --- a/config/application.rb +++ b/config/application.rb @@ -16,6 +16,13 @@ class Application < Rails::Application config.i18n.available_locales = :ru config.i18n.default_locale = :ru config.assets.initialize_on_precompile = true + config.generators do |g| + g.template_engine :haml + g.stylesheets false + g.javascripts false + g.helper false + g.decorator false + end # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. From 9c7fb058feb759885ff29eabb562ffae5914a6d6 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 6 Mar 2015 03:31:57 +0300 Subject: [PATCH 122/227] wants to join states --- app/models/member.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/member.rb b/app/models/member.rb index 2999c34d..83c3a894 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -26,7 +26,10 @@ class Member < ActiveRecord::Base state :confirmed state :declined state :removed + + #Wants to join people states state :wants_to_join + state :on_the_trial event :confirm do transition all => :confirmed @@ -40,6 +43,9 @@ class Member < ActiveRecord::Base event :restore do transition removed: :not_confirmed end + event :put_on_the_trial do + transition wants_to_join: :on_the_trial + end end attr_accessor :first_name, :last_name, :email From 48457db307ac73b3d97d95e03a1aad74ffa463f3 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 6 Mar 2015 03:48:35 +0300 Subject: [PATCH 123/227] add admin join controller --- app/controllers/web/admin/join_controller.rb | 43 +++++++++++++++++++ app/controllers/web/join_controller.rb | 12 +++--- app/views/web/admin/join/_form.html.haml | 27 ++++++++++++ app/views/web/admin/join/edit.html.haml | 1 + app/views/web/admin/join/index.html.haml | 25 +++++++++++ app/views/web/admin/join/new.html.haml | 1 + app/views/web/join/new.html.haml | 4 +- app/views/web/members/new.html.haml | 2 +- config/routes.rb | 1 + .../web/admin/join_controller_test.rb | 41 ++++++++++++++++++ test/controllers/web/join_controller_test.rb | 8 ++-- test/factories/questionaries.rb | 5 +++ 12 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 app/controllers/web/admin/join_controller.rb create mode 100644 app/views/web/admin/join/_form.html.haml create mode 100644 app/views/web/admin/join/edit.html.haml create mode 100644 app/views/web/admin/join/index.html.haml create mode 100644 app/views/web/admin/join/new.html.haml create mode 100644 test/controllers/web/admin/join_controller_test.rb create mode 100644 test/factories/questionaries.rb diff --git a/app/controllers/web/admin/join_controller.rb b/app/controllers/web/admin/join_controller.rb new file mode 100644 index 00000000..44f9c4e6 --- /dev/null +++ b/app/controllers/web/admin/join_controller.rb @@ -0,0 +1,43 @@ +class Web::Admin::JoinController < Web::Admin::ApplicationController + def index + @questionarys = ::MemberDecorator.decorate_collection questionary.presented + end + + def new + @questionary = Member.new + @questionary_form = MemberForm.new @questionary + end + + def edit + @questionary = Member.find params[:id] + @questionary_form = MemberForm.new @questionary + end + + def create + @questionary = Member.new + @questionary_form = MemberForm.new @questionary + @questionary_form.submit params[:questionary] + if @questionary_form.save + redirect_to admin_join_index_path + else + render action: :new + end + end + + def update + @questionary = Member.find params[:id] + @questionary_form = MemberForm.new @questionary + @questionary_form.submit params[:questionary] + if @questionary_form.save + redirect_to admin_join_index_path + else + render action: :edit + end + end + + def destroy + @questionary = Member.find params[:id] + @questionary.remove + redirect_to admin_join_index_path + end +end diff --git a/app/controllers/web/join_controller.rb b/app/controllers/web/join_controller.rb index 54bb8e15..6f2c9613 100644 --- a/app/controllers/web/join_controller.rb +++ b/app/controllers/web/join_controller.rb @@ -2,15 +2,15 @@ class Web::JoinController < Web::ApplicationController before_filter :authenticate_user!, only: [ :new, :create ] def new - @member = Member.new - @member_form = MemberForm.new @member + @questionary = Member.new + @questionary_form = MemberForm.new @questionary end def create - @member = Member.new - @member_form = MemberForm.new @member - @member_form.submit params[:member] - if @member_form.save + @questionary = Member.new + @questionary_form = MemberForm.new @questionary + @questionary_form.submit params[:questionary] + if @questionary_form.save redirect_to root_path else render action: :new diff --git a/app/views/web/admin/join/_form.html.haml b/app/views/web/admin/join/_form.html.haml new file mode 100644 index 00000000..0a1af767 --- /dev/null +++ b/app/views/web/admin/join/_form.html.haml @@ -0,0 +1,27 @@ +.page-header + %h1= page_title(action, Member.model_name.human) +.row + .col-lg-6 + = simple_form_for @questionary, url: { controller: 'web/admin/join', action: action }, input_html: { class: 'form-horizontal' } do |f| + - if @questionary.user.present? + = f.input :first_name, as: :string, required: true, input_html: { value: @questionary.user.first_name } + - else + = f.input :first_name, as: :string, required: true + = f.input :patronymic, as: :string + - if @questionary.user.present? + = f.input :last_name, as: :string, required: true, input_html: { value: @questionary.user.last_name } + = f.input :email, as: :string, required: true, input_html: { value: @questionary.user.email } + - else + = f.input :last_name, as: :string, required: true + = f.input :email, as: :string, required: true + = f.input :motto, as: :string + = f.input :ticket, as: :string + = f.input :mobile_phone, as: :string + = f.input :birth_date, as: :string + = f.input :home_adress, as: :string + = f.input :municipality, as: :string + = f.input :locality, as: :string + = f.input :avatar, as: :string + = f.association :user, label_method: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id, required: true + = f.button :submit, t('helpers.links.save'), class: 'btn-success' + = link_to t('helpers.links.back'), admin_join_index_path, class: 'btn btn-default' diff --git a/app/views/web/admin/join/edit.html.haml b/app/views/web/admin/join/edit.html.haml new file mode 100644 index 00000000..51cb957a --- /dev/null +++ b/app/views/web/admin/join/edit.html.haml @@ -0,0 +1 @@ += render partial: 'form', locals: { action: :update } diff --git a/app/views/web/admin/join/index.html.haml b/app/views/web/admin/join/index.html.haml new file mode 100644 index 00000000..29cde00f --- /dev/null +++ b/app/views/web/admin/join/index.html.haml @@ -0,0 +1,25 @@ +- model_class = Member +.page-header + %h1= model_class.model_name.human.pluralize(:ru) +%table.table.table-condensed.table-hover + %thead + %tr + %th= model_class.human_attribute_name(:id) + %th= model_class.human_attribute_name(:avatar) + %th= model_class.human_attribute_name(:full_name) + %th= model_class.human_attribute_name(:ticket) + %th= model_class.human_attribute_name(:place) + %th= t 'helpers.links.actions' + %tbody + - @members.each do |member| + %tr.link{ class: state_color(member), data: { href: edit_admin_member_path(member) } } + %td= member.id + %td= member.avatar + %td= member.full_name + %td= member.ticket + %td= member.place + %td + = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' + = link_to t('helpers.links.destroy'), admin_member_path(member), method: :delete, class: 'btn btn-xs btn-danger' + += link_to t('.new', default: t('helpers.links.new')), new_admin_member_path, class: 'btn btn-primary' diff --git a/app/views/web/admin/join/new.html.haml b/app/views/web/admin/join/new.html.haml new file mode 100644 index 00000000..02017918 --- /dev/null +++ b/app/views/web/admin/join/new.html.haml @@ -0,0 +1 @@ += render partial: 'form', locals: { action: :create } diff --git a/app/views/web/join/new.html.haml b/app/views/web/join/new.html.haml index e68617bb..c9d4b9ae 100644 --- a/app/views/web/join/new.html.haml +++ b/app/views/web/join/new.html.haml @@ -1,4 +1,4 @@ -= simple_form_for @member, url: { controller: 'web/join', action: :create } do |f| += simple_form_for @questionary, url: { controller: 'web/join', action: :create } do |f| = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } = f.input :patronymic, as: :string = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } @@ -12,4 +12,4 @@ = f.input :avatar, as: :string = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.button :submit, t('helpers.links.save'), class: 'btn-success' - = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' + = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' diff --git a/app/views/web/members/new.html.haml b/app/views/web/members/new.html.haml index ef776086..ee0fe326 100644 --- a/app/views/web/members/new.html.haml +++ b/app/views/web/members/new.html.haml @@ -12,4 +12,4 @@ = f.input :avatar, as: :string = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.button :submit, t('helpers.links.save'), class: 'btn-success' - = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' + = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' diff --git a/config/routes.rb b/config/routes.rb index 6cd07c2c..95bb04a5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,6 +27,7 @@ delete 'destroy' end end + resources :join end end diff --git a/test/controllers/web/admin/join_controller_test.rb b/test/controllers/web/admin/join_controller_test.rb new file mode 100644 index 00000000..bba48d94 --- /dev/null +++ b/test/controllers/web/admin/join_controller_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' + +class Web::Admin::JoinControllerTest < ActionController::TestCase + setup do + @questionary = create :questionary + end + + test 'should get new' do + get :new + assert_response :success, @response.body + end + + test 'should create questionary' do + attributes = attributes_for :questionary + post :create, questionary: attributes + assert_response :redirect, @response.body + assert_redirected_to admin_join_index_path + assert_equal attributes[:patronymic], Member.last.patronymic + end + + test 'should get edit' do + get :edit, id: @questionary + assert_response :success, @response.body + end + + test 'should patch update' do + attributes = attributes_for :questionary + patch :update, questionary: attributes, id: @questionary + assert_response :redirect, @response.body + assert_redirected_to admin_join_index_path + @questionary.reload + assert_equal attributes[:patronymic], @questionary.patronymic + end + + test 'should delete destroy' do + count = Member.count + delete :destroy, id: @questionary + @questionary.reload + assert @questionary.removed? + end +end diff --git a/test/controllers/web/join_controller_test.rb b/test/controllers/web/join_controller_test.rb index 87afec2e..0ca7e283 100644 --- a/test/controllers/web/join_controller_test.rb +++ b/test/controllers/web/join_controller_test.rb @@ -2,8 +2,8 @@ class Web::JoinControllerTest < ActionController::TestCase setup do - @member = create :member - sign_in @member.user + @questionary = create :questionary + sign_in @questionary.user end test 'should get new' do @@ -12,8 +12,8 @@ class Web::JoinControllerTest < ActionController::TestCase end test 'should post create' do - attributes = attributes_for :member - post :create, member: attributes + attributes = attributes_for :questionary + post :create, questionary: attributes assert_response :redirect, @response.body assert_redirected_to root_path assert_equal attributes[:patronymic], Member.last.patronymic diff --git a/test/factories/questionaries.rb b/test/factories/questionaries.rb new file mode 100644 index 00000000..e84db2e1 --- /dev/null +++ b/test/factories/questionaries.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :questionary, parent: :member do + state :wants_to_join + end +end From fdb9fbfc99d36b165617fea1e66303ad21ae8a6b Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Fri, 6 Mar 2015 04:33:45 +0300 Subject: [PATCH 124/227] add questrionary --- app/controllers/web/admin/join_controller.rb | 20 ++++++------- app/forms/questionary_form.rb | 5 ++++ app/models/member.rb | 7 +++-- app/models/questionary.rb | 29 +++++++++++++++++++ app/views/layouts/web/admin/_navbar.html.haml | 2 ++ app/views/web/admin/join/_form.html.haml | 3 +- app/views/web/admin/join/index.html.haml | 22 +++++++------- app/views/web/admin/members/_form.html.haml | 2 +- app/views/web/members/new.html.haml | 2 +- config/initializers/inflections.rb | 1 + config/locales/ru/models.yml | 1 + .../20150306010850_create_questionaries.rb | 11 +++++++ db/schema.rb | 10 ++++++- lib/yaml/russian_cases.yml | 1 + .../web/admin/join_controller_test.rb | 6 ++++ test/factories/questionaries.rb | 6 ++-- test/models/questionary_test.rb | 7 +++++ 17 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 app/forms/questionary_form.rb create mode 100644 app/models/questionary.rb create mode 100644 db/migrate/20150306010850_create_questionaries.rb create mode 100644 test/models/questionary_test.rb diff --git a/app/controllers/web/admin/join_controller.rb b/app/controllers/web/admin/join_controller.rb index 44f9c4e6..dabab6a6 100644 --- a/app/controllers/web/admin/join_controller.rb +++ b/app/controllers/web/admin/join_controller.rb @@ -1,21 +1,21 @@ class Web::Admin::JoinController < Web::Admin::ApplicationController def index - @questionarys = ::MemberDecorator.decorate_collection questionary.presented + @questionaries = Questionary.all end def new - @questionary = Member.new - @questionary_form = MemberForm.new @questionary + @questionary = Questionary.new + @questionary_form = QuestionaryForm.new @questionary end def edit - @questionary = Member.find params[:id] - @questionary_form = MemberForm.new @questionary + @questionary = Questionary.find params[:id] + @questionary_form = QuestionaryForm.new @questionary end def create - @questionary = Member.new - @questionary_form = MemberForm.new @questionary + @questionary = Questionary.new + @questionary_form = QuestionaryForm.new @questionary @questionary_form.submit params[:questionary] if @questionary_form.save redirect_to admin_join_index_path @@ -25,8 +25,8 @@ def create end def update - @questionary = Member.find params[:id] - @questionary_form = MemberForm.new @questionary + @questionary = Questionary.find params[:id] + @questionary_form = QuestionaryForm.new @questionary @questionary_form.submit params[:questionary] if @questionary_form.save redirect_to admin_join_index_path @@ -36,7 +36,7 @@ def update end def destroy - @questionary = Member.find params[:id] + @questionary = Questionary.find params[:id] @questionary.remove redirect_to admin_join_index_path end diff --git a/app/forms/questionary_form.rb b/app/forms/questionary_form.rb new file mode 100644 index 00000000..32291275 --- /dev/null +++ b/app/forms/questionary_form.rb @@ -0,0 +1,5 @@ +class QuestionaryForm < ActiveForm::Base + self.main_model = :questionary + + attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state, :experience, :member_id, :want_to_do +end diff --git a/app/models/member.rb b/app/models/member.rb index 83c3a894..5b16aa41 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -3,12 +3,12 @@ class Member < ActiveRecord::Base belongs_to :user belongs_to :parent, class_name: 'Member' + has_one :questionary validates :patronymic, presence: true, human_name: true validates :motto, presence: true - validates :ticket, presence: true, - uniqueness: true + validates :ticket, uniqueness: true validates :mobile_phone, presence: true, phone: true validates :birth_date, presence: true @@ -20,6 +20,7 @@ class Member < ActiveRecord::Base scope :presented, -> { where.not(state: :removed) } scope :removed, -> { where state: :removed } scope :not_confirmed, -> { where state: :not_confirmed } + scope :questionaries, -> { where state: :wants_to_join } state_machine :state, initial: :not_confirmed do state :not_confirmed @@ -49,6 +50,8 @@ class Member < ActiveRecord::Base end attr_accessor :first_name, :last_name, :email + private + def update_user_name if user_id User.update user_id, first_name: first_name if first_name.present? diff --git a/app/models/questionary.rb b/app/models/questionary.rb new file mode 100644 index 00000000..f6a1e3c3 --- /dev/null +++ b/app/models/questionary.rb @@ -0,0 +1,29 @@ +class Questionary < ActiveRecord::Base + after_save :update_member + belongs_to :member + + validates :experience, presence: true + validates :want_to_do, presence: true + + attr_accessor :first_name, :last_name, :email, :patronymic, :motto, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state, :user_id + + private + + def update_member + params = { patronymic: patronymic, motto: motto, mobile_phone: mobile_phone, birth_date: birth_date, home_adress: home_adress, municipality: municipality, locality: locality, avatar: avatar, state: state, user_id: user_id } + params.keys.each do |key| + params[key] = nil unless params[key].present? + end + if member_id + member = Member.find member_id + member.first_name = first_name if first_name.present? + member.last_name = last_name if last_name.present? + member.email = email if email.present? + member.save + Member.update member_id, params + else + Member.create params + update member_id: Member.last.id + end + end +end diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 3bf86671..4a014d0a 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -12,6 +12,8 @@ = User.model_name.human.pluralize(:ru) = menu_item admin_members_path do = Member.model_name.human.pluralize(:ru) + = menu_item admin_join_index_path do + = t('activerecord.models.questionary').pluralize(:ru) %ul.nav.navbar-nav.navbar-right = menu_item admin_unviewed_index_path do %span.glyphicon.glyphicon-info-sign diff --git a/app/views/web/admin/join/_form.html.haml b/app/views/web/admin/join/_form.html.haml index 0a1af767..f1c0a5d9 100644 --- a/app/views/web/admin/join/_form.html.haml +++ b/app/views/web/admin/join/_form.html.haml @@ -1,5 +1,5 @@ .page-header - %h1= page_title(action, Member.model_name.human) + %h1= page_title(action, t('activerecord.models.questionary')) .row .col-lg-6 = simple_form_for @questionary, url: { controller: 'web/admin/join', action: action }, input_html: { class: 'form-horizontal' } do |f| @@ -15,7 +15,6 @@ = f.input :last_name, as: :string, required: true = f.input :email, as: :string, required: true = f.input :motto, as: :string - = f.input :ticket, as: :string = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string diff --git a/app/views/web/admin/join/index.html.haml b/app/views/web/admin/join/index.html.haml index 29cde00f..19f26fa6 100644 --- a/app/views/web/admin/join/index.html.haml +++ b/app/views/web/admin/join/index.html.haml @@ -1,25 +1,23 @@ - model_class = Member .page-header - %h1= model_class.model_name.human.pluralize(:ru) + %h1= t('activerecord.models.questionary').pluralize(:ru) %table.table.table-condensed.table-hover %thead %tr %th= model_class.human_attribute_name(:id) %th= model_class.human_attribute_name(:avatar) %th= model_class.human_attribute_name(:full_name) - %th= model_class.human_attribute_name(:ticket) %th= model_class.human_attribute_name(:place) %th= t 'helpers.links.actions' %tbody - - @members.each do |member| - %tr.link{ class: state_color(member), data: { href: edit_admin_member_path(member) } } - %td= member.id - %td= member.avatar - %td= member.full_name - %td= member.ticket - %td= member.place + - @questionaries.each do |questionary| + %tr.link{ class: state_color(questionary), data: { href: edit_admin_join_path(questionary) } } + %td= questionary.id + %td= questionary.avatar + %td= questionary.full_name + %td= questionary.place %td - = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' - = link_to t('helpers.links.destroy'), admin_member_path(member), method: :delete, class: 'btn btn-xs btn-danger' + = link_to t('helpers.links.edit'), edit_admin_join_path(questionary), class: 'btn btn-warning btn-xs' + = link_to t('helpers.links.destroy'), admin_join_path(questionary), method: :delete, class: 'btn btn-xs btn-danger' -= link_to t('.new', default: t('helpers.links.new')), new_admin_member_path, class: 'btn btn-primary' += link_to t('.new', default: t('helpers.links.new')), new_admin_join_path, class: 'btn btn-primary' diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index f9b5721b..979f6f9a 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -15,7 +15,7 @@ = f.input :last_name, as: :string, required: true = f.input :email, as: :string, required: true = f.input :motto, as: :string - = f.input :ticket, as: :string + = f.input :ticket, as: :string, required: true = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string diff --git a/app/views/web/members/new.html.haml b/app/views/web/members/new.html.haml index ee0fe326..e8e389a0 100644 --- a/app/views/web/members/new.html.haml +++ b/app/views/web/members/new.html.haml @@ -3,7 +3,7 @@ = f.input :patronymic, as: :string = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } = f.input :motto, as: :string - = f.input :ticket, as: :string + = f.input :ticket, as: :string, required: true = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 389274cd..b5d27bee 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -6,4 +6,5 @@ ActiveSupport::Inflector.inflections(:ru) do |inflect| inflect.plural /ль$/i, 'ли' inflect.plural /н /i, 'ны ' + inflect.plural /та /i, 'ты ' end diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 61c81d04..d49c2df0 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -3,6 +3,7 @@ ru: models: user: Пользователь member: Член организации + questionary: Анкета на вступление attributes: user: first_name: Имя diff --git a/db/migrate/20150306010850_create_questionaries.rb b/db/migrate/20150306010850_create_questionaries.rb new file mode 100644 index 00000000..f43a820c --- /dev/null +++ b/db/migrate/20150306010850_create_questionaries.rb @@ -0,0 +1,11 @@ +class CreateQuestionaries < ActiveRecord::Migration + def change + create_table :questionaries do |t| + t.text :experience + t.text :want_to_do + t.integer :member_id + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a1af2bbd..0d82d082 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150301213120) do +ActiveRecord::Schema.define(version: 20150306010850) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -41,6 +41,14 @@ t.text "state" end + create_table "questionaries", force: :cascade do |t| + t.text "experience" + t.text "want_to_do" + t.integer "member_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" diff --git a/lib/yaml/russian_cases.yml b/lib/yaml/russian_cases.yml index 469f3dd4..039e9b39 100644 --- a/lib/yaml/russian_cases.yml +++ b/lib/yaml/russian_cases.yml @@ -2,3 +2,4 @@ cases: genitive: 'пользователь': пользователя 'член организации': члена организации + 'анкета на вступление': анкету на вступление diff --git a/test/controllers/web/admin/join_controller_test.rb b/test/controllers/web/admin/join_controller_test.rb index bba48d94..4a936fe5 100644 --- a/test/controllers/web/admin/join_controller_test.rb +++ b/test/controllers/web/admin/join_controller_test.rb @@ -2,9 +2,15 @@ class Web::Admin::JoinControllerTest < ActionController::TestCase setup do + create :member @questionary = create :questionary end + test 'should get index' do + get :index + assert_response :success, @response.body + end + test 'should get new' do get :new assert_response :success, @response.body diff --git a/test/factories/questionaries.rb b/test/factories/questionaries.rb index e84db2e1..42dc8b1e 100644 --- a/test/factories/questionaries.rb +++ b/test/factories/questionaries.rb @@ -1,5 +1,7 @@ FactoryGirl.define do - factory :questionary, parent: :member do - state :wants_to_join + factory :questionary do + member_id { Member.last ? Member.last.id : 1 } + experience { generate :string } + want_to_do { generate :string } end end diff --git a/test/models/questionary_test.rb b/test/models/questionary_test.rb new file mode 100644 index 00000000..2faf169f --- /dev/null +++ b/test/models/questionary_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class QuestionaryTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 290b628233019f09e2334b125c0cbb58907c1740 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 03:28:43 +0300 Subject: [PATCH 125/227] fix auth with vk --- Gemfile | 4 ++-- Gemfile.lock | 10 +++++----- app/helpers/application_helper.rb | 4 ++++ app/models/member.rb | 7 ------- app/models/questionary.rb | 7 +++++++ app/models/user.rb | 3 ++- app/views/web/sessions/new.html.haml | 5 +++++ config/locales/ru/ru.yml | 5 +++++ db/migrate/20150306010850_create_questionaries.rb | 1 + db/schema.rb | 1 + 10 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 6dd9b122..7f9ce8ca 100644 --- a/Gemfile +++ b/Gemfile @@ -50,8 +50,8 @@ group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' - # Access an IRB console on exception pages or by using <%= console %> in views - gem 'web-console', '~> 2.0' + # Access an IRB console on exception pages or by using <%= console %> in viewr + gem 'web-console', '2.0.0.beta2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' diff --git a/Gemfile.lock b/Gemfile.lock index 84899320..82eb58e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,10 +271,10 @@ GEM uglifier (2.7.1) execjs (>= 0.3.0) json (>= 1.8.0) - web-console (2.1.0) - activemodel (>= 4.0) - binding_of_caller (>= 0.7.2) - railties (>= 4.0) + web-console (2.0.0.beta2) + activemodel (~> 4.0) + binding_of_caller + railties (~> 4.0) sprockets-rails (>= 2.0, < 4.0) PLATFORMS @@ -311,4 +311,4 @@ DEPENDENCIES state_machine! tconsole! uglifier (>= 1.3.0) - web-console (~> 2.0) + web-console (= 2.0.0.beta2) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 82ebedc7..51aac156 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -34,4 +34,8 @@ def uri_state(uri, options = {}) end end end + + def auth_path(provider) + "/auth/#{provider}" + end end diff --git a/app/models/member.rb b/app/models/member.rb index 5b16aa41..e2fb1b50 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -28,10 +28,6 @@ class Member < ActiveRecord::Base state :declined state :removed - #Wants to join people states - state :wants_to_join - state :on_the_trial - event :confirm do transition all => :confirmed end @@ -44,9 +40,6 @@ class Member < ActiveRecord::Base event :restore do transition removed: :not_confirmed end - event :put_on_the_trial do - transition wants_to_join: :on_the_trial - end end attr_accessor :first_name, :last_name, :email diff --git a/app/models/questionary.rb b/app/models/questionary.rb index f6a1e3c3..7a9f0e53 100644 --- a/app/models/questionary.rb +++ b/app/models/questionary.rb @@ -5,6 +5,13 @@ class Questionary < ActiveRecord::Base validates :experience, presence: true validates :want_to_do, presence: true + state_machine initial: :new do + state :new + state :confirmed + state :declined + state :on_the_trial + end + attr_accessor :first_name, :last_name, :email, :patronymic, :motto, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state, :user_id private diff --git a/app/models/user.rb b/app/models/user.rb index 6f19de70..022e6764 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,8 @@ class User < ActiveRecord::Base has_many :authentications - validates :email, uniqueness: true + validates :email, uniqueness: true, + allow_nil: true validates :first_name, human_name: true, allow_blank: true validates :last_name, human_name: true, diff --git a/app/views/web/sessions/new.html.haml b/app/views/web/sessions/new.html.haml index 9bcd57dc..4de68d01 100644 --- a/app/views/web/sessions/new.html.haml +++ b/app/views/web/sessions/new.html.haml @@ -2,4 +2,9 @@ = f.input :email, label: t('activerecord.attributes.user.email') = f.input :password, label: t('activerecord.attributes.user.password') = f.button :submit, t('helpers.new.enter') + += link_to t('social_networks.vkontakte'), auth_path(:vkontakte) += link_to t('social_networks.twitter'), auth_path(:twitter) += link_to t('social_networks.facebook'), auth_path(:facebook) += link_to t('social_networks.google'), auth_path(:google) = link_to t('.register'), new_user_path diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 0a2655aa..be8ad7d2 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -16,6 +16,11 @@ ru: back: Назад approve: Подтвердить decline: Отклонить + social_networks: + vkontakte: ВКонтакте + facebook: Facebook + twitter: Twitter + google: Google web: users: new: diff --git a/db/migrate/20150306010850_create_questionaries.rb b/db/migrate/20150306010850_create_questionaries.rb index f43a820c..7ff3b032 100644 --- a/db/migrate/20150306010850_create_questionaries.rb +++ b/db/migrate/20150306010850_create_questionaries.rb @@ -3,6 +3,7 @@ def change create_table :questionaries do |t| t.text :experience t.text :want_to_do + t.text :state t.integer :member_id t.timestamps null: false diff --git a/db/schema.rb b/db/schema.rb index 0d82d082..2cc4cb2c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -44,6 +44,7 @@ create_table "questionaries", force: :cascade do |t| t.text "experience" t.text "want_to_do" + t.text "state" t.integer "member_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false From 9cc71ed500825398f49d9f1cea81859f9007e669 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 04:32:29 +0300 Subject: [PATCH 126/227] fix tests --- app/controllers/web/admin/join_controller.rb | 2 +- .../web/admin/unviewed_controller.rb | 2 +- app/controllers/web/join_controller.rb | 8 ++--- app/decorators/questionary_decorator.rb | 23 +++++++++++++++ app/helpers/web/admin/application_helper.rb | 4 +-- app/models/member.rb | 9 +++--- app/models/questionary.rb | 29 ++++++++++++++++--- app/models/user.rb | 6 ++-- app/views/web/admin/join/_form.html.haml | 2 +- .../web/admin/join_controller_test.rb | 4 +-- .../web/admin/trash_controller_test.rb | 2 +- test/controllers/web/join_controller_test.rb | 11 ++++++- test/decorators/questionary_decorator_test.rb | 4 +++ test/factories/questionaries.rb | 1 + 14 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 app/decorators/questionary_decorator.rb create mode 100644 test/decorators/questionary_decorator_test.rb diff --git a/app/controllers/web/admin/join_controller.rb b/app/controllers/web/admin/join_controller.rb index dabab6a6..4efdc486 100644 --- a/app/controllers/web/admin/join_controller.rb +++ b/app/controllers/web/admin/join_controller.rb @@ -1,6 +1,6 @@ class Web::Admin::JoinController < Web::Admin::ApplicationController def index - @questionaries = Questionary.all + @questionaries = Questionary.all.decorate end def new diff --git a/app/controllers/web/admin/unviewed_controller.rb b/app/controllers/web/admin/unviewed_controller.rb index 55309e37..2e315332 100644 --- a/app/controllers/web/admin/unviewed_controller.rb +++ b/app/controllers/web/admin/unviewed_controller.rb @@ -1,5 +1,5 @@ class Web::Admin::UnviewedController < Web::Admin::ApplicationController def index - @members = ::MemberDecorator.decorate_collection Member.not_confirmed + @members = ::MemberDecorator.decorate_collection Member.unviewed end end diff --git a/app/controllers/web/join_controller.rb b/app/controllers/web/join_controller.rb index 6f2c9613..e33593c8 100644 --- a/app/controllers/web/join_controller.rb +++ b/app/controllers/web/join_controller.rb @@ -2,13 +2,13 @@ class Web::JoinController < Web::ApplicationController before_filter :authenticate_user!, only: [ :new, :create ] def new - @questionary = Member.new - @questionary_form = MemberForm.new @questionary + @questionary = Questionary.new + @questionary_form = QuestionaryForm.new @questionary end def create - @questionary = Member.new - @questionary_form = MemberForm.new @questionary + @questionary = Questionary.new + @questionary_form = QuestionaryForm.new @questionary @questionary_form.submit params[:questionary] if @questionary_form.save redirect_to root_path diff --git a/app/decorators/questionary_decorator.rb b/app/decorators/questionary_decorator.rb new file mode 100644 index 00000000..6399c355 --- /dev/null +++ b/app/decorators/questionary_decorator.rb @@ -0,0 +1,23 @@ +class QuestionaryDecorator < Draper::Decorator + delegate_all + + def first_name + member.user.first_name + end + + def last_name + member.user.last_name + end + + def patronymic + member.patronymic + end + + def full_name + "#{first_name} #{patronymic} #{last_name}" + end + + def place + "#{member.municipality}, #{member.locality}" + end +end diff --git a/app/helpers/web/admin/application_helper.rb b/app/helpers/web/admin/application_helper.rb index 41872da5..9ef34b1b 100644 --- a/app/helpers/web/admin/application_helper.rb +++ b/app/helpers/web/admin/application_helper.rb @@ -1,8 +1,8 @@ module Web::Admin::ApplicationHelper def notification_count - Member.not_confirmed.count + Member.unviewed.count end def state_color(item) - :success if item.not_confirmed? + :success if item.unviewed? end end diff --git a/app/models/member.rb b/app/models/member.rb index e2fb1b50..3c4be599 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -19,11 +19,10 @@ class Member < ActiveRecord::Base scope :presented, -> { where.not(state: :removed) } scope :removed, -> { where state: :removed } - scope :not_confirmed, -> { where state: :not_confirmed } - scope :questionaries, -> { where state: :wants_to_join } + scope :unviewed, -> { where state: :unviewed } - state_machine :state, initial: :not_confirmed do - state :not_confirmed + state_machine :state, initial: :unviewed do + state :unviewed state :confirmed state :declined state :removed @@ -38,7 +37,7 @@ class Member < ActiveRecord::Base transition all => :removed end event :restore do - transition removed: :not_confirmed + transition removed: :unviewed end end attr_accessor :first_name, :last_name, :email diff --git a/app/models/questionary.rb b/app/models/questionary.rb index 7a9f0e53..4b7b4693 100644 --- a/app/models/questionary.rb +++ b/app/models/questionary.rb @@ -5,19 +5,40 @@ class Questionary < ActiveRecord::Base validates :experience, presence: true validates :want_to_do, presence: true - state_machine initial: :new do - state :new + state_machine :state, initial: :unviewed do + state :unviewed state :confirmed state :declined state :on_the_trial + state :removed + + event :confirm do + transition all => :confirmed + end + event :decline do + transition all => :declined + end + event :put_on_the_trial do + transition all => :on_the_trial + end + event :remove do + transition all => :removed + end + event :restore do + transition removed: :unviewed + end + end + + def user + member.user if member end - attr_accessor :first_name, :last_name, :email, :patronymic, :motto, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state, :user_id + attr_accessor :first_name, :last_name, :email, :patronymic, :motto, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :user_id, :ticket private def update_member - params = { patronymic: patronymic, motto: motto, mobile_phone: mobile_phone, birth_date: birth_date, home_adress: home_adress, municipality: municipality, locality: locality, avatar: avatar, state: state, user_id: user_id } + params = { patronymic: patronymic, motto: motto, mobile_phone: mobile_phone, birth_date: birth_date, home_adress: home_adress, municipality: municipality, locality: locality, avatar: avatar, state: state, user_id: user_id, ticket: ticket } params.keys.each do |key| params[key] = nil unless params[key].present? end diff --git a/app/models/user.rb b/app/models/user.rb index 022e6764..415d2c43 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,8 +18,8 @@ class User < ActiveRecord::Base scope :presented, -> { where.not(state: :removed) } scope :removed, -> { where state: :removed } - state_machine :state, initial: :not_confirmed do - state :not_confirmed + state_machine :state, initial: :unviewed do + state :unviewed state :confirmed state :declined state :removed @@ -34,7 +34,7 @@ class User < ActiveRecord::Base transition all => :removed end event :restore do - transition :removed => :not_confirmed + transition :removed => :unviewed end end end diff --git a/app/views/web/admin/join/_form.html.haml b/app/views/web/admin/join/_form.html.haml index f1c0a5d9..d187e47f 100644 --- a/app/views/web/admin/join/_form.html.haml +++ b/app/views/web/admin/join/_form.html.haml @@ -21,6 +21,6 @@ = f.input :municipality, as: :string = f.input :locality, as: :string = f.input :avatar, as: :string - = f.association :user, label_method: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id, required: true + = f.association :member, label_method: lambda { |member| "#{member.id} | #{member.user.first_name} #{member.user.last_name}" }, value_method: :id, required: true = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_join_index_path, class: 'btn btn-default' diff --git a/test/controllers/web/admin/join_controller_test.rb b/test/controllers/web/admin/join_controller_test.rb index 4a936fe5..a9e716f9 100644 --- a/test/controllers/web/admin/join_controller_test.rb +++ b/test/controllers/web/admin/join_controller_test.rb @@ -21,7 +21,7 @@ class Web::Admin::JoinControllerTest < ActionController::TestCase post :create, questionary: attributes assert_response :redirect, @response.body assert_redirected_to admin_join_index_path - assert_equal attributes[:patronymic], Member.last.patronymic + assert_equal attributes[:patronymic], Questionary.last.patronymic end test 'should get edit' do @@ -39,7 +39,7 @@ class Web::Admin::JoinControllerTest < ActionController::TestCase end test 'should delete destroy' do - count = Member.count + count = Questionary.count delete :destroy, id: @questionary @questionary.reload assert @questionary.removed? diff --git a/test/controllers/web/admin/trash_controller_test.rb b/test/controllers/web/admin/trash_controller_test.rb index 63829d2c..9c2a69ff 100644 --- a/test/controllers/web/admin/trash_controller_test.rb +++ b/test/controllers/web/admin/trash_controller_test.rb @@ -17,7 +17,7 @@ class Web::Admin::TrashControllerTest < ActionController::TestCase test 'should patch restore' do patch :restore, type: :user, id: @user @user.reload - assert @user.not_confirmed? + assert @user.unviewed? assert_response :redirect, @response.body end diff --git a/test/controllers/web/join_controller_test.rb b/test/controllers/web/join_controller_test.rb index 0ca7e283..9dfb4742 100644 --- a/test/controllers/web/join_controller_test.rb +++ b/test/controllers/web/join_controller_test.rb @@ -2,6 +2,7 @@ class Web::JoinControllerTest < ActionController::TestCase setup do + create :member @questionary = create :questionary sign_in @questionary.user end @@ -16,6 +17,14 @@ class Web::JoinControllerTest < ActionController::TestCase post :create, questionary: attributes assert_response :redirect, @response.body assert_redirected_to root_path - assert_equal attributes[:patronymic], Member.last.patronymic + assert_equal attributes[:patronymic], Questionary.last.patronymic + end + + test 'should not get create' do + attributes = attributes_for :questionary + attributes[:experience] = nil + post :create, questionary: attributes + assert_response :success, @response.body + assert_not_equal attributes[:want_to_do], Questionary.last.want_to_do end end diff --git a/test/decorators/questionary_decorator_test.rb b/test/decorators/questionary_decorator_test.rb new file mode 100644 index 00000000..eb56088c --- /dev/null +++ b/test/decorators/questionary_decorator_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class QuestionaryDecoratorTest < Draper::TestCase +end diff --git a/test/factories/questionaries.rb b/test/factories/questionaries.rb index 42dc8b1e..c03ddd9c 100644 --- a/test/factories/questionaries.rb +++ b/test/factories/questionaries.rb @@ -3,5 +3,6 @@ member_id { Member.last ? Member.last.id : 1 } experience { generate :string } want_to_do { generate :string } + state 'unviewed' end end From ca6e27ce4e87fba1bd138fe97ae6c5acd4f0fb68 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 04:35:47 +0300 Subject: [PATCH 127/227] fix form of questionary --- app/views/web/join/new.html.haml | 3 ++- config/locales/ru/models.yml | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/views/web/join/new.html.haml b/app/views/web/join/new.html.haml index c9d4b9ae..001dff1b 100644 --- a/app/views/web/join/new.html.haml +++ b/app/views/web/join/new.html.haml @@ -3,7 +3,6 @@ = f.input :patronymic, as: :string = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } = f.input :motto, as: :string - = f.input :ticket, as: :string = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string @@ -11,5 +10,7 @@ = f.input :locality, as: :string = f.input :avatar, as: :string = f.input :user_id, as: :hidden, input_html: { value: current_user.id } + = f.input :want_to_do + = f.input :experience = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index d49c2df0..6a104630 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -28,3 +28,18 @@ ru: municipality: Муниципальное образование locality: Населённый пункт user: Прикреплённый Пользователь + questionary: + first_name: Имя + patronymic: Отчество + last_name: Фамилия + avatar: Аватар + full_name: Ф.И.О. + place: Место проживания + motto: Девиз по жизни + mobile_phone: Мобильный телефон + birth_date: Дата рождения + home_adress: Домашний адрес + municipality: Муниципальное образование + want_to_do: "Чем хочешь заниматься в МИЦ?" + experience: Опыт и навыки + locality: Населённый пункт From 2b1d7c6fe9682c20ce7d04022ccdae2f940aef5b Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 04:52:49 +0300 Subject: [PATCH 128/227] fix admin panel --- Gemfile | 1 + Gemfile.lock | 3 +++ app/controllers/web/admin/join_controller.rb | 2 +- app/controllers/web/admin/unviewed_controller.rb | 1 + app/decorators/questionary_decorator.rb | 4 ++++ app/helpers/locals_helper.rb | 2 +- app/helpers/web/admin/application_helper.rb | 2 +- app/models/questionary.rb | 4 ++++ app/views/layouts/web/admin/_navbar.html.haml | 7 ++++--- app/views/web/admin/join/index.html.haml | 4 ++++ config/locales/ru/models.yml | 1 + config/locales/ru/ru.yml | 14 ++++++++++---- .../controllers/web/admin/trash_controller_test.rb | 4 ++++ 13 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 7f9ce8ca..ea0a9ec7 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,7 @@ gem 'simple_form' gem 'bootstrap-sass' gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' gem 'draper' +gem 'russian' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' diff --git a/Gemfile.lock b/Gemfile.lock index 82eb58e0..9874c8cf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -231,6 +231,8 @@ GEM netrc (~> 0.7) ruby_parser (3.6.4) sexp_processor (~> 4.1) + russian (0.6.0) + i18n (>= 0.5.0) sass (3.4.13) sass-rails (5.0.1) railties (>= 4.0.0, < 5.0) @@ -302,6 +304,7 @@ DEPENDENCIES pry-byebug pry-rails rails (= 4.2.0) + russian sass-rails (~> 5.0) sdoc (~> 0.4.0) simple_form diff --git a/app/controllers/web/admin/join_controller.rb b/app/controllers/web/admin/join_controller.rb index 4efdc486..8094b3bb 100644 --- a/app/controllers/web/admin/join_controller.rb +++ b/app/controllers/web/admin/join_controller.rb @@ -1,6 +1,6 @@ class Web::Admin::JoinController < Web::Admin::ApplicationController def index - @questionaries = Questionary.all.decorate + @questionaries = Questionary.presented.decorate end def new diff --git a/app/controllers/web/admin/unviewed_controller.rb b/app/controllers/web/admin/unviewed_controller.rb index 2e315332..bb0ad624 100644 --- a/app/controllers/web/admin/unviewed_controller.rb +++ b/app/controllers/web/admin/unviewed_controller.rb @@ -1,5 +1,6 @@ class Web::Admin::UnviewedController < Web::Admin::ApplicationController def index @members = ::MemberDecorator.decorate_collection Member.unviewed + @questionaries = Questionary.unviewed.decorate end end diff --git a/app/decorators/questionary_decorator.rb b/app/decorators/questionary_decorator.rb index 6399c355..35cbc430 100644 --- a/app/decorators/questionary_decorator.rb +++ b/app/decorators/questionary_decorator.rb @@ -20,4 +20,8 @@ def full_name def place "#{member.municipality}, #{member.locality}" end + + def name + full_name + end end diff --git a/app/helpers/locals_helper.rb b/app/helpers/locals_helper.rb index bbb2c9c9..82d15881 100644 --- a/app/helpers/locals_helper.rb +++ b/app/helpers/locals_helper.rb @@ -8,7 +8,7 @@ def page_title(action, model_name) end def trash_page_title(model_class) - t('web.trash.deleted') + ' ' + model_class.model_name.human.pluralize(:ru).mb_chars.downcase.to_s + t('web.admin.trash.deleted') + ' ' + model_class.model_name.human.pluralize(:ru).mb_chars.downcase.to_s end end diff --git a/app/helpers/web/admin/application_helper.rb b/app/helpers/web/admin/application_helper.rb index 9ef34b1b..7913d8c8 100644 --- a/app/helpers/web/admin/application_helper.rb +++ b/app/helpers/web/admin/application_helper.rb @@ -1,6 +1,6 @@ module Web::Admin::ApplicationHelper def notification_count - Member.unviewed.count + Member.unviewed.count + Questionary.unviewed.count end def state_color(item) :success if item.unviewed? diff --git a/app/models/questionary.rb b/app/models/questionary.rb index 4b7b4693..2bfa595d 100644 --- a/app/models/questionary.rb +++ b/app/models/questionary.rb @@ -5,6 +5,10 @@ class Questionary < ActiveRecord::Base validates :experience, presence: true validates :want_to_do, presence: true + scope :unviewed, -> { where state: :unviewed } + scope :presented, -> { where.not(state: :removed) } + scope :removed, -> { where state: :removed } + state_machine :state, initial: :unviewed do state :unviewed state :confirmed diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 4a014d0a..41171067 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -13,7 +13,7 @@ = menu_item admin_members_path do = Member.model_name.human.pluralize(:ru) = menu_item admin_join_index_path do - = t('activerecord.models.questionary').pluralize(:ru) + = Questionary.model_name.human.pluralize(:ru) %ul.nav.navbar-nav.navbar-right = menu_item admin_unviewed_index_path do %span.glyphicon.glyphicon-info-sign @@ -21,10 +21,11 @@ %li.dropdown %a.dropdown-toggle{ href: '#', data: { toggle: :dropdown } } %span.glyphicon.glyphicon-trash - = t('web.trash.title') + = t('web.admin.trash.title') %ul.dropdown-menu = menu_item type_admin_trash_index_path('user') do = User.model_name.human.pluralize(:ru) = menu_item type_admin_trash_index_path('member') do = Member.model_name.human.pluralize(:ru) - + = menu_item type_admin_trash_index_path('questionary') do + = Questionary.model_name.human.pluralize(:ru) diff --git a/app/views/web/admin/join/index.html.haml b/app/views/web/admin/join/index.html.haml index 19f26fa6..a4a0a675 100644 --- a/app/views/web/admin/join/index.html.haml +++ b/app/views/web/admin/join/index.html.haml @@ -8,6 +8,7 @@ %th= model_class.human_attribute_name(:avatar) %th= model_class.human_attribute_name(:full_name) %th= model_class.human_attribute_name(:place) + %th= model_class.human_attribute_name(:created_at) %th= t 'helpers.links.actions' %tbody - @questionaries.each do |questionary| @@ -16,7 +17,10 @@ %td= questionary.avatar %td= questionary.full_name %td= questionary.place + %td=l questionary.created_at %td + - if questionary.unviewed? + = link_to t('state_machines.questionary.state.events.put_on_the_trial'), admin_join_path(questionary, questionary: { state: :put_on_the_trial }), method: :put, class: 'btn btn-success btn-xs' = link_to t('helpers.links.edit'), edit_admin_join_path(questionary), class: 'btn btn-warning btn-xs' = link_to t('helpers.links.destroy'), admin_join_path(questionary), method: :delete, class: 'btn btn-xs btn-danger' diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 6a104630..289f2686 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -43,3 +43,4 @@ ru: want_to_do: "Чем хочешь заниматься в МИЦ?" experience: Опыт и навыки locality: Населённый пункт + created_at: Дата подачи diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index be8ad7d2..f0483663 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -21,11 +21,17 @@ ru: facebook: Facebook twitter: Twitter google: Google + state_machines: + questionary: + state: + events: + put_on_the_trial: Поставить на испытательный срок web: users: new: register: Регистрация - trash: - name: Имя - title: Корзина - deleted: Удалённые + admin: + trash: + name: Имя + title: Корзина + deleted: Удалённые diff --git a/test/controllers/web/admin/trash_controller_test.rb b/test/controllers/web/admin/trash_controller_test.rb index 9c2a69ff..7bb37280 100644 --- a/test/controllers/web/admin/trash_controller_test.rb +++ b/test/controllers/web/admin/trash_controller_test.rb @@ -6,12 +6,16 @@ class Web::Admin::TrashControllerTest < ActionController::TestCase @user.remove @member = create :member @member.remove + @questionary = create :questionary + @questionary.remove end test 'should get index' do get :index, type: :user assert_response :success, @response.body get :index, type: :member assert_response :success, @response.body + get :index, type: :questionary + assert_response :success, @response.body end test 'should patch restore' do From d4e29e8c8030c7a494c316301ea5b672e93b64d9 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 04:54:11 +0300 Subject: [PATCH 129/227] move on decline button --- app/views/web/admin/join/index.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/web/admin/join/index.html.haml b/app/views/web/admin/join/index.html.haml index a4a0a675..58449f8f 100644 --- a/app/views/web/admin/join/index.html.haml +++ b/app/views/web/admin/join/index.html.haml @@ -23,5 +23,7 @@ = link_to t('state_machines.questionary.state.events.put_on_the_trial'), admin_join_path(questionary, questionary: { state: :put_on_the_trial }), method: :put, class: 'btn btn-success btn-xs' = link_to t('helpers.links.edit'), edit_admin_join_path(questionary), class: 'btn btn-warning btn-xs' = link_to t('helpers.links.destroy'), admin_join_path(questionary), method: :delete, class: 'btn btn-xs btn-danger' + - unless questionary.declined? + = link_to t('helpers.links.decline'), admin_join_path(questionary, questionary: { state: :declined }), method: :patch, class: 'btn btn-xs btn-danger' = link_to t('.new', default: t('helpers.links.new')), new_admin_join_path, class: 'btn btn-primary' From dd03c76db0aaaa78f8d9bbc9b8636751367f711a Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 04:58:21 +0300 Subject: [PATCH 130/227] add join org button --- app/controllers/web/admin/join_controller.rb | 1 + app/views/web/admin/join/index.html.haml | 2 ++ config/locales/ru/ru.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/app/controllers/web/admin/join_controller.rb b/app/controllers/web/admin/join_controller.rb index 8094b3bb..dda0597b 100644 --- a/app/controllers/web/admin/join_controller.rb +++ b/app/controllers/web/admin/join_controller.rb @@ -29,6 +29,7 @@ def update @questionary_form = QuestionaryForm.new @questionary @questionary_form.submit params[:questionary] if @questionary_form.save + @questionary_form.member.confirm if @questionary_form.confirmed? redirect_to admin_join_index_path else render action: :edit diff --git a/app/views/web/admin/join/index.html.haml b/app/views/web/admin/join/index.html.haml index 58449f8f..ada2609e 100644 --- a/app/views/web/admin/join/index.html.haml +++ b/app/views/web/admin/join/index.html.haml @@ -21,6 +21,8 @@ %td - if questionary.unviewed? = link_to t('state_machines.questionary.state.events.put_on_the_trial'), admin_join_path(questionary, questionary: { state: :put_on_the_trial }), method: :put, class: 'btn btn-success btn-xs' + - unless questionary.confirmed? + = link_to t('state_machines.questionary.state.events.confirm'), admin_join_path(questionary, questionary: { state: :put_on_the_trial }), method: :put, class: 'btn btn-success btn-xs' = link_to t('helpers.links.edit'), edit_admin_join_path(questionary), class: 'btn btn-warning btn-xs' = link_to t('helpers.links.destroy'), admin_join_path(questionary), method: :delete, class: 'btn btn-xs btn-danger' - unless questionary.declined? diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index f0483663..76d688cf 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -26,6 +26,7 @@ ru: state: events: put_on_the_trial: Поставить на испытательный срок + confirm: Принять в организацию web: users: new: From 5d6d4ee8211dc6504911e657067010ec17b8ef6c Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 04:59:25 +0300 Subject: [PATCH 131/227] fix locale --- app/views/web/admin/join/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/web/admin/join/index.html.haml b/app/views/web/admin/join/index.html.haml index ada2609e..cec77820 100644 --- a/app/views/web/admin/join/index.html.haml +++ b/app/views/web/admin/join/index.html.haml @@ -1,4 +1,4 @@ -- model_class = Member +- model_class = Questionary .page-header %h1= t('activerecord.models.questionary').pluralize(:ru) %table.table.table-condensed.table-hover From ba74cabbbe2962964ccb984ba11c1cec097f3f84 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:01:35 +0300 Subject: [PATCH 132/227] fix functional --- app/controllers/web/admin/join_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/admin/join_controller.rb b/app/controllers/web/admin/join_controller.rb index dda0597b..53371b8d 100644 --- a/app/controllers/web/admin/join_controller.rb +++ b/app/controllers/web/admin/join_controller.rb @@ -29,7 +29,7 @@ def update @questionary_form = QuestionaryForm.new @questionary @questionary_form.submit params[:questionary] if @questionary_form.save - @questionary_form.member.confirm if @questionary_form.confirmed? + @questionary_form.member.confirm if @questionary_form.model.confirmed? redirect_to admin_join_index_path else render action: :edit From cc3097ee2da38b29428173c73a06900b2fe8ef07 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:03:08 +0300 Subject: [PATCH 133/227] fix database.yml --- config/database.yml.sample | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/database.yml.sample b/config/database.yml.sample index b3338409..562e4628 100644 --- a/config/database.yml.sample +++ b/config/database.yml.sample @@ -6,11 +6,9 @@ default: &default development: <<: *default - username: develop database: ulmic_development test: <<: *default - username: develop database: ulmic_test staging: From d454060cb377db19bf1f71dee121fb9c3777a186 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:24:07 +0300 Subject: [PATCH 134/227] add oauth file --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3bffcd8a..c712daaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ script: - gem install sdoc -v '0.4.1' - cp config/secrets.yml.sample config/secrets.yml - cp config/database.yml.sample config/database.yml + - cp config/oauth.yml.sample config/oauth.yml - RAILS_ENV=test bundle exec rake db:create db:migrate test services: - redis-server From 9a19ad5b0015071506262e6936edca4d3c06bdc3 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:42:20 +0300 Subject: [PATCH 135/227] add account controller --- app/controllers/concerns/auth_managment.rb | 10 ++++++++++ app/controllers/web/account_controller.rb | 5 +++++ app/models/user.rb | 1 + app/views/web/account/index.html.haml | 0 config/routes.rb | 1 + test/controllers/web/account_controller_test.rb | 13 +++++++++++++ 6 files changed, 30 insertions(+) create mode 100644 app/controllers/web/account_controller.rb create mode 100644 app/views/web/account/index.html.haml create mode 100644 test/controllers/web/account_controller_test.rb diff --git a/app/controllers/concerns/auth_managment.rb b/app/controllers/concerns/auth_managment.rb index 7eb091c6..84fe41f4 100644 --- a/app/controllers/concerns/auth_managment.rb +++ b/app/controllers/concerns/auth_managment.rb @@ -25,6 +25,16 @@ def authenticate_user! redirect_to new_session_path unless signed_in? end + def authenticate_member! + if signed_in? + unless current_user.member.present? + redirect_to new_session_path + end + else + redirect_to new_session_path + end + end + def current_user @_current_user ||= User.find_by(id: session[:user_id]) end diff --git a/app/controllers/web/account_controller.rb b/app/controllers/web/account_controller.rb new file mode 100644 index 00000000..39d03e3c --- /dev/null +++ b/app/controllers/web/account_controller.rb @@ -0,0 +1,5 @@ +class Web::AccountController < Web::ApplicationController + before_filter :authenticate_user! + def index + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 415d2c43..26d3c5d8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < ActiveRecord::Base has_secure_password validations: false has_many :authentications + has_one :member validates :email, uniqueness: true, allow_nil: true diff --git a/app/views/web/account/index.html.haml b/app/views/web/account/index.html.haml new file mode 100644 index 00000000..e69de29b diff --git a/config/routes.rb b/config/routes.rb index 95bb04a5..2b99a94e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,7 @@ end end resources :join, only: [ :new, :create ] + resources :account, only: :index namespace :admin do root to: 'welcome#index' resources :users diff --git a/test/controllers/web/account_controller_test.rb b/test/controllers/web/account_controller_test.rb new file mode 100644 index 00000000..58f8d120 --- /dev/null +++ b/test/controllers/web/account_controller_test.rb @@ -0,0 +1,13 @@ +require 'test_helper' + +class Web::AccountControllerTest < ActionController::TestCase + setup do + @user = create :user + sign_in @user + end + + test 'should get index' do + get :index + assert_response :success, @response.body + end +end From da2cdc5b0f6d475cf062c1bd3d0ffe9d08c23ba0 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:51:15 +0300 Subject: [PATCH 136/227] redirect to account path --- app/controllers/web/members_controller.rb | 2 +- app/controllers/web/omniauth_controller.rb | 2 +- app/controllers/web/sessions_controller.rb | 2 +- test/controllers/web/members_controller_test.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/web/members_controller.rb b/app/controllers/web/members_controller.rb index 944b5b8d..6ca477d6 100644 --- a/app/controllers/web/members_controller.rb +++ b/app/controllers/web/members_controller.rb @@ -11,7 +11,7 @@ def create @member_form = MemberForm.new @member @member_form.submit params[:member] if @member_form.save - redirect_to root_path + redirect_to account_index_path else render action: :new end diff --git a/app/controllers/web/omniauth_controller.rb b/app/controllers/web/omniauth_controller.rb index e8c70286..f1187c47 100644 --- a/app/controllers/web/omniauth_controller.rb +++ b/app/controllers/web/omniauth_controller.rb @@ -25,7 +25,7 @@ def callback end Authentication.create user_id: current_user.id, provider: provider, uid: uid end - redirect_to root_path + redirect_to account_index_path end alias :google :callback diff --git a/app/controllers/web/sessions_controller.rb b/app/controllers/web/sessions_controller.rb index 39d884e8..b1da5730 100644 --- a/app/controllers/web/sessions_controller.rb +++ b/app/controllers/web/sessions_controller.rb @@ -10,7 +10,7 @@ def create if @user if @user.authenticate params[:user][:password] sign_in @user - redirect_to root_path + redirect_to account_index_path else render :new end diff --git a/test/controllers/web/members_controller_test.rb b/test/controllers/web/members_controller_test.rb index 26ce010f..63166a06 100644 --- a/test/controllers/web/members_controller_test.rb +++ b/test/controllers/web/members_controller_test.rb @@ -21,7 +21,7 @@ class Web::MembersControllerTest < ActionController::TestCase attributes = attributes_for :member post :create, member: attributes assert_response :redirect, @response.body - assert_redirected_to root_path + assert_redirected_to account_index_path assert_equal attributes[:patronymic], Member.last.patronymic end From a8b13e5b44cda9ff176474fec7b58239c411cc60 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:57:25 +0300 Subject: [PATCH 137/227] replace account controller --- app/controllers/web/account_controller.rb | 5 ----- app/controllers/web/members_controller.rb | 2 +- app/controllers/web/sessions_controller.rb | 2 +- app/controllers/web/users/account_controller.rb | 4 ++++ app/controllers/web/users/application_controller.rb | 3 +++ app/views/web/{ => users}/account/index.html.haml | 0 config/routes.rb | 4 +++- test/controllers/web/members_controller_test.rb | 2 +- .../controllers/web/{ => users}/account_controller_test.rb | 2 +- test/controllers/web/users/application_controller_test.rb | 7 +++++++ 10 files changed, 21 insertions(+), 10 deletions(-) delete mode 100644 app/controllers/web/account_controller.rb create mode 100644 app/controllers/web/users/account_controller.rb create mode 100644 app/controllers/web/users/application_controller.rb rename app/views/web/{ => users}/account/index.html.haml (100%) rename test/controllers/web/{ => users}/account_controller_test.rb (72%) create mode 100644 test/controllers/web/users/application_controller_test.rb diff --git a/app/controllers/web/account_controller.rb b/app/controllers/web/account_controller.rb deleted file mode 100644 index 39d03e3c..00000000 --- a/app/controllers/web/account_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -class Web::AccountController < Web::ApplicationController - before_filter :authenticate_user! - def index - end -end diff --git a/app/controllers/web/members_controller.rb b/app/controllers/web/members_controller.rb index 6ca477d6..4235d683 100644 --- a/app/controllers/web/members_controller.rb +++ b/app/controllers/web/members_controller.rb @@ -11,7 +11,7 @@ def create @member_form = MemberForm.new @member @member_form.submit params[:member] if @member_form.save - redirect_to account_index_path + redirect_to account_path else render action: :new end diff --git a/app/controllers/web/sessions_controller.rb b/app/controllers/web/sessions_controller.rb index b1da5730..1704cce4 100644 --- a/app/controllers/web/sessions_controller.rb +++ b/app/controllers/web/sessions_controller.rb @@ -10,7 +10,7 @@ def create if @user if @user.authenticate params[:user][:password] sign_in @user - redirect_to account_index_path + redirect_to account_path else render :new end diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb new file mode 100644 index 00000000..c0db7b0e --- /dev/null +++ b/app/controllers/web/users/account_controller.rb @@ -0,0 +1,4 @@ +class Web::Users::AccountController < Web::Users::ApplicationController + def index + end +end diff --git a/app/controllers/web/users/application_controller.rb b/app/controllers/web/users/application_controller.rb new file mode 100644 index 00000000..3d8acc9c --- /dev/null +++ b/app/controllers/web/users/application_controller.rb @@ -0,0 +1,3 @@ +class Web::Users::ApplicationController < Web::ApplicationController + before_filter :authenticate_user! +end diff --git a/app/views/web/account/index.html.haml b/app/views/web/users/account/index.html.haml similarity index 100% rename from app/views/web/account/index.html.haml rename to app/views/web/users/account/index.html.haml diff --git a/config/routes.rb b/config/routes.rb index 2b99a94e..a58abb6a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ root to: 'web/welcome#index' get '/auth/:provider/callback' => 'web/omniauth#callback' + get 'account' => 'web/users/account#index' scope module: :web do resource :session, only: [:new, :create, :destroy] @@ -13,7 +14,8 @@ end end resources :join, only: [ :new, :create ] - resources :account, only: :index + namespace :users do + end namespace :admin do root to: 'welcome#index' resources :users diff --git a/test/controllers/web/members_controller_test.rb b/test/controllers/web/members_controller_test.rb index 63166a06..6a0ef019 100644 --- a/test/controllers/web/members_controller_test.rb +++ b/test/controllers/web/members_controller_test.rb @@ -21,7 +21,7 @@ class Web::MembersControllerTest < ActionController::TestCase attributes = attributes_for :member post :create, member: attributes assert_response :redirect, @response.body - assert_redirected_to account_index_path + assert_redirected_to account_path assert_equal attributes[:patronymic], Member.last.patronymic end diff --git a/test/controllers/web/account_controller_test.rb b/test/controllers/web/users/account_controller_test.rb similarity index 72% rename from test/controllers/web/account_controller_test.rb rename to test/controllers/web/users/account_controller_test.rb index 58f8d120..9bc5cd38 100644 --- a/test/controllers/web/account_controller_test.rb +++ b/test/controllers/web/users/account_controller_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class Web::AccountControllerTest < ActionController::TestCase +class Web::Users::AccountControllerTest < ActionController::TestCase setup do @user = create :user sign_in @user diff --git a/test/controllers/web/users/application_controller_test.rb b/test/controllers/web/users/application_controller_test.rb new file mode 100644 index 00000000..35592cb8 --- /dev/null +++ b/test/controllers/web/users/application_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::Users::ApplicationControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end From b935b7ea3de284d013c1de30ca9be6dc4ff6d73e Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 05:58:33 +0300 Subject: [PATCH 138/227] auths to account controller --- app/controllers/web/users/account_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index c0db7b0e..b858b192 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -1,4 +1,5 @@ class Web::Users::AccountController < Web::Users::ApplicationController def index + @authentications = current_user.authentications end end From ce4f7fe20d40e5c8f87c4839f7528722363d89a1 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 06:16:21 +0300 Subject: [PATCH 139/227] add form to acccount path --- app/controllers/web/omniauth_controller.rb | 2 +- .../web/users/account_controller.rb | 15 +++++++++++++ app/views/web/users/account/index.html.haml | 9 ++++++++ config/routes.rb | 1 + .../web/users/account_controller_test.rb | 22 +++++++++++++++++-- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/app/controllers/web/omniauth_controller.rb b/app/controllers/web/omniauth_controller.rb index f1187c47..62e347ec 100644 --- a/app/controllers/web/omniauth_controller.rb +++ b/app/controllers/web/omniauth_controller.rb @@ -25,7 +25,7 @@ def callback end Authentication.create user_id: current_user.id, provider: provider, uid: uid end - redirect_to account_index_path + redirect_to account_path end alias :google :callback diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index b858b192..7c9e3d33 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -1,5 +1,20 @@ class Web::Users::AccountController < Web::Users::ApplicationController def index + @user = User.find current_user.id + @member = @user.member @authentications = current_user.authentications end + + def update + if params[:user] + @user = User.find params[:id] + @user_form = UserForm.new @user + @user_form.submit params[:user] + if @user_form.save + redirect_to account_path + else + redirect_to account_path + end + end + end end diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index e69de29b..1345ff5d 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -0,0 +1,9 @@ +- if @member.present? + = simple_form_for +- else + = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| + = f.input :first_name, as: :string + = f.input :last_name, as: :string + = f.input :email, as: :string + = f.input :password, as: :string + = f.button :submit, t('helpers.links.save') diff --git a/config/routes.rb b/config/routes.rb index a58abb6a..7143a56c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,6 +15,7 @@ end resources :join, only: [ :new, :create ] namespace :users do + resources :account, only: [ :update ] end namespace :admin do root to: 'welcome#index' diff --git a/test/controllers/web/users/account_controller_test.rb b/test/controllers/web/users/account_controller_test.rb index 9bc5cd38..0595f763 100644 --- a/test/controllers/web/users/account_controller_test.rb +++ b/test/controllers/web/users/account_controller_test.rb @@ -2,12 +2,30 @@ class Web::Users::AccountControllerTest < ActionController::TestCase setup do - @user = create :user - sign_in @user + @member = create :member + sign_in @member.user end test 'should get index' do get :index assert_response :success, @response.body end + + test 'should patch update with user' do + attributes = attributes_for :user + patch :update, user: attributes, id: @member.user + assert_response :redirect, @response.body + assert_redirected_to account_path + @member.user.reload + assert_equal attributes[:first_name], @member.user.first_name + end + + test 'should patch update with member' do + attributes = attributes_for :member + patch :update, member: attributes, id: @member + assert_response :redirect, @response.body + assert_redirected_to account_path + @member.reload + assert_equal attributes[:patronymic], @member.patronymic + end end From 5379e46b6bf689ab31f425a011c5812c136a8eb3 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 11:18:26 +0300 Subject: [PATCH 140/227] Fixed wrong sorting by param I changed form 'created_at' to 'published_at', because news must be sorted by published_at, not created_at modified: app/controllers/web/admin/news_controller.rb modified: app/controllers/web/news_controller.rb --- app/controllers/web/admin/news_controller.rb | 4 ++-- app/controllers/web/news_controller.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index d04094ac..e410c1d5 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -1,7 +1,7 @@ class Web::Admin::NewsController < Web::Admin::ApplicationController def index - @published_news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') #FIXME using another decorator in that file - @unpublished_news = Web::NewsDecorator.decorate_collection News.unpublished.order('created_at DESC') + @published_news = Web::NewsDecorator.decorate_collection News.published.order('published_at DESC') #FIXME using another decorator in that file + @unpublished_news = Web::NewsDecorator.decorate_collection News.unpublished.order('published_at DESC') end def show diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index a31a06aa..8f7a0cbe 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -1,6 +1,6 @@ class Web::NewsController < Web::ApplicationController def index - @news = Web::NewsDecorator.decorate_collection News.published.order('created_at DESC') + @news = Web::NewsDecorator.decorate_collection News.published.order('published_at DESC') end def show From 6a6f91a13cfb08ab6e0677f5cf879c97c4b3f6b2 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 11:26:49 +0300 Subject: [PATCH 141/227] fix timezone --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index 1805c7be..d5fe0da6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -18,7 +18,7 @@ class Application < Rails::Application # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' + config.time_zone = 'Moscow' # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] From b279b3960509a856d08a43e971aa1ad47756ed90 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 11:27:25 +0300 Subject: [PATCH 142/227] Deleted some comments in view modified: app/views/web/admin/news/index.html.haml --- app/views/web/admin/news/index.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/web/admin/news/index.html.haml b/app/views/web/admin/news/index.html.haml index db5b9c10..765f959d 100644 --- a/app/views/web/admin/news/index.html.haml +++ b/app/views/web/admin/news/index.html.haml @@ -3,12 +3,11 @@ .page-header %h1=t '.title' -#tabs %ul.nav.nav-tabs{ role: :tablist } %li = link_to t('.published'), '#published' %li - = link_to t('.unpublished'), '#unpublished'#{@unpublished_news.count}", '#unpublished' + = link_to t('.unpublished'), '#unpublished' #published = render 'news_list', news: @published_news #unpublished From eaecb5467e9da1eb503c39fd4785ca6b51f97902 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 11:30:32 +0300 Subject: [PATCH 143/227] fix wrong param from :user to :news ;) --- app/controllers/web/admin/news_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index e410c1d5..1805be29 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -34,7 +34,7 @@ def edit def update @news = News.find params[:id] @news_form = NewsForm.new(@news) - @news_form.submit params[:user] + @news_form.submit params[:news] if @news_form.save redirect_to admin_news_index_path else From 6bf1c1a5c8d13351411fa2ac65254c0f8f858844 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 14:07:47 +0300 Subject: [PATCH 144/227] Add some test for actions in Web::Admin::NewsController --- .../web/admin/news_controller_test.rb | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb index 5e16c00c..6eda9961 100644 --- a/test/controllers/web/admin/news_controller_test.rb +++ b/test/controllers/web/admin/news_controller_test.rb @@ -3,30 +3,64 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase setup do @news = create :news - end - - test 'should get index' do - get :index - assert_response :success, @response.body + @admin = create :admin + sign_in @admin end - test 'should get show' do - get :show, id: @news - assert_response :success, @response.body + test "should get index" do + get :index + assert_response :success end - test 'should get new' do + test "should get new" do get :new - assert_response :success, @response.body + assert_response :success end - test 'should create news' do + test "should create news" do attributes = attributes_for :news + post :create, news: attributes - assert_response :redirect, @response.body - assert_redirected_to admin_news_index_path - assert_equal attributes[:body], News.last.body + assert_response :redirect + + news = News.last + assert_equal attributes[:title], news.title + end + + test "should not create news" do + attributes = { body: @news.body } + + post :create, news: attributes + assert_response :redirect + end + + test "should get edit by admin" do + get :edit, id: @news + assert_response :success end - + test "should update news by admin" do + attributes = attributes_for :news + put :update, id: @news, news: attributes + assert_response :redirect + + @news.reload + assert_equal attributes[:title], @news.title + end + + test "should not update news with render edit" do + attributes = attributes_for :news + attributes[:title] = nil + put :update, id: @news, news: attributes + + assert_response :redirect + end + + test "should destroy news" do + assert_difference('News.count', -1) do + delete :destroy, id: @news + end + + assert_redirected_to admin_news_index_path + end end From 888ede8c5f7c77a9127136aa764a9e17d3ee80b9 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 14:10:18 +0300 Subject: [PATCH 145/227] Add beauty probels:D --- app/models/news.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/news.rb b/app/models/news.rb index 794ff827..58672ebe 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -1,5 +1,5 @@ class News < ActiveRecord::Base - mount_uploader :photo, PhotoUploader + mount_uploader :photo, PhotoUploader validates :title, presence: true validates :body, presence: true validates :published_at, presence: true From 93cc4131cc14288742c17cbbdbc11305cb47e004 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 14:11:33 +0300 Subject: [PATCH 146/227] fix wrong links --- app/views/web/admin/news/_news_list.html.haml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/web/admin/news/_news_list.html.haml b/app/views/web/admin/news/_news_list.html.haml index 0384c042..4c8c8896 100644 --- a/app/views/web/admin/news/_news_list.html.haml +++ b/app/views/web/admin/news/_news_list.html.haml @@ -1,24 +1,24 @@ -= link_to t('.new', default: t("helpers.links.new")), new_admin_news_path, class: 'btn btn-primary' - model_class = News %table.table.table-striped.table-condensed %thead %tr %th= model_class.human_attribute_name(:id) + %th= model_class.human_attribute_name(:author_id) %th= model_class.human_attribute_name(:title) %th= model_class.human_attribute_name(:body) - %th= model_class.human_attribute_name(:published_at) - %th= model_class.human_attribute_name(:created_at) - %th=t '.actions', default: t("helpers.actions") + %th= model_class.human_attribute_name(:published) + %th= model_class.human_attribute_name(:created) + %th=t '.actions', default: t('helpers.actions') %tbody - news.each do |news| %tr %td= link_to news.id, edit_admin_news_path(news) + %td= news.author_id %td= link_to news.title, edit_admin_news_path(news) %td= news.lead %td=l news.published_at %td=l news.created_at %td - = link_to t('.edit', default: t("helpers.links.edit")), edit_admin_news_path(news), class: 'btn btn-warning btn-xs' - = link_to t('.destroy', default: t("helpers.links.destroy")), admin_news_path(news), method: :delete, data: { confirm: t('.confirm', default: t("helpers.links.confirm", default: 'Are you sure?')) }, class: 'btn btn-xs btn-danger' + = link_to t('.edit', default: t('helpers.links.edit')), edit_admin_news_path(news), class: 'btn btn-warning btn-xs' + = button_to 'Destroy', admin_news_path(news), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-xs btn-danger' -= link_to t('.new', default: t("helpers.links.new")), new_admin_news_path, class: 'btn btn-primary' From ba0085e41c5237c7da2d1426f5e003070e2b9eff Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 14:12:52 +0300 Subject: [PATCH 147/227] Add links to create news --- app/views/web/admin/news/index.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/web/admin/news/index.html.haml b/app/views/web/admin/news/index.html.haml index 765f959d..2b8e588d 100644 --- a/app/views/web/admin/news/index.html.haml +++ b/app/views/web/admin/news/index.html.haml @@ -9,6 +9,9 @@ %li = link_to t('.unpublished'), '#unpublished' #published + = link_to t('.new', default: t('helpers.links.new')), new_admin_news_path, class: 'btn btn-primary' = render 'news_list', news: @published_news #unpublished + = link_to t('.new', default: t('helpers.links.new')), new_admin_news_path, class: 'btn btn-primary' = render 'news_list', news: @unpublished_news + = link_to t('.new', default: t('helpers.links.new')), new_admin_news_path, class: 'btn btn-primary' From 7664e1d110e3076537b9dee61ee11310848af009 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 14:13:36 +0300 Subject: [PATCH 148/227] Changed remove to destroy :D --- app/controllers/web/admin/news_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index 1805be29..c19c7e76 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -44,7 +44,7 @@ def update def destroy @news = News.find params[:id] - @news.remove + @news.destroy redirect_to admin_news_index_path end end From bba38780a61c2f7875697da5b324335a2001783f Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 14:56:50 +0300 Subject: [PATCH 149/227] localize some text --- app/views/web/admin/news/_form.html.haml | 2 +- app/views/web/admin/news/_news_list.html.haml | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index 0e8e738e..a71ddc1e 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -1,5 +1,5 @@ .page-header - %h1= "TITLE" #FIXME Wrong title :) + %h1=t '.title' .row .col-lg-6 = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| diff --git a/app/views/web/admin/news/_news_list.html.haml b/app/views/web/admin/news/_news_list.html.haml index 4c8c8896..477e2628 100644 --- a/app/views/web/admin/news/_news_list.html.haml +++ b/app/views/web/admin/news/_news_list.html.haml @@ -19,6 +19,11 @@ %td=l news.published_at %td=l news.created_at %td - = link_to t('.edit', default: t('helpers.links.edit')), edit_admin_news_path(news), class: 'btn btn-warning btn-xs' - = button_to 'Destroy', admin_news_path(news), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-xs btn-danger' - + = link_to t('.edit', default: t('helpers.links.edit')), + edit_admin_news_path(news), + class: 'btn btn-warning btn-xs' + = button_to t('.destroy', default: t('helpers.links.destroy')), + admin_news_path(news), method: :delete, + data: { confirm: 'Are you sure?' }, + class: 'btn btn-xs btn-danger' +/fixme wrong localize From 443335482c88b3755dd81bb4fa94973fd8b0bbd8 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 15:12:43 +0300 Subject: [PATCH 150/227] moved unicorn-rails to production group --- Gemfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 3eaeff49..cb014f28 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,6 @@ gem 'turbolinks' gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc -gem 'unicorn-rails' gem 'haml-rails' gem 'enumerize' gem 'authority' @@ -50,6 +49,10 @@ gem 'momentjs-rails', '>= 2.8.1', :github => 'derekprior/momentjs-rails' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +group :production do + gem 'unicorn-rails' +end + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' From 9e631fd3ba08a412c00f477a31a6e22dd1bcd7bb Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 15:28:03 +0300 Subject: [PATCH 151/227] Add global News decorator --- app/controllers/web/admin/news_controller.rb | 6 +++--- app/controllers/web/news_controller.rb | 4 ++-- app/decorators/{web => }/news_decorator.rb | 2 +- app/decorators/web/admin/news_decorator.rb | 20 -------------------- 4 files changed, 6 insertions(+), 26 deletions(-) rename app/decorators/{web => }/news_decorator.rb (87%) delete mode 100644 app/decorators/web/admin/news_decorator.rb diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index c19c7e76..e9f1b55c 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -1,11 +1,11 @@ class Web::Admin::NewsController < Web::Admin::ApplicationController def index - @published_news = Web::NewsDecorator.decorate_collection News.published.order('published_at DESC') #FIXME using another decorator in that file - @unpublished_news = Web::NewsDecorator.decorate_collection News.unpublished.order('published_at DESC') + @published_news = NewsDecorator.decorate_collection News.published.order('published_at DESC') #FIXME using another decorator in that file + @unpublished_news = NewsDecorator.decorate_collection News.unpublished.order('published_at DESC') end def show - @news = Web::NewsDecorator.decorate News.find params[:id]#FIXME I'm don't know how to include decorate there into code + @news = NewsDecorator.decorate News.find params[:id]#FIXME I'm don't know how to include decorate there into code if !@news.is_published? #FIXME there 404 error path end diff --git a/app/controllers/web/news_controller.rb b/app/controllers/web/news_controller.rb index 8f7a0cbe..a15e8e0d 100644 --- a/app/controllers/web/news_controller.rb +++ b/app/controllers/web/news_controller.rb @@ -1,10 +1,10 @@ class Web::NewsController < Web::ApplicationController def index - @news = Web::NewsDecorator.decorate_collection News.published.order('published_at DESC') + @news = NewsDecorator.decorate_collection News.published.order('published_at DESC') end def show - @news = Web::NewsDecorator.decorate News.find params[:id] #FIXME I'm don't know how to include decorate there into code + @news = NewsDecorator.decorate News.find params[:id] #FIXME I'm don't know how to include decorate there into code if !@news.is_published? #FIXME there 404 error path end diff --git a/app/decorators/web/news_decorator.rb b/app/decorators/news_decorator.rb similarity index 87% rename from app/decorators/web/news_decorator.rb rename to app/decorators/news_decorator.rb index 17093be0..5f7b8993 100644 --- a/app/decorators/web/news_decorator.rb +++ b/app/decorators/news_decorator.rb @@ -1,4 +1,4 @@ -class Web::NewsDecorator < Draper::Decorator +class NewsDecorator < Draper::Decorator delegate_all def short_lead diff --git a/app/decorators/web/admin/news_decorator.rb b/app/decorators/web/admin/news_decorator.rb deleted file mode 100644 index 4a3d47a8..00000000 --- a/app/decorators/web/admin/news_decorator.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Web::Admin::NewsDecorator < Draper::Decorator - delegate_all - - def lead - "#{model.body.first(250)}..." - end - - def description_lead - "#{model.body.first(200)}..." - end - - def long_lead - "#{model.body.first(600)}..." - end - - def publish_date_time - l(object.published_at)[0..22] - end - -end From ef2d2f7d88455c5d6f67d290774538c329a109f3 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 15:36:29 +0300 Subject: [PATCH 152/227] author_id => user_id It was being changed to new name modified: db/migrate/20150302192318_create_news.rb deleted: db/migrate/20150302234749_add_photo_to_news.rb deleted: db/migrate/20150303003907_add_author_id_to_news.rb --- app/models/news.rb | 2 +- app/views/web/admin/news/_news_list.html.haml | 2 +- db/migrate/20150302192318_create_news.rb | 3 ++- db/migrate/20150302234749_add_photo_to_news.rb | 5 ----- db/migrate/20150303003907_add_author_id_to_news.rb | 5 ----- 5 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 db/migrate/20150302234749_add_photo_to_news.rb delete mode 100644 db/migrate/20150303003907_add_author_id_to_news.rb diff --git a/app/models/news.rb b/app/models/news.rb index 58672ebe..368c2bb3 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -4,7 +4,7 @@ class News < ActiveRecord::Base validates :body, presence: true validates :published_at, presence: true validates :photo, presence: false - validates :author_id, presence: true + validates :user_id, presence: true def is_published? published_at <= DateTime.now diff --git a/app/views/web/admin/news/_news_list.html.haml b/app/views/web/admin/news/_news_list.html.haml index 477e2628..a8105ee8 100644 --- a/app/views/web/admin/news/_news_list.html.haml +++ b/app/views/web/admin/news/_news_list.html.haml @@ -13,7 +13,7 @@ - news.each do |news| %tr %td= link_to news.id, edit_admin_news_path(news) - %td= news.author_id + %td= news.user_id %td= link_to news.title, edit_admin_news_path(news) %td= news.lead %td=l news.published_at diff --git a/db/migrate/20150302192318_create_news.rb b/db/migrate/20150302192318_create_news.rb index 5f9f3361..f7f12fae 100644 --- a/db/migrate/20150302192318_create_news.rb +++ b/db/migrate/20150302192318_create_news.rb @@ -4,7 +4,8 @@ def change t.string :title t.text :body t.datetime :published_at - + t.text :photo + t.integer :user_id t.timestamps null: false end end diff --git a/db/migrate/20150302234749_add_photo_to_news.rb b/db/migrate/20150302234749_add_photo_to_news.rb deleted file mode 100644 index 2ca771e8..00000000 --- a/db/migrate/20150302234749_add_photo_to_news.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddPhotoToNews < ActiveRecord::Migration - def change - add_column :news, :photo, :text - end -end diff --git a/db/migrate/20150303003907_add_author_id_to_news.rb b/db/migrate/20150303003907_add_author_id_to_news.rb deleted file mode 100644 index 00725db6..00000000 --- a/db/migrate/20150303003907_add_author_id_to_news.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddAuthorIdToNews < ActiveRecord::Migration - def change - add_column :news, :author_id, :integer - end -end From 70b86d456aaeeb09e6320d4a437f507ea1a07738 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 16:04:55 +0300 Subject: [PATCH 153/227] update db/schema --- db/schema.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 15982847..aa0b9f04 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150303003907) do +ActiveRecord::Schema.define(version: 20150302192318) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -28,10 +28,10 @@ t.string "title" t.text "body" t.datetime "published_at" + t.text "photo" + t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "photo" - t.integer "author_id" end create_table "users", force: :cascade do |t| From 51ae0451165b659db5852e3ec6c5757dfd0dd1de Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 16:05:56 +0300 Subject: [PATCH 154/227] remove sequence --- test/factories/news.rb | 2 +- test/factories/sequences.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/test/factories/news.rb b/test/factories/news.rb index 30303fa2..85126a8d 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -4,6 +4,6 @@ body { generate :string } published_at { DateTime.now } photo { generate :file } - author_id { "1" } + author_id { generate :integer } end end diff --git a/test/factories/sequences.rb b/test/factories/sequences.rb index 3e5cffb2..cb6eff0d 100644 --- a/test/factories/sequences.rb +++ b/test/factories/sequences.rb @@ -28,7 +28,4 @@ sequence :human_name do "Leopold" end - sequence :number do - Random.rand(2) - end end From ac054093b41c7156d77abd0d8845b008410a9a0d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 16:06:20 +0300 Subject: [PATCH 155/227] fix localization --- config/application.rb | 2 +- config/environments/production.rb | 1 - config/environments/test.rb | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index d5fe0da6..9cfc7404 100644 --- a/config/application.rb +++ b/config/application.rb @@ -15,7 +15,7 @@ class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - + config.i18n.default_locale = :ru # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. config.time_zone = 'Moscow' diff --git a/config/environments/production.rb b/config/environments/production.rb index 81f0dc7b..5c1b32e4 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -74,7 +74,6 @@ # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new - config.i18n.default_locale = :ru # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index c0aade09..1c19f08b 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -31,7 +31,6 @@ # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - config.i18n.default_locale = :en # Randomize the order test cases are executed. config.active_support.test_order = :random From 4278fef1eac792b299e21e7e90b87431a5a174ae Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 16:08:21 +0300 Subject: [PATCH 156/227] author_id => user_id --- app/forms/news_form.rb | 2 +- app/models/news.rb | 4 ++-- app/views/web/admin/news/_form.html.haml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/forms/news_form.rb b/app/forms/news_form.rb index 175b9a66..5dd10e6f 100644 --- a/app/forms/news_form.rb +++ b/app/forms/news_form.rb @@ -1,5 +1,5 @@ class NewsForm < ApplicationForm self.main_model = :news - attributes :title, :body, :published_at, :photo, :author_id + attributes :title, :body, :published_at, :photo, :user_id end diff --git a/app/models/news.rb b/app/models/news.rb index 368c2bb3..da56c5a7 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -13,7 +13,7 @@ def is_published? extend Enumerize - scope :published, -> { where "published_at <= ?", DateTime.now} - scope :unpublished, -> { where "published_at > ?", DateTime.now} + scope :published, -> { where 'published_at <= ?', DateTime.now} + scope :unpublished, -> { where 'published_at > ?', DateTime.now} end diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index a71ddc1e..d32a5de4 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -6,7 +6,7 @@ = f.input :title, as: :string = f.input :body, as: :string = f.input :published_at, as: :datetime_picker - = f.input :author_id, as: :string + = f.input :user_id, as: :string /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } = f.button :submit, class: 'btn-success' From a541e18e5e7105ea5944e7b4561b6471848cbf4f Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 16:09:52 +0300 Subject: [PATCH 157/227] some fix --- app/views/web/admin/news/show.html.haml | 4 ++-- app/views/web/news/show.html.haml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/web/admin/news/show.html.haml b/app/views/web/admin/news/show.html.haml index 9b414df7..c4611352 100644 --- a/app/views/web/admin/news/show.html.haml +++ b/app/views/web/admin/news/show.html.haml @@ -1,7 +1,7 @@ - content_for :meta do - %meta{ property: 'og:description', content: "#{@news.description_lead} #ЯЛидер" } + %meta{ property: 'og:description', content: "#{@news.description_lead} #МИЦ" } %meta{ property:'og:image', content: @news.photo } - %meta{ name:'description', content: "#{@news.description_lead} #ЯЛидер" } + %meta{ name:'description', content: "#{@news.description_lead} #МИЦ" } %link{ rel: 'image_src', href: @news.photo } .show .title diff --git a/app/views/web/news/show.html.haml b/app/views/web/news/show.html.haml index 3f0cd0ca..c4611352 100644 --- a/app/views/web/news/show.html.haml +++ b/app/views/web/news/show.html.haml @@ -1,7 +1,7 @@ - content_for :meta do - %meta{ property: 'og:description', content: "#{@news.description_lead} #ЯЛидер" } + %meta{ property: 'og:description', content: "#{@news.description_lead} #МИЦ" } %meta{ property:'og:image', content: @news.photo } - %meta{ name:'description', content: "#{@news.description_lead} #ЯЛидер" } + %meta{ name:'description', content: "#{@news.description_lead} #МИЦ" } %link{ rel: 'image_src', href: @news.photo } .show .title @@ -10,4 +10,4 @@ = @news.publish_date_time .body = image_tag @news.photo, class: 'main_photo' - != @news.body + = @news.body From 15e6683ab8307a6af9e77eaa76d5ffe2fffe9f7d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sat, 7 Mar 2015 16:11:59 +0300 Subject: [PATCH 158/227] deleted strip_tags --- app/views/web/news/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/web/news/index.html.haml b/app/views/web/news/index.html.haml index f22bd6a9..cbce6c6c 100644 --- a/app/views/web/news/index.html.haml +++ b/app/views/web/news/index.html.haml @@ -5,4 +5,4 @@ = news.title .body = image_tag news.photo - != strip_tags news.long_lead + = strip_tags news.long_lead From 70199253dab359d09a3d88301173e4d569026936 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 23:48:49 +0300 Subject: [PATCH 159/227] fix forms on acccount --- .../web/users/account_controller.rb | 3 +-- app/decorators/user_decorator.rb | 4 ++++ app/views/web/users/account/index.html.haml | 24 +++++++++++++++++-- config/locales/ru/ru.yml | 9 +++++++ config/routes.rb | 2 +- 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index 7c9e3d33..7e258602 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -1,7 +1,6 @@ class Web::Users::AccountController < Web::Users::ApplicationController def index - @user = User.find current_user.id - @member = @user.member + @user = User.find(current_user.id).decorate @authentications = current_user.authentications end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index d5bad13b..6e7d08ce 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -5,4 +5,8 @@ def name "#{first_name} #{last_name}" end + def has_confirmed_member? + member.confirmed? if member.present? + end + end diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index 1345ff5d..e6eb1bfa 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -1,5 +1,19 @@ -- if @member.present? - = simple_form_for +- if @user.has_confirmed_member? + = simple_form_for @user.member, url: { controller: 'web/users/account', action: :update, id: @user.member.id } do |f| + = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } + = f.input :patronymic, as: :string + = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } + = f.input :motto, as: :string + = f.input :ticket, as: :string, required: true + = f.input :mobile_phone, as: :string + = f.input :birth_date, as: :string + = f.input :home_adress, as: :string + = f.input :municipality, as: :string + = f.input :locality, as: :string + = f.input :avatar, as: :string + = f.input :user_id, as: :hidden, input_html: { value: current_user.id } + = f.button :submit, t('helpers.links.save'), class: 'btn-success' + = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' - else = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string @@ -7,3 +21,9 @@ = f.input :email, as: :string = f.input :password, as: :string = f.button :submit, t('helpers.links.save') + - unless @user.member.present? + = t('.you_are_member_mic') + = link_to t('.enter_datas_mic_member'), new_member_path + %br + = t('.you_are_not_member_mic') + = link_to t('organization.send_request'), new_join_path diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 76d688cf..92d119b0 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -1,6 +1,10 @@ ru: application: name: Сайт МИЦ + organization: + full_name: "Ульяновская областная молодёжная общественная организация «Молодёжный инициативный центр»" + short_name: "УОМОО «МИЦ»" + send_request: Подать заявку на вступление в МИЦ helpers: enter: Войти actions: @@ -29,6 +33,11 @@ ru: confirm: Принять в организацию web: users: + account: + index: + you_are_member_mic: Вы являетесь членом МИЦ + you_are_not_member_mic: Вы не являетесь членом МИЦ + enter_datas_mic_member: Заполните профиль члена МИЦ new: register: Регистрация admin: diff --git a/config/routes.rb b/config/routes.rb index 7143a56c..2df0a371 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,13 +4,13 @@ get '/auth/:provider/callback' => 'web/omniauth#callback' get 'account' => 'web/users/account#index' + get '/:ticket' => 'web/members#show' scope module: :web do resource :session, only: [:new, :create, :destroy] resources :users, only: [ :new, :create ] resources :members, only: [ :new, :create ] do collection do - get '/:ticket' => 'members#show' end end resources :join, only: [ :new, :create ] From f5e5d9965397791a585b9822156a98a2dc21b5a6 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sat, 7 Mar 2015 23:54:41 +0300 Subject: [PATCH 160/227] view member form if confirmed member --- app/views/web/admin/members/index.html.haml | 2 ++ config/routes.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/web/admin/members/index.html.haml b/app/views/web/admin/members/index.html.haml index 29cde00f..679fe9b3 100644 --- a/app/views/web/admin/members/index.html.haml +++ b/app/views/web/admin/members/index.html.haml @@ -19,6 +19,8 @@ %td= member.ticket %td= member.place %td + - unless member.confirmed? + = link_to t('helpers.links.approve'), admin_member_path(member, member: { state: :confirmed }), method: :patch, class: 'btn btn-success btn-xs' = link_to t('helpers.links.edit'), edit_admin_member_path(member), class: 'btn btn-warning btn-xs' = link_to t('helpers.links.destroy'), admin_member_path(member), method: :delete, class: 'btn btn-xs btn-danger' diff --git a/config/routes.rb b/config/routes.rb index 2df0a371..9e20d8f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,7 @@ get '/auth/:provider/callback' => 'web/omniauth#callback' get 'account' => 'web/users/account#index' + get '/admin' => 'web/admin/welcome#index' get '/:ticket' => 'web/members#show' scope module: :web do @@ -18,7 +19,6 @@ resources :account, only: [ :update ] end namespace :admin do - root to: 'welcome#index' resources :users resources :members resources :unviewed, only: :index From d1c74fa37c1f90172385feae37db4d05b81fd960 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 00:00:20 +0300 Subject: [PATCH 161/227] fix factories --- test/factories/news.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/factories/news.rb b/test/factories/news.rb index 85126a8d..90e25cf1 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -4,6 +4,6 @@ body { generate :string } published_at { DateTime.now } photo { generate :file } - author_id { generate :integer } + user_id { generate :integer } end end From c38aeab7cd68119e81f7b78771286a0ca28e83b6 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 00:26:09 +0300 Subject: [PATCH 162/227] edit auth on account page --- .../web/users/authentications_controller.rb | 7 +++++++ app/helpers/application_helper.rb | 14 ++++++++++++++ app/views/web/users/account/index.html.haml | 9 ++++++++- config/routes.rb | 3 ++- lib/social_networks.rb | 10 ++++++++++ lib/yaml/social_networks.yml | 5 +++++ .../web/users/authentications_controller_test.rb | 7 +++++++ 7 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 app/controllers/web/users/authentications_controller.rb create mode 100644 lib/social_networks.rb create mode 100644 lib/yaml/social_networks.yml create mode 100644 test/controllers/web/users/authentications_controller_test.rb diff --git a/app/controllers/web/users/authentications_controller.rb b/app/controllers/web/users/authentications_controller.rb new file mode 100644 index 00000000..36a97406 --- /dev/null +++ b/app/controllers/web/users/authentications_controller.rb @@ -0,0 +1,7 @@ +class Web::Users::AuthenticationsController < Web::Users::ApplicationController + def destroy + @authentication = Authentication.find params[:id] + @authentication.destroy + redirect_to account_path + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 51aac156..42cfa3a5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -38,4 +38,18 @@ def uri_state(uri, options = {}) def auth_path(provider) "/auth/#{provider}" end + + include SocialNetworks + + def social_network_localize(provider) + SocialNetworks.localize provider + end + + def not_linked_social_networks(authentications) + list = SocialNetworks.list + authentications.each do |auth| + list -= [ auth.provider ] + end + list + end end diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index e6eb1bfa..8929fe6c 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -24,6 +24,13 @@ - unless @user.member.present? = t('.you_are_member_mic') = link_to t('.enter_datas_mic_member'), new_member_path - %br = t('.you_are_not_member_mic') = link_to t('organization.send_request'), new_join_path + +- @authentications.each do |auth| + = social_network_localize auth.provider + = link_to t('helpers.links.destroy'), users_authentication_path(auth), method: :delete, class: 'btn btn-danger btn-xs' + +- not_linked_social_networks(@authentications).each do |network| + = social_network_localize network + = link_to t('helpers.links.auth'), auth_path(network), class: 'btn btn-success btn-xs' diff --git a/config/routes.rb b/config/routes.rb index 9e20d8f9..b4b75a3b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,7 +16,8 @@ end resources :join, only: [ :new, :create ] namespace :users do - resources :account, only: [ :update ] + resources :account, only: :update + resources :authentications, only: :destroy end namespace :admin do resources :users diff --git a/lib/social_networks.rb b/lib/social_networks.rb new file mode 100644 index 00000000..1cc600b2 --- /dev/null +++ b/lib/social_networks.rb @@ -0,0 +1,10 @@ +module SocialNetworks + def self.localize(network_name) + networks = YAML.load_file("#{Rails.root}/lib/yaml/social_networks.yml").with_indifferent_access[:social_networks] + networks[network_name] + end + + def self.list + YAML.load_file("#{Rails.root}/lib/yaml/social_networks.yml").with_indifferent_access[:social_networks].keys + end +end diff --git a/lib/yaml/social_networks.yml b/lib/yaml/social_networks.yml new file mode 100644 index 00000000..2c8a2719 --- /dev/null +++ b/lib/yaml/social_networks.yml @@ -0,0 +1,5 @@ +social_networks: + vkontakte: ВКонтакте + twitter: Твиттер + facebook: Facebook + google: Google diff --git a/test/controllers/web/users/authentications_controller_test.rb b/test/controllers/web/users/authentications_controller_test.rb new file mode 100644 index 00000000..7d816fd3 --- /dev/null +++ b/test/controllers/web/users/authentications_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Web::Users::AuthenticationsControllerTest < ActionController::TestCase + # test "the truth" do + # assert true + # end +end From 15fa506b70b926887423f6c895bd6fdcd4bb9e76 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 00:31:31 +0300 Subject: [PATCH 163/227] add awesome print to all env --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index cb014f28..5522ac1e 100644 --- a/Gemfile +++ b/Gemfile @@ -40,6 +40,7 @@ gem 'omniauth-vkontakte' gem 'omniauth-twitter' gem 'omniauth-facebook' gem 'momentjs-rails', '>= 2.8.1', :github => 'derekprior/momentjs-rails' +gem 'awesome_print' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' From 93f14fb80e6ae48a5b49df0bdae0a48704085d30 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 00:31:42 +0300 Subject: [PATCH 164/227] Fix datetime locales --- app/decorators/news_decorator.rb | 3 ++- config/locales/ru/ru.yml | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/decorators/news_decorator.rb b/app/decorators/news_decorator.rb index 5f7b8993..4603b56e 100644 --- a/app/decorators/news_decorator.rb +++ b/app/decorators/news_decorator.rb @@ -18,7 +18,8 @@ def long_lead end def publish_date_time - l(object.published_at)[0..22] + #l(object.published_at, format: '%d %b %Y %H:%m') + object.published_at.strftime('%d %b %Y %H:%m') end end diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 3bc0aca0..b4d28e47 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -1,4 +1,9 @@ ru: + time: + formats: + default: "%Y/%m/%d" + short: "%b %d" + long: "%B %d, %Y" application: name: Сайт МИЦ helpers: From 86753c84099a457f0f23002a606f054176266c68 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 00:42:57 +0300 Subject: [PATCH 165/227] add fcuntional for update memberj --- app/controllers/web/users/account_controller.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index 7e258602..a3693e15 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -14,6 +14,15 @@ def update else redirect_to account_path end + elsif params[:member] + @member = Member.find params[:id] + @member_form = MemberForm.new @member + @member_form.submit params[:member] + if @member_form.save + redirect_to account_path + else + redirect_to account_path + end end end end From b5af1aaf579177b0f87e052b4c1048b77cb59c07 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 01:40:44 +0300 Subject: [PATCH 166/227] fix logic of questionary create --- app/controllers/web/join_controller.rb | 2 +- app/decorators/user_decorator.rb | 3 +++ app/models/member.rb | 4 +++- app/models/questionary.rb | 2 +- app/views/web/join/new.html.haml | 16 ++++++++-------- app/views/web/users/account/index.html.haml | 4 +++- db/schema.rb | 10 +++++++++- 7 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/controllers/web/join_controller.rb b/app/controllers/web/join_controller.rb index e33593c8..bde96b49 100644 --- a/app/controllers/web/join_controller.rb +++ b/app/controllers/web/join_controller.rb @@ -11,7 +11,7 @@ def create @questionary_form = QuestionaryForm.new @questionary @questionary_form.submit params[:questionary] if @questionary_form.save - redirect_to root_path + redirect_to account_path else render action: :new end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index 6e7d08ce..d7f12537 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -9,4 +9,7 @@ def has_confirmed_member? member.confirmed? if member.present? end + def has_member? + not member.removed? if member.present? + end end diff --git a/app/models/member.rb b/app/models/member.rb index 3c4be599..de67d646 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -4,6 +4,7 @@ class Member < ActiveRecord::Base belongs_to :user belongs_to :parent, class_name: 'Member' has_one :questionary + has_many :attribute_accesses validates :patronymic, presence: true, human_name: true @@ -17,7 +18,7 @@ class Member < ActiveRecord::Base validates :locality, presence: true validates :avatar, presence: true - scope :presented, -> { where.not(state: :removed) } + scope :presented, -> { where('state != \'removed\' AND state != \'not_member\'') } scope :removed, -> { where state: :removed } scope :unviewed, -> { where state: :unviewed } @@ -26,6 +27,7 @@ class Member < ActiveRecord::Base state :confirmed state :declined state :removed + state :not_member event :confirm do transition all => :confirmed diff --git a/app/models/questionary.rb b/app/models/questionary.rb index 2bfa595d..686ec2a6 100644 --- a/app/models/questionary.rb +++ b/app/models/questionary.rb @@ -42,7 +42,7 @@ def user private def update_member - params = { patronymic: patronymic, motto: motto, mobile_phone: mobile_phone, birth_date: birth_date, home_adress: home_adress, municipality: municipality, locality: locality, avatar: avatar, state: state, user_id: user_id, ticket: ticket } + params = { patronymic: patronymic, motto: motto, mobile_phone: mobile_phone, birth_date: birth_date, home_adress: home_adress, municipality: municipality, locality: locality, avatar: avatar, state: :not_member, user_id: user_id, ticket: ticket } params.keys.each do |key| params[key] = nil unless params[key].present? end diff --git a/app/views/web/join/new.html.haml b/app/views/web/join/new.html.haml index 001dff1b..9e2e52d1 100644 --- a/app/views/web/join/new.html.haml +++ b/app/views/web/join/new.html.haml @@ -1,14 +1,14 @@ = simple_form_for @questionary, url: { controller: 'web/join', action: :create } do |f| = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } - = f.input :patronymic, as: :string + = f.input :patronymic, as: :string, required: true = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } - = f.input :motto, as: :string - = f.input :mobile_phone, as: :string - = f.input :birth_date, as: :string - = f.input :home_adress, as: :string - = f.input :municipality, as: :string - = f.input :locality, as: :string - = f.input :avatar, as: :string + = f.input :motto, as: :string, required: true + = f.input :mobile_phone, as: :string, required: true + = f.input :birth_date, as: :string, required: true + = f.input :home_adress, as: :string, required: true + = f.input :municipality, as: :string, required: true + = f.input :locality, as: :string, required: true + = f.input :avatar, as: :string, required: true = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.input :want_to_do = f.input :experience diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index 8929fe6c..1cba450c 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -14,6 +14,8 @@ = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' + - @user.member.attribute_accesses.each do |attr_access| + = check_box :attr_access - else = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string @@ -21,7 +23,7 @@ = f.input :email, as: :string = f.input :password, as: :string = f.button :submit, t('helpers.links.save') - - unless @user.member.present? + - unless @user.has_member? = t('.you_are_member_mic') = link_to t('.enter_datas_mic_member'), new_member_path = t('.you_are_not_member_mic') diff --git a/db/schema.rb b/db/schema.rb index 2cc4cb2c..d887c57d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,11 +11,19 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150306010850) do +ActiveRecord::Schema.define(version: 20150307215230) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "attribute_accesses", force: :cascade do |t| + t.integer "member_id" + t.text "attribute" + t.text "access" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "authentications", force: :cascade do |t| t.text "provider" t.text "uid" From ba880580a1ff65b0bf36d4f2a4e1b556eac7fc50 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 02:04:28 +0300 Subject: [PATCH 167/227] fix locales of social networks --- app/helpers/application_helper.rb | 2 +- lib/social_networks.rb | 7 +------ lib/yaml/social_networks.yml | 8 ++++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 42cfa3a5..456ee093 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -42,7 +42,7 @@ def auth_path(provider) include SocialNetworks def social_network_localize(provider) - SocialNetworks.localize provider + I18n.t("social_networks.#{provider}") end def not_linked_social_networks(authentications) diff --git a/lib/social_networks.rb b/lib/social_networks.rb index 1cc600b2..1238a935 100644 --- a/lib/social_networks.rb +++ b/lib/social_networks.rb @@ -1,10 +1,5 @@ module SocialNetworks - def self.localize(network_name) - networks = YAML.load_file("#{Rails.root}/lib/yaml/social_networks.yml").with_indifferent_access[:social_networks] - networks[network_name] - end - def self.list - YAML.load_file("#{Rails.root}/lib/yaml/social_networks.yml").with_indifferent_access[:social_networks].keys + YAML.load_file("#{Rails.root}/lib/yaml/social_networks.yml").with_indifferent_access[:social_networks] end end diff --git a/lib/yaml/social_networks.yml b/lib/yaml/social_networks.yml index 2c8a2719..0a0bcb83 100644 --- a/lib/yaml/social_networks.yml +++ b/lib/yaml/social_networks.yml @@ -1,5 +1,5 @@ social_networks: - vkontakte: ВКонтакте - twitter: Твиттер - facebook: Facebook - google: Google + - vkontakte + - twitter + - facebook + - google From 1a5161549cfe7019f92fd484306246606053da18 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 06:03:36 +0300 Subject: [PATCH 168/227] add attribute access functional in account --- Gemfile | 2 + Gemfile.lock | 12 ++++++ app/assets/javascripts/application.js | 2 + app/assets/javascripts/i18n_setup.js.coffee | 5 +++ .../javascripts/web/users/account.js.coffee | 19 +++++++++ .../users/attribute_accesses_controller.rb | 15 +++++++ app/forms/attribute_access_form.rb | 5 +++ app/helpers/web/users/account_helper.rb | 7 ++++ app/models/attribute_access.rb | 10 +++++ app/views/web/users/account/index.html.haml | 4 +- config/locales/ru/ru.yml | 7 ++++ config/routes.rb | 1 + ...0150307215230_create_attribute_accesses.rb | 11 +++++ db/schema.rb | 6 +-- lib/accesses.rb | 5 +++ lib/yaml/accesses.yml | 7 ++++ .../attribute_accesses_controller_test.rb | 41 +++++++++++++++++++ test/factories/attribute_accesses.rb | 7 ++++ test/models/attribute_access_test.rb | 7 ++++ 19 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/i18n_setup.js.coffee create mode 100644 app/assets/javascripts/web/users/account.js.coffee create mode 100644 app/controllers/web/users/attribute_accesses_controller.rb create mode 100644 app/forms/attribute_access_form.rb create mode 100644 app/helpers/web/users/account_helper.rb create mode 100644 app/models/attribute_access.rb create mode 100644 db/migrate/20150307215230_create_attribute_accesses.rb create mode 100644 lib/accesses.rb create mode 100644 lib/yaml/accesses.yml create mode 100644 test/controllers/web/users/attribute_accesses_controller_test.rb create mode 100644 test/factories/attribute_accesses.rb create mode 100644 test/models/attribute_access_test.rb diff --git a/Gemfile b/Gemfile index ea0a9ec7..cd832465 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,8 @@ gem 'bootstrap-sass' gem 'state_machine', git: 'https://github.com/seuros/state_machine.git' gem 'draper' gem 'russian' +gem 'js-routes' +gem 'i18n-js', git: 'https://github.com/fnando/i18n-js' gem 'omniauth-google-oauth2' gem 'omniauth-vkontakte' diff --git a/Gemfile.lock b/Gemfile.lock index 9874c8cf..5148daeb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,6 +16,13 @@ GIT chattyproc (~> 1.0.0) term-ansicolor (~> 1.0.7) +GIT + remote: https://github.com/fnando/i18n-js + revision: 09332cd81ffa3b3675ed919f2d6821746d41cd8f + specs: + i18n-js (3.0.0.rc8) + i18n (~> 0.6) + GIT remote: https://github.com/seuros/state_machine.git revision: b5baef856b170f18dc2fdc36def2411c255335b9 @@ -139,6 +146,9 @@ GEM rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) + js-routes (1.0.0) + railties (>= 3.2) + sprockets-rails json (1.8.2) jwt (1.3.0) loofah (2.0.1) @@ -294,8 +304,10 @@ DEPENDENCIES enumerize factory_girl_rails haml-rails + i18n-js! jbuilder (~> 2.0) jquery-rails + js-routes omniauth-facebook omniauth-google-oauth2 omniauth-twitter diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 3e5bd214..8d2ba0c7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,4 +12,6 @@ // //= require jquery //= require jquery_ujs +//= require js-routes +//= require i18n_setup //= require_tree . diff --git a/app/assets/javascripts/i18n_setup.js.coffee b/app/assets/javascripts/i18n_setup.js.coffee new file mode 100644 index 00000000..b58f7b7e --- /dev/null +++ b/app/assets/javascripts/i18n_setup.js.coffee @@ -0,0 +1,5 @@ +#= require i18n +#= require i18n/translations +I18n.defaultLocale = "ru" +I18n.locale = "ru" +I18n.currentLocale() diff --git a/app/assets/javascripts/web/users/account.js.coffee b/app/assets/javascripts/web/users/account.js.coffee new file mode 100644 index 00000000..e5ffbe89 --- /dev/null +++ b/app/assets/javascripts/web/users/account.js.coffee @@ -0,0 +1,19 @@ +$ -> + $('.attribute_access').change -> + value = 'hidden' + if $(this).prop('checked') == true + value = 'visible' + else + value = 'hidden' + $.ajax + data: + attribute_access: + access: value + member_id: $(this).data().id + member_attribute: $(this).attr('name') + url: Routes.users_attribute_accesses_path() + method: 'POST' + success: -> alert(I18n.t('notices.web.users.account.attribute_accesses.changed')) + error: -> alert(I18n.t('notices.web.users.account.attribute_accesses.not_changed')) + return + return diff --git a/app/controllers/web/users/attribute_accesses_controller.rb b/app/controllers/web/users/attribute_accesses_controller.rb new file mode 100644 index 00000000..3bc2ab1f --- /dev/null +++ b/app/controllers/web/users/attribute_accesses_controller.rb @@ -0,0 +1,15 @@ +class Web::Users::AttributeAccessesController < Web::Users::ApplicationController + def create + @access = AttributeAccess.where(member_attribute: params[:attribute_access][:member_attribute], member_id: params[:attribute_access][:member_id]).first + unless @access + @access = AttributeAccess.new + end + @access_form = AttributeAccessForm.new @access + @access_form.submit params[:attribute_access] + if @access_form.save + head :ok + else + head :bad_request + end + end +end diff --git a/app/forms/attribute_access_form.rb b/app/forms/attribute_access_form.rb new file mode 100644 index 00000000..90443f81 --- /dev/null +++ b/app/forms/attribute_access_form.rb @@ -0,0 +1,5 @@ +class AttributeAccessForm < ActiveForm::Base + self.main_model = :attribute_access + + attribute :member_attribute, :member_id, :access +end diff --git a/app/helpers/web/users/account_helper.rb b/app/helpers/web/users/account_helper.rb new file mode 100644 index 00000000..1e5a3d47 --- /dev/null +++ b/app/helpers/web/users/account_helper.rb @@ -0,0 +1,7 @@ +module Web::Users::AccountHelper + include Accesses + + def attributes_need_access + Accesses.attributes + end +end diff --git a/app/models/attribute_access.rb b/app/models/attribute_access.rb new file mode 100644 index 00000000..00ea2ede --- /dev/null +++ b/app/models/attribute_access.rb @@ -0,0 +1,10 @@ +class AttributeAccess < ActiveRecord::Base + belongs_to :member + + validates :member_id, presence: true + validates :member_attribute, presence: true + validates :access, presence: true + + extend Enumerize + enumerize :access, in: [ :visible, :hidden ], default: :hidden +end diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index 1cba450c..f6d4a652 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -14,8 +14,8 @@ = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' - - @user.member.attribute_accesses.each do |attr_access| - = check_box :attr_access + - attributes_need_access.each do |attribute| + = check_box_tag attribute, '', false, class: 'attribute_access', data: { id: @user.member.id } - else = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 92d119b0..6ef628fd 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -25,6 +25,13 @@ ru: facebook: Facebook twitter: Twitter google: Google + notices: + web: + users: + account: + attribute_accesses: + changed: Доступ к этому аттрибуту успешно изменён + not_changed: Доступ к этому аттрибуту не изменён state_machines: questionary: state: diff --git a/config/routes.rb b/config/routes.rb index b4b75a3b..79ec553e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,6 +18,7 @@ namespace :users do resources :account, only: :update resources :authentications, only: :destroy + resources :attribute_accesses, only: :create end namespace :admin do resources :users diff --git a/db/migrate/20150307215230_create_attribute_accesses.rb b/db/migrate/20150307215230_create_attribute_accesses.rb new file mode 100644 index 00000000..c4895d3f --- /dev/null +++ b/db/migrate/20150307215230_create_attribute_accesses.rb @@ -0,0 +1,11 @@ +class CreateAttributeAccesses < ActiveRecord::Migration + def change + create_table :attribute_accesses do |t| + t.integer :member_id + t.text :member_attribute + t.text :access + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d887c57d..e164a6bf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -18,10 +18,10 @@ create_table "attribute_accesses", force: :cascade do |t| t.integer "member_id" - t.text "attribute" + t.text "member_attribute" t.text "access" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "authentications", force: :cascade do |t| diff --git a/lib/accesses.rb b/lib/accesses.rb new file mode 100644 index 00000000..3a0cd4fe --- /dev/null +++ b/lib/accesses.rb @@ -0,0 +1,5 @@ +module Accesses + def self.attributes + YAML.load_file("#{Rails.root}/lib/yaml/accesses.yml").with_indifferent_access[:attributes] + end +end diff --git a/lib/yaml/accesses.yml b/lib/yaml/accesses.yml new file mode 100644 index 00000000..6af5c771 --- /dev/null +++ b/lib/yaml/accesses.yml @@ -0,0 +1,7 @@ +attributes: + - mobile_phone + - birth_date + - vkontakte + - twitter + - facebook + - google diff --git a/test/controllers/web/users/attribute_accesses_controller_test.rb b/test/controllers/web/users/attribute_accesses_controller_test.rb new file mode 100644 index 00000000..b20cfd39 --- /dev/null +++ b/test/controllers/web/users/attribute_accesses_controller_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' + +class Web::Users::AttributeAccessesControllerTest < ActionController::TestCase + setup do + member = create :member + sign_in member.user + @attribute_access = create :attribute_access + end + + test 'should post create' do + attributes = attributes_for :attribute_access + post :create, attribute_access: attributes + assert_response :success, @response.body + assert_equal attributes[:member_attribute], AttributeAccess.last.member_attribute + end + + test 'should not post create' do + count = AttributeAccess.count + attributes = attributes_for :attribute_access + attributes[:member_id] = nil + post :create, attribute_access: attributes + assert_response :bad_request, @response.body + assert_equal count, AttributeAccess.count + end + + test 'should post create with update' do + attributes = { member_id: @attribute_access.member_id, member_attribute: @attribute_access.member_attribute, access: 'visible' } + post :create, attribute_access: attributes + assert_response :success, @response.body + @attribute_access.reload + assert_equal attributes[:access], @attribute_access.access + end + + test 'should not post create with update' do + attributes = { member_id: @attribute_access.member_id, member_attribute: @attribute_access.member_attribute, access: nil } + post :create, attribute_access: attributes + assert_response :bad_request, @response.body + @attribute_access.reload + assert_not_equal attributes[:access], @attribute_access.access + end +end diff --git a/test/factories/attribute_accesses.rb b/test/factories/attribute_accesses.rb new file mode 100644 index 00000000..dd12c920 --- /dev/null +++ b/test/factories/attribute_accesses.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :attribute_access do + member_id { Member.last ? Member.last.id : 1 } + member_attribute { generate :string } + access 'hidden' + end +end diff --git a/test/models/attribute_access_test.rb b/test/models/attribute_access_test.rb new file mode 100644 index 00000000..0931a430 --- /dev/null +++ b/test/models/attribute_access_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class AttributeAccessTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From cda52b7c04ea44c37be98642da588574f119f5e0 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 06:04:42 +0300 Subject: [PATCH 169/227] fix test --- test/controllers/web/join_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/web/join_controller_test.rb b/test/controllers/web/join_controller_test.rb index 9dfb4742..310beffc 100644 --- a/test/controllers/web/join_controller_test.rb +++ b/test/controllers/web/join_controller_test.rb @@ -16,7 +16,7 @@ class Web::JoinControllerTest < ActionController::TestCase attributes = attributes_for :questionary post :create, questionary: attributes assert_response :redirect, @response.body - assert_redirected_to root_path + assert_redirected_to account_path assert_equal attributes[:patronymic], Questionary.last.patronymic end From fc6290e021ca2a0b46ebf9ea93b5078118c33bda Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 10:51:50 +0300 Subject: [PATCH 170/227] Add :state to News --- db/migrate/20150308075000_add_state_to_news.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150308075000_add_state_to_news.rb diff --git a/db/migrate/20150308075000_add_state_to_news.rb b/db/migrate/20150308075000_add_state_to_news.rb new file mode 100644 index 00000000..4fe444ad --- /dev/null +++ b/db/migrate/20150308075000_add_state_to_news.rb @@ -0,0 +1,5 @@ +class AddStateToNews < ActiveRecord::Migration + def change + add_column :news, :state, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 508f635d..a7e9e9ae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150307215230) do +ActiveRecord::Schema.define(version: 20150308075000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -57,6 +57,7 @@ t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "state" end create_table "questionaries", force: :cascade do |t| From 025aa5cc9fd93d9ad2cc56f136bf123c785fa847 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 10:52:20 +0300 Subject: [PATCH 171/227] Add decorator 'name' to NewsDecorator --- app/decorators/news_decorator.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/decorators/news_decorator.rb b/app/decorators/news_decorator.rb index 4603b56e..1092cd3b 100644 --- a/app/decorators/news_decorator.rb +++ b/app/decorators/news_decorator.rb @@ -22,4 +22,8 @@ def publish_date_time object.published_at.strftime('%d %b %Y %H:%m') end + def name + "#{model.title.first(30)}" + end + end From 9664836f3c317216b256a49050830b5149b301f4 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 10:52:58 +0300 Subject: [PATCH 172/227] Add state_machine to News --- app/models/news.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/models/news.rb b/app/models/news.rb index da56c5a7..6872003c 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -16,4 +16,25 @@ def is_published? scope :published, -> { where 'published_at <= ?', DateTime.now} scope :unpublished, -> { where 'published_at > ?', DateTime.now} + scope :removed, -> { where state: :removed } + + state_machine :state, initial: :unviewed do + state :unviewed + state :confirmed + state :declined + state :removed + + event :confirm do + transition all => :confirmed + end + event :decline do + transition all => :declined + end + event :remove do + transition all => :removed + end + event :restore do + transition :removed => :unviewed + end + end end From d823cf7cc100828ea10d84c14ed995d78cda1fdc Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 10:53:47 +0300 Subject: [PATCH 173/227] Add satate_mashine to News --- app/views/layouts/web/admin/_navbar.html.haml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/layouts/web/admin/_navbar.html.haml b/app/views/layouts/web/admin/_navbar.html.haml index 41171067..80f36101 100644 --- a/app/views/layouts/web/admin/_navbar.html.haml +++ b/app/views/layouts/web/admin/_navbar.html.haml @@ -14,6 +14,8 @@ = Member.model_name.human.pluralize(:ru) = menu_item admin_join_index_path do = Questionary.model_name.human.pluralize(:ru) + = menu_item admin_news_index_path do + = News.model_name.human.pluralize(:ru) %ul.nav.navbar-nav.navbar-right = menu_item admin_unviewed_index_path do %span.glyphicon.glyphicon-info-sign @@ -29,3 +31,5 @@ = Member.model_name.human.pluralize(:ru) = menu_item type_admin_trash_index_path('questionary') do = Questionary.model_name.human.pluralize(:ru) + = menu_item type_admin_trash_index_path('news') do + = News.model_name.human.pluralize(:ru) From cc87cc9fdca948fe00cb47a3c5b82256cd83c531 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 11:12:29 +0300 Subject: [PATCH 174/227] fix wrong delete in news_controller --- app/controllers/web/admin/news_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index e9f1b55c..7a8eb148 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -44,7 +44,7 @@ def update def destroy @news = News.find params[:id] - @news.destroy + @news.remove redirect_to admin_news_index_path end end From cebeca38cdb99c657f2461ea9e48d7784b8d48bd Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 11:24:37 +0300 Subject: [PATCH 175/227] Delete tabs --- app/models/news.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/news.rb b/app/models/news.rb index 6872003c..be166434 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -11,13 +11,12 @@ def is_published? end - extend Enumerize scope :published, -> { where 'published_at <= ?', DateTime.now} scope :unpublished, -> { where 'published_at > ?', DateTime.now} scope :removed, -> { where state: :removed } - + state_machine :state, initial: :unviewed do state :unviewed state :confirmed From db023519e073560086d275c70623b16ab11fbc5e Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 11:24:54 +0300 Subject: [PATCH 176/227] Add right test --- test/controllers/web/admin/news_controller_test.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb index 6eda9961..111d1836 100644 --- a/test/controllers/web/admin/news_controller_test.rb +++ b/test/controllers/web/admin/news_controller_test.rb @@ -3,8 +3,6 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase setup do @news = create :news - @admin = create :admin - sign_in @admin end test "should get index" do @@ -57,10 +55,9 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase end test "should destroy news" do - assert_difference('News.count', -1) do - delete :destroy, id: @news - end - - assert_redirected_to admin_news_index_path + count = News.count + delete :destroy, id: @news + @news.reload + assert @news.removed? end end From 6d08093505c1a8e91961108eb0db5de8b4ac1a95 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 14:38:26 +0300 Subject: [PATCH 177/227] ru is default locale for development --- config/application.rb | 1 + config/environments/development.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index eeedfc4b..dcbdc7ef 100644 --- a/config/application.rb +++ b/config/application.rb @@ -14,6 +14,7 @@ class Application < Rails::Application config.active_record.raise_in_transactional_callbacks = true config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] config.i18n.available_locales = [:en, :ru] + config.i18n.default_locale = :ru config.assets.initialize_on_precompile = true config.generators do |g| g.template_engine :haml diff --git a/config/environments/development.rb b/config/environments/development.rb index 5b7d2420..b55e2144 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -22,7 +22,6 @@ # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load - config.i18n.default_locale = :en # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. From 9c219400dcbeabe1c181d9e50eeb1502bf74493a Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 21:13:12 +0300 Subject: [PATCH 178/227] Fix query for scope --- app/models/news.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/news.rb b/app/models/news.rb index be166434..91eb26aa 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -12,8 +12,8 @@ def is_published? - scope :published, -> { where 'published_at <= ?', DateTime.now} - scope :unpublished, -> { where 'published_at > ?', DateTime.now} + scope :published, -> { where('published_at <= ?', DateTime.now).where.not(state: :removed)} + scope :unpublished, -> { where('published_at > ?', DateTime.now).where.not(state: :removed)} scope :removed, -> { where state: :removed } From efeb1004f7df1e415bb1d0991fd66707e8b8b99a Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Sun, 8 Mar 2015 21:18:56 +0300 Subject: [PATCH 179/227] fix test for news --- test/controllers/web/admin/news_controller_test.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb index 111d1836..9fe1ab6c 100644 --- a/test/controllers/web/admin/news_controller_test.rb +++ b/test/controllers/web/admin/news_controller_test.rb @@ -17,12 +17,10 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase test "should create news" do attributes = attributes_for :news - post :create, news: attributes - assert_response :redirect - - news = News.last - assert_equal attributes[:title], news.title + assert_response :redirect, @response.body + assert_redirected_to admin_news_index_path + assert_equal attributes[:title], News.last.title end test "should not create news" do From 90a097cae5f8198b87c1681b62eff357c717e5a5 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Mon, 9 Mar 2015 10:49:55 +0300 Subject: [PATCH 180/227] fix icons to datetimepicker-rails --- app/inputs/date_picker_input.rb | 2 +- app/inputs/datetime_picker_input.rb | 2 +- app/inputs/time_picker_input.rb | 2 +- vendor/assets/javascripts/pickers.js | 10 ---------- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/app/inputs/date_picker_input.rb b/app/inputs/date_picker_input.rb index 7a34ed9e..a6230310 100644 --- a/app/inputs/date_picker_input.rb +++ b/app/inputs/date_picker_input.rb @@ -19,7 +19,7 @@ def input(wrapper_options) input = super(wrapper_options) # leave StringInput do the real rendering input += template.content_tag :span, class: 'input-group-btn' do template.content_tag :button, class: 'btn btn-default', type: 'button' do - template.content_tag :span, '', class: 'fa fa-calendar' + template.content_tag :span, '', class: 'glyphicon glyphicon-calendar' end end input diff --git a/app/inputs/datetime_picker_input.rb b/app/inputs/datetime_picker_input.rb index 40430088..c3a7f34c 100644 --- a/app/inputs/datetime_picker_input.rb +++ b/app/inputs/datetime_picker_input.rb @@ -19,7 +19,7 @@ def input(wrapper_options) input = super(wrapper_options) # leave StringInput do the real rendering input += template.content_tag :span, class: 'input-group-btn' do template.content_tag :button, class: 'btn btn-default', type: 'button' do - template.content_tag :span, '', class: 'fa fa-calendar' + template.content_tag :span, '', class: 'glyphicon glyphicon-calendar' end end input diff --git a/app/inputs/time_picker_input.rb b/app/inputs/time_picker_input.rb index 57b32e90..f79661f9 100644 --- a/app/inputs/time_picker_input.rb +++ b/app/inputs/time_picker_input.rb @@ -17,7 +17,7 @@ def input(wrapper_options) input = super(wrapper_options) # leave StringInput do the real rendering input += template.content_tag :span, class: 'input-group-btn' do template.content_tag :button, class: 'btn btn-default', type: 'button' do - template.content_tag :span, '', class: 'fa fa-clock-o' + template.content_tag :span, '', class: 'glyphicon glyphicon-time' end end input diff --git a/vendor/assets/javascripts/pickers.js b/vendor/assets/javascripts/pickers.js index b1b57d1d..40617e77 100644 --- a/vendor/assets/javascripts/pickers.js +++ b/vendor/assets/javascripts/pickers.js @@ -1,14 +1,4 @@ var default_picker_options = { - icons: { - date: 'fa fa-calendar', - time: 'fa fa-clock-o', - up: 'fa fa-chevron-up', - down: 'fa fa-chevron-down', - previous: 'fa fa-chevron-left', - next: 'fa fa-chevron-right', - today: 'fa fa-crosshairs', - clear: 'fa fa-trash-o' - } } From 6b4c195cbff70ece43d0bb270c866046fc683fbe Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Sun, 8 Mar 2015 14:43:09 +0300 Subject: [PATCH 181/227] fix bug with datetimepicker --- app/assets/javascripts/web/admin/application.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index bb537a55..60b8e156 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -4,6 +4,7 @@ #= require moment #= require moment/ru #= require bootstrap-datetimepicker +#= require moment/ru #= require pickers $ -> From e757cf9d5970350f8ffe9aa5ccffe9ace1f1fbf2 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 9 Mar 2015 23:10:01 +0300 Subject: [PATCH 182/227] add positions --- app/models/member.rb | 1 + app/models/position.rb | 16 ++++++++++++ app/views/web/users/account/index.html.haml | 11 +++++++- config/locales/ru/ru.yml | 25 ++++++++++--------- db/migrate/20150309174901_create_positions.rb | 12 +++++++++ db/schema.rb | 11 +++++++- test/factories/positions.rb | 9 +++++++ test/models/position_test.rb | 7 ++++++ 8 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 app/models/position.rb create mode 100644 db/migrate/20150309174901_create_positions.rb create mode 100644 test/factories/positions.rb create mode 100644 test/models/position_test.rb diff --git a/app/models/member.rb b/app/models/member.rb index de67d646..6d34edf2 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -5,6 +5,7 @@ class Member < ActiveRecord::Base belongs_to :parent, class_name: 'Member' has_one :questionary has_many :attribute_accesses + has_many :positions validates :patronymic, presence: true, human_name: true diff --git a/app/models/position.rb b/app/models/position.rb new file mode 100644 index 00000000..049015db --- /dev/null +++ b/app/models/position.rb @@ -0,0 +1,16 @@ +class Position < ActiveRecord::Base + belongs_to :member + + validates :title, presence: true + validates :member_id, presence: true + validates :begin_date, presence: true + validates :end_date, presence: true + validate :begin_before_end_date + + def begin_before_end_date + if begin_date.present? && end_date.present? + return if begin_date <= end_date + end + errors.add(:end_date, I18n.t('validations.errors.end_date_must_be_after_begin_date')) + end +end diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index f6d4a652..a8c0217a 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -14,8 +14,17 @@ = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' + %br - attributes_need_access.each do |attribute| = check_box_tag attribute, '', false, class: 'attribute_access', data: { id: @user.member.id } + %br + = t('.my_position_in_mic') + - @user.member.positions.each do |position| + = simple_form_for position, url: { controller: 'web/users/positions', action: :update } do |f| + = f.input :title, as: :string + = f.input :begin_date, as: :date_picker + = f.input :end_date, as: :date_picker + = f.button :submit - else = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string @@ -28,7 +37,7 @@ = link_to t('.enter_datas_mic_member'), new_member_path = t('.you_are_not_member_mic') = link_to t('organization.send_request'), new_join_path - +%br - @authentications.each do |auth| = social_network_localize auth.provider = link_to t('helpers.links.destroy'), users_authentication_path(auth), method: :delete, class: 'btn btn-danger btn-xs' diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 91d116e8..d477e755 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -1,16 +1,4 @@ ru: - time: - formats: - default: "%Y/%m/%d" - short: "%b %d" - long: "%B %d, %Y" - datepicker: - dformat: '%d/%m/%Y' # display format of the date (this is the default, can be ommited) - pformat: 'DD/MM/YYYY' # picking format of the date (this is the default, can be ommited) - timepicker: - dformat: '%R' # display format of the time (this is the default, can be ommited) - pformat: 'HH:mm' # picking format of the time (this is the default, can be ommited) - dayViewHeaderFormat: 'MMMM YYYY' application: name: Сайт МИЦ organization: @@ -57,6 +45,7 @@ ru: you_are_member_mic: Вы являетесь членом МИЦ you_are_not_member_mic: Вы не являетесь членом МИЦ enter_datas_mic_member: Заполните профиль члена МИЦ + my_position_in_mic: История моих должностей в МИЦ new: register: Регистрация admin: @@ -64,3 +53,15 @@ ru: name: Имя title: Корзина deleted: Удалённые + time: + formats: + default: "%Y/%m/%d" + short: "%b %d" + long: "%B %d, %Y" + datepicker: + dformat: '%d/%m/%Y' # display format of the date (this is the default, can be ommited) + pformat: 'DD/MM/YYYY' # picking format of the date (this is the default, can be ommited) + timepicker: + dformat: '%R' # display format of the time (this is the default, can be ommited) + pformat: 'HH:mm' # picking format of the time (this is the default, can be ommited) + dayViewHeaderFormat: 'MMMM YYYY' diff --git a/db/migrate/20150309174901_create_positions.rb b/db/migrate/20150309174901_create_positions.rb new file mode 100644 index 00000000..cf2476a7 --- /dev/null +++ b/db/migrate/20150309174901_create_positions.rb @@ -0,0 +1,12 @@ +class CreatePositions < ActiveRecord::Migration + def change + create_table :positions do |t| + t.text :title + t.integer :member_id + t.datetime :begin_date + t.datetime :end_date + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a7e9e9ae..8cb4d90a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150308075000) do +ActiveRecord::Schema.define(version: 20150309174901) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -60,6 +60,15 @@ t.text "state" end + create_table "positions", force: :cascade do |t| + t.text "title" + t.integer "member_id" + t.datetime "begin_date" + t.datetime "end_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "questionaries", force: :cascade do |t| t.text "experience" t.text "want_to_do" diff --git a/test/factories/positions.rb b/test/factories/positions.rb new file mode 100644 index 00000000..c567ead3 --- /dev/null +++ b/test/factories/positions.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :position do + title "MyText" +member_id 1 +begin_date "2015-03-09 20:49:01" +end_date "2015-03-09 20:49:01" + end + +end diff --git a/test/models/position_test.rb b/test/models/position_test.rb new file mode 100644 index 00000000..461cf1b2 --- /dev/null +++ b/test/models/position_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class PositionTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 7fb137ed44f1d64cbe49ef308d763aeab240690b Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Mon, 9 Mar 2015 23:24:57 +0300 Subject: [PATCH 183/227] add positions controller --- .../web/users/positions_controller.rb | 29 +++++++++++++++++ app/forms/position_form.rb | 5 +++ config/routes.rb | 1 + .../web/users/positions_controller_test.rb | 32 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 app/controllers/web/users/positions_controller.rb create mode 100644 app/forms/position_form.rb create mode 100644 test/controllers/web/users/positions_controller_test.rb diff --git a/app/controllers/web/users/positions_controller.rb b/app/controllers/web/users/positions_controller.rb new file mode 100644 index 00000000..40a48a5f --- /dev/null +++ b/app/controllers/web/users/positions_controller.rb @@ -0,0 +1,29 @@ +class Web::Users::PositionsController < Web::Users::ApplicationController + def create + @position = Position.new + @position_form = PositionForm.new @position + @position_form.submit params[:position] + if @position_form.save + redirect_to account_path + else + render action: :new + end + end + + def update + @position = Position.find params[:id] + @position_form = PositionForm.new @position + @position_form.submit params[:position] + if @position_form.save + redirect_to account_path + else + render action: :edit + end + end + + def destroy + @position = Position.find params[:id] + @position.destroy + redirect_to account_path + end +end diff --git a/app/forms/position_form.rb b/app/forms/position_form.rb new file mode 100644 index 00000000..0041bd63 --- /dev/null +++ b/app/forms/position_form.rb @@ -0,0 +1,5 @@ +class PositionForm < ActiveForm::Base + self.main_model = :position + + attributes :title, :member_id, :begin_date, :end_date +end diff --git a/config/routes.rb b/config/routes.rb index 8a9873d2..9d0ce877 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ resources :account, only: :update resources :authentications, only: :destroy resources :attribute_accesses, only: :create + resources :positions, only: [ :create, :update, :destroy ] end namespace :admin do resources :users diff --git a/test/controllers/web/users/positions_controller_test.rb b/test/controllers/web/users/positions_controller_test.rb new file mode 100644 index 00000000..c85c6ab7 --- /dev/null +++ b/test/controllers/web/users/positions_controller_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' + +class Web::Users::PositionsControllerTest < ActionController::TestCase + setup do + @position = create :position + member = create :member + sign_in member.user + end + + test 'should create position' do + attributes = attributes_for :position + post :create, position: attributes + assert_response :redirect, @response.body + assert_redirected_to account_path + assert_equal attributes[:title], Position.last.title + end + + test 'should patch update' do + attributes = attributes_for :position + patch :update, position: attributes, id: @position + assert_response :redirect, @response.body + assert_redirected_to account_path + @position.reload + assert_equal attributes[:title], @position.title + end + + test 'should delete destroy' do + count = Position.count + delete :destroy, id: @position + assert_equal count - 1, Position.count + end +end From b327817c64d5a9997b4a0da1acd47330c70a8d80 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Mon, 9 Mar 2015 23:59:56 +0300 Subject: [PATCH 184/227] Add Wysiwyg editor and Change icons Icons: Glyph => Font Awesome --- Gemfile | 3 +++ Gemfile.lock | 7 +++++++ app/assets/javascripts/application.js | 2 ++ app/assets/javascripts/web/admin/application.coffee | 7 +++++++ app/assets/stylesheets/application.css.sass | 3 +++ app/assets/stylesheets/web/admin/application.sass | 8 ++++++++ app/inputs/date_picker_input.rb | 2 +- app/inputs/datetime_picker_input.rb | 2 +- app/inputs/time_picker_input.rb | 2 +- app/views/web/admin/news/_form.html.haml | 2 +- vendor/assets/javascripts/pickers.js | 10 ++++++++++ 11 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index d58f3bf0..d7976ef2 100644 --- a/Gemfile +++ b/Gemfile @@ -43,6 +43,9 @@ gem 'omniauth-facebook' gem 'momentjs-rails', '>= 2.9', :github => 'derekprior/momentjs-rails' gem 'awesome_print' gem 'turbolinks' +gem 'wysiwyg-rails' +gem 'font-awesome-rails' + # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/Gemfile.lock b/Gemfile.lock index b1003c1b..ef0f483a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,6 +142,8 @@ GEM railties (>= 3.0.0) faraday (0.9.1) multipart-post (>= 1.2, < 3) + font-awesome-rails (4.3.0.0) + railties (>= 3.2, < 5.0) globalid (0.3.3) activesupport (>= 4.1.0) haml (4.0.6) @@ -320,6 +322,9 @@ GEM binding_of_caller railties (~> 4.0) sprockets-rails (>= 2.0, < 4.0) + wysiwyg-rails (1.2.6) + font-awesome-rails (>= 4.2.0.0) + railties (>= 3.2, < 5.0) PLATFORMS ruby @@ -338,6 +343,7 @@ DEPENDENCIES draper enumerize factory_girl_rails + font-awesome-rails haml-rails i18n-js! jbuilder (~> 2.0) @@ -366,3 +372,4 @@ DEPENDENCIES uglifier (>= 1.3.0) unicorn-rails web-console (= 2.0.0.beta2) + wysiwyg-rails diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 379fd515..fa8ff428 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,4 +18,6 @@ //= require pickers //= require js-routes //= require i18n_setup +//= require froala_editor.min.js +//= require langs/ru.js //= require_tree . diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index bb537a55..80e6edf7 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -5,7 +5,14 @@ #= require moment/ru #= require bootstrap-datetimepicker #= require pickers +#= require froala_editor.min.js $ -> $('.link').click -> location.href = $(this).attr('data-href') +$ -> + $('#news_body').editable + inlineMode: false + +$ -> + $('#news_published_at').datetimepicker() diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass index 1aa6345f..fdda07e7 100644 --- a/app/assets/stylesheets/application.css.sass +++ b/app/assets/stylesheets/application.css.sass @@ -2,5 +2,8 @@ *= require_self *= require bootstrap-datetimepicker + *= require froala_editor.min.css + *= require froala_style.min.css + *= require font-awesome */ diff --git a/app/assets/stylesheets/web/admin/application.sass b/app/assets/stylesheets/web/admin/application.sass index 59e1f182..57057ae6 100644 --- a/app/assets/stylesheets/web/admin/application.sass +++ b/app/assets/stylesheets/web/admin/application.sass @@ -1,6 +1,14 @@ +/* + + *= require font-awesome + *= require froala_editor.min.css + *= require froala_style.min.css + */ + @import 'bootstrap-sprockets' @import 'bootstrap' @import 'bootstrap-datetimepicker' +@import 'font-awesome' .link cursor: pointer diff --git a/app/inputs/date_picker_input.rb b/app/inputs/date_picker_input.rb index a6230310..7a34ed9e 100644 --- a/app/inputs/date_picker_input.rb +++ b/app/inputs/date_picker_input.rb @@ -19,7 +19,7 @@ def input(wrapper_options) input = super(wrapper_options) # leave StringInput do the real rendering input += template.content_tag :span, class: 'input-group-btn' do template.content_tag :button, class: 'btn btn-default', type: 'button' do - template.content_tag :span, '', class: 'glyphicon glyphicon-calendar' + template.content_tag :span, '', class: 'fa fa-calendar' end end input diff --git a/app/inputs/datetime_picker_input.rb b/app/inputs/datetime_picker_input.rb index c3a7f34c..40430088 100644 --- a/app/inputs/datetime_picker_input.rb +++ b/app/inputs/datetime_picker_input.rb @@ -19,7 +19,7 @@ def input(wrapper_options) input = super(wrapper_options) # leave StringInput do the real rendering input += template.content_tag :span, class: 'input-group-btn' do template.content_tag :button, class: 'btn btn-default', type: 'button' do - template.content_tag :span, '', class: 'glyphicon glyphicon-calendar' + template.content_tag :span, '', class: 'fa fa-calendar' end end input diff --git a/app/inputs/time_picker_input.rb b/app/inputs/time_picker_input.rb index f79661f9..57b32e90 100644 --- a/app/inputs/time_picker_input.rb +++ b/app/inputs/time_picker_input.rb @@ -17,7 +17,7 @@ def input(wrapper_options) input = super(wrapper_options) # leave StringInput do the real rendering input += template.content_tag :span, class: 'input-group-btn' do template.content_tag :button, class: 'btn btn-default', type: 'button' do - template.content_tag :span, '', class: 'glyphicon glyphicon-time' + template.content_tag :span, '', class: 'fa fa-clock-o' end end input diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index d32a5de4..bcb7d0b5 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -4,7 +4,7 @@ .col-lg-6 = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :title, as: :string - = f.input :body, as: :string + = f.input :body = f.input :published_at, as: :datetime_picker = f.input :user_id, as: :string /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } diff --git a/vendor/assets/javascripts/pickers.js b/vendor/assets/javascripts/pickers.js index 40617e77..b1b57d1d 100644 --- a/vendor/assets/javascripts/pickers.js +++ b/vendor/assets/javascripts/pickers.js @@ -1,4 +1,14 @@ var default_picker_options = { + icons: { + date: 'fa fa-calendar', + time: 'fa fa-clock-o', + up: 'fa fa-chevron-up', + down: 'fa fa-chevron-down', + previous: 'fa fa-chevron-left', + next: 'fa fa-chevron-right', + today: 'fa fa-crosshairs', + clear: 'fa fa-trash-o' + } } From 0eb148be65ab479e68c9cd2104bffe02c47b3b9d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 00:12:52 +0300 Subject: [PATCH 185/227] fix requires --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/web/admin/application.coffee | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index fa8ff428..1a2c5bb1 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,6 +14,7 @@ //= require jquery_ujs //= require turbolinks //= require moment +//= require moment/ru //= require bootstrap-datetimepicker //= require pickers //= require js-routes diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index 80e6edf7..f6d8c0a9 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -1,11 +1,14 @@ #= require jquery #= require jquery_ujs -#= require bootstrap-sprockets +#= require turbolinks #= require moment #= require moment/ru #= require bootstrap-datetimepicker #= require pickers +#= require js-routes +#= require i18n_setup #= require froala_editor.min.js +#= require langs/ru.js $ -> $('.link').click -> From 67f62dc11496ac7b297e592d716d708fdfe81b2f Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 00:13:28 +0300 Subject: [PATCH 186/227] fix translation --- app/views/web/admin/news/_news_list.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/web/admin/news/_news_list.html.haml b/app/views/web/admin/news/_news_list.html.haml index a8105ee8..f8971e72 100644 --- a/app/views/web/admin/news/_news_list.html.haml +++ b/app/views/web/admin/news/_news_list.html.haml @@ -3,11 +3,11 @@ %thead %tr %th= model_class.human_attribute_name(:id) - %th= model_class.human_attribute_name(:author_id) + %th= model_class.human_attribute_name(:user_id) %th= model_class.human_attribute_name(:title) %th= model_class.human_attribute_name(:body) - %th= model_class.human_attribute_name(:published) - %th= model_class.human_attribute_name(:created) + %th= model_class.human_attribute_name(:published_at) + %th= model_class.human_attribute_name(:created_at) %th=t '.actions', default: t('helpers.actions') %tbody - news.each do |news| From e0525ae8e96280f25105ddc925df151a417bea5d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 00:29:02 +0300 Subject: [PATCH 187/227] edit where to find by --- app/controllers/web/users/attribute_accesses_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/web/users/attribute_accesses_controller.rb b/app/controllers/web/users/attribute_accesses_controller.rb index 3bc2ab1f..16c84a14 100644 --- a/app/controllers/web/users/attribute_accesses_controller.rb +++ b/app/controllers/web/users/attribute_accesses_controller.rb @@ -1,6 +1,6 @@ class Web::Users::AttributeAccessesController < Web::Users::ApplicationController def create - @access = AttributeAccess.where(member_attribute: params[:attribute_access][:member_attribute], member_id: params[:attribute_access][:member_id]).first + @access = AttributeAccess.find member_attribute: params[:attribute_access][:member_attribute], member_id: params[:attribute_access][:member_id] unless @access @access = AttributeAccess.new end From 6ff5d66c798798f46f2eae8b5e5aca6cb1a5d50c Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 00:45:55 +0300 Subject: [PATCH 188/227] add action_form add_link_association --- app/assets/javascripts/application.js | 1 + app/controllers/web/users/account_controller.rb | 3 ++- .../web/users/attribute_accesses_controller.rb | 1 + app/models/member.rb | 2 ++ app/views/web/users/account/index.html.haml | 16 ++++++++-------- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 379fd515..6e122111 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,5 +17,6 @@ //= require bootstrap-datetimepicker //= require pickers //= require js-routes +//= require action_form //= require i18n_setup //= require_tree . diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index a3693e15..9b39e873 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -1,6 +1,7 @@ class Web::Users::AccountController < Web::Users::ApplicationController def index - @user = User.find(current_user.id).decorate + @user = current_user.decorate + @user.member.positions.build if @user.member.present? @authentications = current_user.authentications end diff --git a/app/controllers/web/users/attribute_accesses_controller.rb b/app/controllers/web/users/attribute_accesses_controller.rb index 16c84a14..94b44c82 100644 --- a/app/controllers/web/users/attribute_accesses_controller.rb +++ b/app/controllers/web/users/attribute_accesses_controller.rb @@ -4,6 +4,7 @@ def create unless @access @access = AttributeAccess.new end + access = AttributeAccess.find_or_initialize_by(params[:attribute_access]) @access_form = AttributeAccessForm.new @access @access_form.submit params[:attribute_access] if @access_form.save diff --git a/app/models/member.rb b/app/models/member.rb index 6d34edf2..9af39024 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -7,6 +7,8 @@ class Member < ActiveRecord::Base has_many :attribute_accesses has_many :positions + accepts_nested_attributes_for :positions + validates :patronymic, presence: true, human_name: true validates :motto, presence: true diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index a8c0217a..589ad254 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -12,19 +12,19 @@ = f.input :locality, as: :string = f.input :avatar, as: :string = f.input :user_id, as: :hidden, input_html: { value: current_user.id } + = f.hint t('.my_position_in_mic') + = f.simple_fields_for :positions do |ff| + = ff.input :title, as: :string + = ff.input :begin_date, as: :date_picker + = ff.input :end_date, as: :date_picker + = ff.input :member_id, as: :hidden + = link_to_remove_association t('.remove_position'), ff + = link_to_add_association t('.add_position'), f, :positions = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' %br - attributes_need_access.each do |attribute| = check_box_tag attribute, '', false, class: 'attribute_access', data: { id: @user.member.id } - %br - = t('.my_position_in_mic') - - @user.member.positions.each do |position| - = simple_form_for position, url: { controller: 'web/users/positions', action: :update } do |f| - = f.input :title, as: :string - = f.input :begin_date, as: :date_picker - = f.input :end_date, as: :date_picker - = f.button :submit - else = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string From 4bd658461b733adec3e0471d1d39a3856e590fdd Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 01:02:10 +0300 Subject: [PATCH 189/227] edit migrates --- db/migrate/20150220220847_create_users.rb | 17 +++++++++++++++++ db/migrate/20150220222045_add_role_to_user.rb | 5 ----- .../20150227211019_add_names_to_user.rb | 7 ------- ...50301164922_remove_patronymic_from_user.rb | 5 ----- .../20150301191940_add_state_to_users.rb | 5 ----- db/migrate/20150301204107_create_members.rb | 19 ------------------- .../20150301213120_add_state_to_member.rb | 5 ----- .../20150306010850_create_questionaries.rb | 12 ------------ 8 files changed, 17 insertions(+), 58 deletions(-) delete mode 100644 db/migrate/20150220222045_add_role_to_user.rb delete mode 100644 db/migrate/20150227211019_add_names_to_user.rb delete mode 100644 db/migrate/20150301164922_remove_patronymic_from_user.rb delete mode 100644 db/migrate/20150301191940_add_state_to_users.rb delete mode 100644 db/migrate/20150301204107_create_members.rb delete mode 100644 db/migrate/20150301213120_add_state_to_member.rb delete mode 100644 db/migrate/20150306010850_create_questionaries.rb diff --git a/db/migrate/20150220220847_create_users.rb b/db/migrate/20150220220847_create_users.rb index 3d665df7..f400bee4 100644 --- a/db/migrate/20150220220847_create_users.rb +++ b/db/migrate/20150220220847_create_users.rb @@ -3,6 +3,23 @@ def change create_table :users do |t| t.text :email t.text :password_digest + t.text :first_name + t.text :last_name + t.text :patronymic + t.integer :user_id + t.text :motto + t.integer :ticket + t.integer :parent_id + t.text :mobile_phone + t.datetime :birth_date + t.text :home_adress + t.text :municipality + t.text :locality + t.text :avatar + t.text :role + t.text :state + t.text :experience + t.text :want_to_do t.timestamps null: false end diff --git a/db/migrate/20150220222045_add_role_to_user.rb b/db/migrate/20150220222045_add_role_to_user.rb deleted file mode 100644 index 268a1a09..00000000 --- a/db/migrate/20150220222045_add_role_to_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddRoleToUser < ActiveRecord::Migration - def change - add_column :users, :role, :text - end -end diff --git a/db/migrate/20150227211019_add_names_to_user.rb b/db/migrate/20150227211019_add_names_to_user.rb deleted file mode 100644 index 5236cc27..00000000 --- a/db/migrate/20150227211019_add_names_to_user.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AddNamesToUser < ActiveRecord::Migration - def change - add_column :users, :first_name, :text - add_column :users, :patronymic, :text - add_column :users, :last_name, :text - end -end diff --git a/db/migrate/20150301164922_remove_patronymic_from_user.rb b/db/migrate/20150301164922_remove_patronymic_from_user.rb deleted file mode 100644 index 027dfe3e..00000000 --- a/db/migrate/20150301164922_remove_patronymic_from_user.rb +++ /dev/null @@ -1,5 +0,0 @@ -class RemovePatronymicFromUser < ActiveRecord::Migration - def change - remove_column :users, :patronymic - end -end diff --git a/db/migrate/20150301191940_add_state_to_users.rb b/db/migrate/20150301191940_add_state_to_users.rb deleted file mode 100644 index 17f92ac3..00000000 --- a/db/migrate/20150301191940_add_state_to_users.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddStateToUsers < ActiveRecord::Migration - def change - add_column :users, :state, :text - end -end diff --git a/db/migrate/20150301204107_create_members.rb b/db/migrate/20150301204107_create_members.rb deleted file mode 100644 index ecc4ca29..00000000 --- a/db/migrate/20150301204107_create_members.rb +++ /dev/null @@ -1,19 +0,0 @@ -class CreateMembers < ActiveRecord::Migration - def change - create_table :members do |t| - t.text :patronymic - t.integer :user_id - t.text :motto - t.integer :ticket - t.integer :parent_id - t.text :mobile_phone - t.datetime :birth_date - t.text :home_adress - t.text :municipality - t.text :locality - t.text :avatar - - t.timestamps null: false - end - end -end diff --git a/db/migrate/20150301213120_add_state_to_member.rb b/db/migrate/20150301213120_add_state_to_member.rb deleted file mode 100644 index 00483ca9..00000000 --- a/db/migrate/20150301213120_add_state_to_member.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddStateToMember < ActiveRecord::Migration - def change - add_column :members, :state, :text - end -end diff --git a/db/migrate/20150306010850_create_questionaries.rb b/db/migrate/20150306010850_create_questionaries.rb deleted file mode 100644 index 7ff3b032..00000000 --- a/db/migrate/20150306010850_create_questionaries.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateQuestionaries < ActiveRecord::Migration - def change - create_table :questionaries do |t| - t.text :experience - t.text :want_to_do - t.text :state - t.integer :member_id - - t.timestamps null: false - end - end -end From 15c1ad3c76d9aad8ce804b7ad8b84548642b6173 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 01:03:13 +0300 Subject: [PATCH 190/227] fix news localization --- app/views/web/admin/news/index.html.haml | 4 ++-- config/locales/ru/models.yml | 6 +++--- config/locales/ru/ru.yml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/web/admin/news/index.html.haml b/app/views/web/admin/news/index.html.haml index 2b8e588d..6c5e4514 100644 --- a/app/views/web/admin/news/index.html.haml +++ b/app/views/web/admin/news/index.html.haml @@ -5,9 +5,9 @@ %h1=t '.title' %ul.nav.nav-tabs{ role: :tablist } %li - = link_to t('.published'), '#published' + = link_to t('helpers.links.published'), '#published' %li - = link_to t('.unpublished'), '#unpublished' + = link_to t('helpers.links.unpublished'), '#unpublished' #published = link_to t('.new', default: t('helpers.links.new')), new_admin_news_path, class: 'btn btn-primary' = render 'news_list', news: @published_news diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 1c4e29f9..2fe9a99a 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -4,7 +4,7 @@ ru: user: Пользователь member: Член организации questionary: Анкета на вступление - news: Новости + news: Новость attributes: user: first_name: Имя @@ -49,7 +49,7 @@ ru: title: Заголовок body: Контекст published_at: Дата публикации - created_at: Дата создания - user_id: Прикреплённый пользователь + created_at: Создано + user_id: Автор photo: Фотография diff --git a/config/locales/ru/ru.yml b/config/locales/ru/ru.yml index 91d116e8..b6c25e5f 100644 --- a/config/locales/ru/ru.yml +++ b/config/locales/ru/ru.yml @@ -1,9 +1,7 @@ ru: time: formats: - default: "%Y/%m/%d" - short: "%b %d" - long: "%B %d, %Y" + default: "%a, %d %b %Y, %H:%M" datepicker: dformat: '%d/%m/%Y' # display format of the date (this is the default, can be ommited) pformat: 'DD/MM/YYYY' # picking format of the date (this is the default, can be ommited) @@ -32,6 +30,8 @@ ru: back: Назад approve: Подтвердить decline: Отклонить + published: Опубликованные новости + unpublished: Неопубликованные новости social_networks: vkontakte: ВКонтакте facebook: Facebook From d8d4e7afaec93cb411ee3b624798688aeef5e20a Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 01:18:17 +0300 Subject: [PATCH 191/227] remove associations and fix tests --- app/decorators/member_decorator.rb | 9 ---- app/decorators/questionary_decorator.rb | 14 +----- app/models/member.rb | 20 +-------- app/models/questionary.rb | 31 +------------ app/views/web/admin/join/_form.html.haml | 14 ++---- app/views/web/admin/members/_form.html.haml | 15 ++----- db/schema.rb | 45 +++++++------------ test/controllers/web/join_controller_test.rb | 4 +- .../web/members_controller_test.rb | 2 +- .../web/users/account_controller_test.rb | 8 ++-- .../attribute_accesses_controller_test.rb | 2 +- test/factories/members.rb | 2 - test/factories/questionaries.rb | 11 ++++- 13 files changed, 43 insertions(+), 134 deletions(-) diff --git a/app/decorators/member_decorator.rb b/app/decorators/member_decorator.rb index c1d53cce..affa89f7 100644 --- a/app/decorators/member_decorator.rb +++ b/app/decorators/member_decorator.rb @@ -1,14 +1,5 @@ class MemberDecorator < Draper::Decorator delegate_all - - def first_name - user.first_name - end - - def last_name - user.last_name - end - def full_name "#{first_name} #{patronymic} #{last_name}" end diff --git a/app/decorators/questionary_decorator.rb b/app/decorators/questionary_decorator.rb index 35cbc430..f1ac3ac3 100644 --- a/app/decorators/questionary_decorator.rb +++ b/app/decorators/questionary_decorator.rb @@ -1,24 +1,12 @@ class QuestionaryDecorator < Draper::Decorator delegate_all - def first_name - member.user.first_name - end - - def last_name - member.user.last_name - end - - def patronymic - member.patronymic - end - def full_name "#{first_name} #{patronymic} #{last_name}" end def place - "#{member.municipality}, #{member.locality}" + "#{municipality}, #{locality}" end def name diff --git a/app/models/member.rb b/app/models/member.rb index de67d646..7a87dc77 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,9 +1,5 @@ -class Member < ActiveRecord::Base - after_save :update_user_name - - belongs_to :user +class Member < User belongs_to :parent, class_name: 'Member' - has_one :questionary has_many :attribute_accesses validates :patronymic, presence: true, @@ -42,18 +38,4 @@ class Member < ActiveRecord::Base transition removed: :unviewed end end - attr_accessor :first_name, :last_name, :email - - private - - def update_user_name - if user_id - User.update user_id, first_name: first_name if first_name.present? - User.update user_id, last_name: last_name if last_name.present? - User.update user_id, email: email if email.present? - else - user = User.create(first_name: first_name, last_name: last_name, email: email) - update user_id: User.last.id - end - end end diff --git a/app/models/questionary.rb b/app/models/questionary.rb index 686ec2a6..8aaa4749 100644 --- a/app/models/questionary.rb +++ b/app/models/questionary.rb @@ -1,7 +1,4 @@ -class Questionary < ActiveRecord::Base - after_save :update_member - belongs_to :member - +class Questionary < Member validates :experience, presence: true validates :want_to_do, presence: true @@ -32,30 +29,4 @@ class Questionary < ActiveRecord::Base transition removed: :unviewed end end - - def user - member.user if member - end - - attr_accessor :first_name, :last_name, :email, :patronymic, :motto, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :user_id, :ticket - - private - - def update_member - params = { patronymic: patronymic, motto: motto, mobile_phone: mobile_phone, birth_date: birth_date, home_adress: home_adress, municipality: municipality, locality: locality, avatar: avatar, state: :not_member, user_id: user_id, ticket: ticket } - params.keys.each do |key| - params[key] = nil unless params[key].present? - end - if member_id - member = Member.find member_id - member.first_name = first_name if first_name.present? - member.last_name = last_name if last_name.present? - member.email = email if email.present? - member.save - Member.update member_id, params - else - Member.create params - update member_id: Member.last.id - end - end end diff --git a/app/views/web/admin/join/_form.html.haml b/app/views/web/admin/join/_form.html.haml index d187e47f..6186dea8 100644 --- a/app/views/web/admin/join/_form.html.haml +++ b/app/views/web/admin/join/_form.html.haml @@ -3,17 +3,10 @@ .row .col-lg-6 = simple_form_for @questionary, url: { controller: 'web/admin/join', action: action }, input_html: { class: 'form-horizontal' } do |f| - - if @questionary.user.present? - = f.input :first_name, as: :string, required: true, input_html: { value: @questionary.user.first_name } - - else - = f.input :first_name, as: :string, required: true + = f.input :first_name, as: :string, required: true = f.input :patronymic, as: :string - - if @questionary.user.present? - = f.input :last_name, as: :string, required: true, input_html: { value: @questionary.user.last_name } - = f.input :email, as: :string, required: true, input_html: { value: @questionary.user.email } - - else - = f.input :last_name, as: :string, required: true - = f.input :email, as: :string, required: true + = f.input :last_name, as: :string, required: true + = f.input :email, as: :string, required: true = f.input :motto, as: :string = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string @@ -21,6 +14,5 @@ = f.input :municipality, as: :string = f.input :locality, as: :string = f.input :avatar, as: :string - = f.association :member, label_method: lambda { |member| "#{member.id} | #{member.user.first_name} #{member.user.last_name}" }, value_method: :id, required: true = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_join_index_path, class: 'btn btn-default' diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index 979f6f9a..10157ff3 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -3,17 +3,9 @@ .row .col-lg-6 = simple_form_for @member, url: { controller: 'web/admin/members', action: action }, input_html: { class: 'form-horizontal' } do |f| - - if @member.user.present? - = f.input :first_name, as: :string, required: true, input_html: { value: @member.user.first_name } - - else - = f.input :first_name, as: :string, required: true - = f.input :patronymic, as: :string - - if @member.user.present? - = f.input :last_name, as: :string, required: true, input_html: { value: @member.user.last_name } - = f.input :email, as: :string, required: true, input_html: { value: @member.user.email } - - else - = f.input :last_name, as: :string, required: true - = f.input :email, as: :string, required: true + = f.input :first_name, as: :string, required: true + = f.input :last_name, as: :string, required: true + = f.input :email, as: :string, required: true = f.input :motto, as: :string = f.input :ticket, as: :string, required: true = f.input :mobile_phone, as: :string @@ -22,6 +14,5 @@ = f.input :municipality, as: :string = f.input :locality, as: :string = f.input :avatar, as: :string - = f.association :user, label_method: lambda { |user| "#{user.id} | #{user.first_name} #{user.last_name}" }, value_method: :id, required: true = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/db/schema.rb b/db/schema.rb index a7e9e9ae..9f329f69 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -32,23 +32,6 @@ t.datetime "updated_at", null: false end - create_table "members", force: :cascade do |t| - t.text "patronymic" - t.integer "user_id" - t.text "motto" - t.integer "ticket" - t.integer "parent_id" - t.text "mobile_phone" - t.datetime "birth_date" - t.text "home_adress" - t.text "municipality" - t.text "locality" - t.text "avatar" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "state" - end - create_table "news", force: :cascade do |t| t.string "title" t.text "body" @@ -60,24 +43,28 @@ t.text "state" end - create_table "questionaries", force: :cascade do |t| - t.text "experience" - t.text "want_to_do" - t.text "state" - t.integer "member_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "users", force: :cascade do |t| t.text "email" t.text "password_digest" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "role" t.text "first_name" t.text "last_name" + t.text "patronymic" + t.integer "user_id" + t.text "motto" + t.integer "ticket" + t.integer "parent_id" + t.text "mobile_phone" + t.datetime "birth_date" + t.text "home_adress" + t.text "municipality" + t.text "locality" + t.text "avatar" + t.text "role" t.text "state" + t.text "experience" + t.text "want_to_do" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end end diff --git a/test/controllers/web/join_controller_test.rb b/test/controllers/web/join_controller_test.rb index 310beffc..63789f74 100644 --- a/test/controllers/web/join_controller_test.rb +++ b/test/controllers/web/join_controller_test.rb @@ -2,9 +2,9 @@ class Web::JoinControllerTest < ActionController::TestCase setup do - create :member + user = create :user + sign_in user @questionary = create :questionary - sign_in @questionary.user end test 'should get new' do diff --git a/test/controllers/web/members_controller_test.rb b/test/controllers/web/members_controller_test.rb index 6a0ef019..3576f750 100644 --- a/test/controllers/web/members_controller_test.rb +++ b/test/controllers/web/members_controller_test.rb @@ -3,7 +3,7 @@ class Web::MembersControllerTest < ActionController::TestCase setup do @member = create :member - sign_in @member.user + sign_in @member end test 'should not get new unsigned' do diff --git a/test/controllers/web/users/account_controller_test.rb b/test/controllers/web/users/account_controller_test.rb index 0595f763..cd2786b4 100644 --- a/test/controllers/web/users/account_controller_test.rb +++ b/test/controllers/web/users/account_controller_test.rb @@ -3,7 +3,7 @@ class Web::Users::AccountControllerTest < ActionController::TestCase setup do @member = create :member - sign_in @member.user + sign_in @member end test 'should get index' do @@ -13,11 +13,11 @@ class Web::Users::AccountControllerTest < ActionController::TestCase test 'should patch update with user' do attributes = attributes_for :user - patch :update, user: attributes, id: @member.user + patch :update, user: attributes, id: @member assert_response :redirect, @response.body assert_redirected_to account_path - @member.user.reload - assert_equal attributes[:first_name], @member.user.first_name + @member.reload + assert_equal attributes[:first_name], @member.first_name end test 'should patch update with member' do diff --git a/test/controllers/web/users/attribute_accesses_controller_test.rb b/test/controllers/web/users/attribute_accesses_controller_test.rb index b20cfd39..93467b0d 100644 --- a/test/controllers/web/users/attribute_accesses_controller_test.rb +++ b/test/controllers/web/users/attribute_accesses_controller_test.rb @@ -3,7 +3,7 @@ class Web::Users::AttributeAccessesControllerTest < ActionController::TestCase setup do member = create :member - sign_in member.user + sign_in member @attribute_access = create :attribute_access end diff --git a/test/factories/members.rb b/test/factories/members.rb index 4ebf6de7..ce133fa0 100644 --- a/test/factories/members.rb +++ b/test/factories/members.rb @@ -1,8 +1,6 @@ FactoryGirl.define do factory :member do patronymic { generate :human_name } - association :user - user_id { User.last ? User.last.id : 1 } motto { generate :string } ticket { generate :integer } parent_id 1 diff --git a/test/factories/questionaries.rb b/test/factories/questionaries.rb index c03ddd9c..3500106c 100644 --- a/test/factories/questionaries.rb +++ b/test/factories/questionaries.rb @@ -1,6 +1,15 @@ FactoryGirl.define do factory :questionary do - member_id { Member.last ? Member.last.id : 1 } + patronymic { generate :human_name } + motto { generate :string } + ticket { generate :integer } + parent_id 1 + mobile_phone { generate :phone } + birth_date { generate :date } + home_adress { generate :string } + municipality { generate :string } + locality { generate :string } + avatar { generate :string } experience { generate :string } want_to_do { generate :string } state 'unviewed' From 2b84ed2e33cc5988613ed88c3f555eb4e60cfd35 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 01:23:13 +0300 Subject: [PATCH 192/227] fix lead decorator's function --- app/decorators/news_decorator.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/decorators/news_decorator.rb b/app/decorators/news_decorator.rb index 1092cd3b..7af33ac1 100644 --- a/app/decorators/news_decorator.rb +++ b/app/decorators/news_decorator.rb @@ -6,7 +6,10 @@ def short_lead end def lead - "#{model.body.first(250)}..." + @sentences = ActionController::Base.helpers.strip_tags(model.body).split('.') + @finally_sen = "#{@sentences[0]}." + (1..2).each { |i| @finally_sen += "#{@sentences[i]}." } + @finally_sen end def description_lead @@ -18,7 +21,6 @@ def long_lead end def publish_date_time - #l(object.published_at, format: '%d %b %Y %H:%m') object.published_at.strftime('%d %b %Y %H:%m') end From e0a5ba5a81d962a8f279b559009f2362b5e8bdb6 Mon Sep 17 00:00:00 2001 From: Dmitry Davydov <haudvd@gmail.com> Date: Tue, 10 Mar 2015 01:30:38 +0300 Subject: [PATCH 193/227] add new and find methods to forms --- app/forms/application_form.rb | 23 +++++++++++++++++++++++ app/forms/attribute_access_form.rb | 2 +- app/forms/member_form.rb | 2 +- app/forms/questionary_form.rb | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/forms/application_form.rb b/app/forms/application_form.rb index 90fdd4be..5f8ae770 100644 --- a/app/forms/application_form.rb +++ b/app/forms/application_form.rb @@ -1,2 +1,25 @@ class ApplicationForm < ActiveForm::Base + class << self + + def find_with_model *args + obj = obj_class.find(*args) + self.new obj + end + + def find_with_model_by *args + obj = obj_class.find_by(*args) + self.new obj + end + + def new_with_model *args + obj = obj_class.new(*args) + self.new obj + end + + private + + def obj_class + Object.const_get(self.main_model.capitalize) + end + end end diff --git a/app/forms/attribute_access_form.rb b/app/forms/attribute_access_form.rb index 90443f81..0b9404a3 100644 --- a/app/forms/attribute_access_form.rb +++ b/app/forms/attribute_access_form.rb @@ -1,4 +1,4 @@ -class AttributeAccessForm < ActiveForm::Base +class AttributeAccessForm < ApplicationForm self.main_model = :attribute_access attribute :member_attribute, :member_id, :access diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb index c7ae503c..791cb3e8 100644 --- a/app/forms/member_form.rb +++ b/app/forms/member_form.rb @@ -1,4 +1,4 @@ -class MemberForm < ActiveForm::Base +class MemberForm < ApplicationForm self.main_model = :member attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state diff --git a/app/forms/questionary_form.rb b/app/forms/questionary_form.rb index 32291275..d9817719 100644 --- a/app/forms/questionary_form.rb +++ b/app/forms/questionary_form.rb @@ -1,4 +1,4 @@ -class QuestionaryForm < ActiveForm::Base +class QuestionaryForm < ApplicationForm self.main_model = :questionary attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state, :experience, :member_id, :want_to_do From d2b2d49a5c1742f8ece72c7ca7aff5d92473c491 Mon Sep 17 00:00:00 2001 From: Dmitry Davydov <haudvd@gmail.com> Date: Tue, 10 Mar 2015 01:31:07 +0300 Subject: [PATCH 194/227] refactor admin/users_controller with new form methods --- app/controllers/web/admin/users_controller.rb | 12 ++++-------- app/views/web/admin/users/_form.html.haml | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/controllers/web/admin/users_controller.rb b/app/controllers/web/admin/users_controller.rb index 88938dfe..c0a63c07 100644 --- a/app/controllers/web/admin/users_controller.rb +++ b/app/controllers/web/admin/users_controller.rb @@ -4,18 +4,15 @@ def index end def new - @user = User.new - @user_form = UserForm.new(@user) + @user_form = UserForm.new_with_model end def edit - @user = User.find params[:id] - @user_form = UserForm.new(@user) + @user_form = UserForm.find_with_model(params[:id]) end def create - @user = User.new - @user_form = UserForm.new(@user) + @user_form = UserForm.new_with_model @user_form.submit(params[:user]) if @user_form.save redirect_to admin_users_path @@ -25,8 +22,7 @@ def create end def update - @user = User.find params[:id] - @user_form = UserForm.new(@user) + @user_form = UserForm.find_with_model params[:id] @user_form.submit(params[:user]) if @user_form.save redirect_to admin_users_path diff --git a/app/views/web/admin/users/_form.html.haml b/app/views/web/admin/users/_form.html.haml index d42cb999..4286fdb2 100644 --- a/app/views/web/admin/users/_form.html.haml +++ b/app/views/web/admin/users/_form.html.haml @@ -2,7 +2,7 @@ %h1= page_title(action, User.model_name.human) .row .col-lg-6 - = simple_form_for @user, url: { controller: 'web/admin/users', action: action }, input_html: { class: 'form-horizontal' } do |f| + = simple_form_for @user_form, url: { controller: 'web/admin/users', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :first_name, as: :string = f.input :last_name, as: :string = f.input :email, as: :string From 0b7d124d74ebd7eaa5f1303b505d68229b86523d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 01:38:58 +0300 Subject: [PATCH 195/227] rake db:migrate :) --- db/schema.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index a7e9e9ae..ddd683c3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,11 +11,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150308075000) do +ActiveRecord::Schema.define(version: 20150309113504) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "articles", force: :cascade do |t| + t.string "title" + t.text "body" + t.integer "user_id" + t.string "view" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "attribute_accesses", force: :cascade do |t| t.integer "member_id" t.text "member_attribute" @@ -54,10 +63,10 @@ t.text "body" t.datetime "published_at" t.text "photo" - t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "state" + t.integer "user_id" end create_table "questionaries", force: :cascade do |t| From 95898310472ee589d1ac52a98e815f612f461c68 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 01:41:22 +0300 Subject: [PATCH 196/227] Resolve dependences --- app/models/news.rb | 1 + app/models/user.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/news.rb b/app/models/news.rb index 91eb26aa..107c7964 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -5,6 +5,7 @@ class News < ActiveRecord::Base validates :published_at, presence: true validates :photo, presence: false validates :user_id, presence: true + belongs_to :user def is_published? published_at <= DateTime.now diff --git a/app/models/user.rb b/app/models/user.rb index 26d3c5d8..adfc9f03 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < ActiveRecord::Base has_secure_password validations: false has_many :authentications + has_many :news has_one :member validates :email, uniqueness: true, From cfbc9056b967904ffa2d6334fcb7b40069265e89 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 01:51:30 +0300 Subject: [PATCH 197/227] some fix --- app/models/news.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/models/news.rb b/app/models/news.rb index 107c7964..8f1a5349 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -11,11 +11,8 @@ def is_published? published_at <= DateTime.now end - - scope :published, -> { where('published_at <= ?', DateTime.now).where.not(state: :removed)} scope :unpublished, -> { where('published_at > ?', DateTime.now).where.not(state: :removed)} - scope :removed, -> { where state: :removed } state_machine :state, initial: :unviewed do From 284d65e4aef05ac268b464ab02e4f6861e4484c6 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 01:59:45 +0300 Subject: [PATCH 198/227] add action_form js --- app/assets/javascripts/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6e122111..e620a649 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,6 +17,6 @@ //= require bootstrap-datetimepicker //= require pickers //= require js-routes -//= require action_form +//= require active_form //= require i18n_setup //= require_tree . From a67323ac6516eaea44e816eb1f57f23a3d626035 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 02:04:34 +0300 Subject: [PATCH 199/227] fix tests --- app/controllers/web/users/attribute_accesses_controller.rb | 3 +-- db/schema.rb | 1 + test/controllers/web/users/positions_controller_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/web/users/attribute_accesses_controller.rb b/app/controllers/web/users/attribute_accesses_controller.rb index 94b44c82..3bc2ab1f 100644 --- a/app/controllers/web/users/attribute_accesses_controller.rb +++ b/app/controllers/web/users/attribute_accesses_controller.rb @@ -1,10 +1,9 @@ class Web::Users::AttributeAccessesController < Web::Users::ApplicationController def create - @access = AttributeAccess.find member_attribute: params[:attribute_access][:member_attribute], member_id: params[:attribute_access][:member_id] + @access = AttributeAccess.where(member_attribute: params[:attribute_access][:member_attribute], member_id: params[:attribute_access][:member_id]).first unless @access @access = AttributeAccess.new end - access = AttributeAccess.find_or_initialize_by(params[:attribute_access]) @access_form = AttributeAccessForm.new @access @access_form.submit params[:attribute_access] if @access_form.save diff --git a/db/schema.rb b/db/schema.rb index 68e099b9..6accdbf4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -75,4 +75,5 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false end + end diff --git a/test/controllers/web/users/positions_controller_test.rb b/test/controllers/web/users/positions_controller_test.rb index c85c6ab7..ef86839c 100644 --- a/test/controllers/web/users/positions_controller_test.rb +++ b/test/controllers/web/users/positions_controller_test.rb @@ -4,7 +4,7 @@ class Web::Users::PositionsControllerTest < ActionController::TestCase setup do @position = create :position member = create :member - sign_in member.user + sign_in member end test 'should create position' do From 2093371ca5ddc9bff85e82139b1465c3c30be903 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 02:20:51 +0300 Subject: [PATCH 200/227] add type column --- db/migrate/20150220220847_create_users.rb | 1 + db/schema.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/db/migrate/20150220220847_create_users.rb b/db/migrate/20150220220847_create_users.rb index f400bee4..014f9bbf 100644 --- a/db/migrate/20150220220847_create_users.rb +++ b/db/migrate/20150220220847_create_users.rb @@ -20,6 +20,7 @@ def change t.text :state t.text :experience t.text :want_to_do + t.string :type t.timestamps null: false end diff --git a/db/schema.rb b/db/schema.rb index 6accdbf4..5b139968 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -72,6 +72,7 @@ t.text "state" t.text "experience" t.text "want_to_do" + t.string "type" t.datetime "created_at", null: false t.datetime "updated_at", null: false end From 31436567cebdc24a927b18489e2181e79ce0ad84 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 02:47:40 +0300 Subject: [PATCH 201/227] Add current user to news --- app/controllers/web/admin/news_controller.rb | 2 ++ app/views/layouts/web/admin/application.html.haml | 2 ++ app/views/web/admin/news/_form.html.haml | 2 +- test/controllers/web/admin/news_controller_test.rb | 6 ++++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index 7a8eb148..ad9e14b9 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -14,10 +14,12 @@ def show def create @news = News.new @news_form = NewsForm.new @news + params[:news][:user_id] = current_user @news_form.submit params[:news] if @news_form.save redirect_to admin_news_index_path else + flash[:errors] = @news_form.errors.messages redirect_to action: :new end end diff --git a/app/views/layouts/web/admin/application.html.haml b/app/views/layouts/web/admin/application.html.haml index a64784b1..f3e3671a 100644 --- a/app/views/layouts/web/admin/application.html.haml +++ b/app/views/layouts/web/admin/application.html.haml @@ -8,4 +8,6 @@ %body = render 'layouts/web/admin/navbar' .container + - flash.each do |name, msg| + = content_tag :div, msg, class: "alert alert-dismissible alert-danger" = yield diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index bcb7d0b5..43fc7c5c 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -6,7 +6,7 @@ = f.input :title, as: :string = f.input :body = f.input :published_at, as: :datetime_picker - = f.input :user_id, as: :string + /= f.input :user_id, as: :string /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } = f.button :submit, class: 'btn-success' diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb index 9fe1ab6c..f6cc41e4 100644 --- a/test/controllers/web/admin/news_controller_test.rb +++ b/test/controllers/web/admin/news_controller_test.rb @@ -3,6 +3,8 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase setup do @news = create :news + @admin = create :admin + sign_in @admin end test "should get index" do @@ -18,6 +20,10 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase test "should create news" do attributes = attributes_for :news post :create, news: attributes + puts attributes + flash.each do |name, msg| + puts "#{name} : #{msg}" + end assert_response :redirect, @response.body assert_redirected_to admin_news_index_path assert_equal attributes[:title], News.last.title From df61e5adf2b5630314941582e5628b0ce9e65588 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 03:29:23 +0300 Subject: [PATCH 202/227] fix id's :) + Add some tests --- app/controllers/web/admin/news_controller.rb | 7 +++---- app/views/web/admin/news/_form.html.haml | 2 +- test/controllers/web/admin/news_controller_test.rb | 8 ++------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index ad9e14b9..daf0d727 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -1,11 +1,11 @@ class Web::Admin::NewsController < Web::Admin::ApplicationController def index - @published_news = NewsDecorator.decorate_collection News.published.order('published_at DESC') #FIXME using another decorator in that file + @published_news = NewsDecorator.decorate_collection News.published.order('published_at DESC') @unpublished_news = NewsDecorator.decorate_collection News.unpublished.order('published_at DESC') end def show - @news = NewsDecorator.decorate News.find params[:id]#FIXME I'm don't know how to include decorate there into code + @news = NewsDecorator.decorate News.find params[:id] if !@news.is_published? #FIXME there 404 error path end @@ -14,12 +14,11 @@ def show def create @news = News.new @news_form = NewsForm.new @news - params[:news][:user_id] = current_user + params[:news][:user_id] = current_user.id @news_form.submit params[:news] if @news_form.save redirect_to admin_news_index_path else - flash[:errors] = @news_form.errors.messages redirect_to action: :new end end diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index 43fc7c5c..845bd6f4 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -7,7 +7,7 @@ = f.input :body = f.input :published_at, as: :datetime_picker /= f.input :user_id, as: :string - /FIXME add image preview= f.input :photo, as: :image_preview, input_html: { preview_version: :medium } + = f.input :photo, as: :file = f.button :submit, class: 'btn-success' = link_to t('helpers.links.back'), admin_news_index_path, class: 'btn btn-default' diff --git a/test/controllers/web/admin/news_controller_test.rb b/test/controllers/web/admin/news_controller_test.rb index f6cc41e4..3cf3d0d9 100644 --- a/test/controllers/web/admin/news_controller_test.rb +++ b/test/controllers/web/admin/news_controller_test.rb @@ -3,8 +3,8 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase setup do @news = create :news - @admin = create :admin - sign_in @admin + @user = create :user + sign_in @user end test "should get index" do @@ -20,10 +20,6 @@ class Web::Admin::NewsControllerTest < ActionController::TestCase test "should create news" do attributes = attributes_for :news post :create, news: attributes - puts attributes - flash.each do |name, msg| - puts "#{name} : #{msg}" - end assert_response :redirect, @response.body assert_redirected_to admin_news_index_path assert_equal attributes[:title], News.last.title From 1ac73cfffb4bc1e4604ba405ae41592dac38891c Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 03:35:50 +0300 Subject: [PATCH 203/227] add Lead textbox to News --- db/migrate/20150310003517_add_lead_to_news.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150310003517_add_lead_to_news.rb diff --git a/db/migrate/20150310003517_add_lead_to_news.rb b/db/migrate/20150310003517_add_lead_to_news.rb new file mode 100644 index 00000000..152a34a5 --- /dev/null +++ b/db/migrate/20150310003517_add_lead_to_news.rb @@ -0,0 +1,5 @@ +class AddLeadToNews < ActiveRecord::Migration + def change + add_column :news, :lead, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index ddd683c3..636516b4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150309113504) do +ActiveRecord::Schema.define(version: 20150310003517) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -67,6 +67,7 @@ t.datetime "updated_at", null: false t.text "state" t.integer "user_id" + t.text "lead" end create_table "questionaries", force: :cascade do |t| From 1bf8a93e15a98b3f8e7ce915b0f5af87c8286bbd Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 04:53:15 +0300 Subject: [PATCH 204/227] adding position fields to form --- app/assets/javascripts/active_form_edited.js | 62 +++++++++++++++++++ app/assets/javascripts/application.js | 2 +- app/controllers/web/members_controller.rb | 6 +- .../web/users/account_controller.rb | 8 ++- app/forms/member_form.rb | 6 +- app/models/member.rb | 2 - app/models/user.rb | 5 ++ app/views/web/admin/members/_form.html.haml | 2 +- app/views/web/members/new.html.haml | 28 +++++---- .../users/account/_position_fields.html.haml | 5 ++ app/views/web/users/account/index.html.haml | 16 ++--- 11 files changed, 109 insertions(+), 33 deletions(-) create mode 100644 app/assets/javascripts/active_form_edited.js create mode 100644 app/views/web/users/account/_position_fields.html.haml diff --git a/app/assets/javascripts/active_form_edited.js b/app/assets/javascripts/active_form_edited.js new file mode 100644 index 00000000..75f734ca --- /dev/null +++ b/app/assets/javascripts/active_form_edited.js @@ -0,0 +1,62 @@ +(function($) { + + var createNewResourceID = function() { + return new Date().getTime(); + } + + $(document).on('click', '.add_fields', function(e) { + e.preventDefault(); + + var $link = $(this); + var assoc = $link.data('association'); + var content = $link.data('association-insertion-template'); + var insertionMethod = $link.data('association-insertion-method') || $link.data('association-insertion-position') || 'before'; + var insertionNode = $link.data('association-insertion-node'); + var insertionTraversal = $link.data('association-insertion-traversal'); + var newId = createNewResourceID(); + var regex = new RegExp("new_" + assoc, "g"); + var newContent = content.replace(regex, newId); + + if (insertionNode){ + if (insertionTraversal){ + insertionNode = $link[insertionTraversal](insertionNode); + } else { + insertionNode = insertionNode == "this" ? $link : $(insertionNode); + } + } else { + insertionNode = $link.parent(); + } + + var contentNode = $(newContent); + insertionNode.trigger('before-insert', [contentNode]); + + //var addedContent = insertionNode[insertionMethod](contentNode); + contentNode.insertBefore(insertionNode) + + insertionNode.trigger('after-insert', [contentNode]); + }); + + $(document).on('click', '.remove_fields.dynamic, .remove_fields.existing', function(e) { + e.preventDefault(); + + var $link = $(this); + var wrapperClass = $link.data('wrapper-class') || 'nested-fields'; + var nodeToDelete = $link.closest('.' + wrapperClass); + var triggerNode = nodeToDelete.parent(); + + triggerNode.trigger('before-remove', [nodeToDelete]); + + var timeout = triggerNode.data('remove-timeout') || 0; + + setTimeout(function() { + if ($link.hasClass('dynamic')) { + nodeToDelete.remove(); + } else { + $link.prev("input[type=hidden]").val("1"); + nodeToDelete.hide(); + } + triggerNode.trigger('after-remove', [nodeToDelete]); + }, timeout); + }); + +})(jQuery); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e620a649..01b87110 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,6 +17,6 @@ //= require bootstrap-datetimepicker //= require pickers //= require js-routes -//= require active_form +//= require active_form_edited //= require i18n_setup //= require_tree . diff --git a/app/controllers/web/members_controller.rb b/app/controllers/web/members_controller.rb index 4235d683..55204aee 100644 --- a/app/controllers/web/members_controller.rb +++ b/app/controllers/web/members_controller.rb @@ -2,14 +2,14 @@ class Web::MembersController < Web::ApplicationController before_filter :authenticate_user!, only: [ :new, :create ] def new - @member = Member.new + @member = current_user @member_form = MemberForm.new @member end def create - @member = Member.new + @member = current_user @member_form = MemberForm.new @member - @member_form.submit params[:member] + @member_form.submit params[:user] if @member_form.save redirect_to account_path else diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index 9b39e873..fac75fd2 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -1,11 +1,15 @@ class Web::Users::AccountController < Web::Users::ApplicationController def index - @user = current_user.decorate - @user.member.positions.build if @user.member.present? + @user = current_user + if @user.is_member? + @member_form = MemberForm.new @user + @user.positions.build + end @authentications = current_user.authentications end def update + raise if params[:user] @user = User.find params[:id] @user_form = UserForm.new @user diff --git a/app/forms/member_form.rb b/app/forms/member_form.rb index 791cb3e8..f473e808 100644 --- a/app/forms/member_form.rb +++ b/app/forms/member_form.rb @@ -1,5 +1,9 @@ class MemberForm < ApplicationForm self.main_model = :member - attributes :first_name, :last_name, :patronymic, :email, :user_id, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state + attributes :first_name, :last_name, :patronymic, :email, :motto, :ticket, :parent_id, :mobile_phone, :birth_date, :home_adress, :municipality, :locality, :avatar, :state, :type + + association :positions do + attributes :title, :begin_date, :end_date, :member_id + end end diff --git a/app/models/member.rb b/app/models/member.rb index 86a8cfc6..5c172839 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -3,8 +3,6 @@ class Member < User has_many :attribute_accesses has_many :positions - accepts_nested_attributes_for :positions - validates :patronymic, presence: true, human_name: true validates :motto, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 26d3c5d8..db3b42a7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,6 +10,7 @@ class User < ActiveRecord::Base allow_blank: true validates :last_name, human_name: true, allow_blank: true + validates :ticket, uniqueness: true extend Enumerize enumerize :role, in: [ :user, :admin ], default: :user @@ -38,4 +39,8 @@ class User < ActiveRecord::Base transition :removed => :unviewed end end + + def is_member? + model_name == 'Member' + end end diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index 10157ff3..5b905b95 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -2,7 +2,7 @@ %h1= page_title(action, Member.model_name.human) .row .col-lg-6 - = simple_form_for @member, url: { controller: 'web/admin/members', action: action }, input_html: { class: 'form-horizontal' } do |f| + = simple_form_for @member_form, url: { controller: 'web/admin/members', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :first_name, as: :string, required: true = f.input :last_name, as: :string, required: true = f.input :email, as: :string, required: true diff --git a/app/views/web/members/new.html.haml b/app/views/web/members/new.html.haml index e8e389a0..a25e5775 100644 --- a/app/views/web/members/new.html.haml +++ b/app/views/web/members/new.html.haml @@ -1,15 +1,17 @@ -= simple_form_for @member, url: { controller: 'web/members', action: :create } do |f| - = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } - = f.input :patronymic, as: :string - = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } - = f.input :motto, as: :string - = f.input :ticket, as: :string, required: true - = f.input :mobile_phone, as: :string - = f.input :birth_date, as: :string - = f.input :home_adress, as: :string - = f.input :municipality, as: :string - = f.input :locality, as: :string - = f.input :avatar, as: :string - = f.input :user_id, as: :hidden, input_html: { value: current_user.id } += simple_form_for @member, url: members_path, method: :post do |f| + - f.with_options required: true do |f| + = f.input :first_name, as: :string + = f.input :patronymic, as: :string + = f.input :last_name, as: :string + = f.input :email, as: :string + = f.input :motto, as: :string + = f.input :ticket, as: :string + = f.input :mobile_phone, as: :string + = f.input :birth_date, as: :string + = f.input :home_adress, as: :string + = f.input :municipality, as: :string + = f.input :locality, as: :string + = f.input :type, as: :hidden, input_html: { value: 'Member' } + = f.input :avatar, as: :string = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' diff --git a/app/views/web/users/account/_position_fields.html.haml b/app/views/web/users/account/_position_fields.html.haml new file mode 100644 index 00000000..a345ad80 --- /dev/null +++ b/app/views/web/users/account/_position_fields.html.haml @@ -0,0 +1,5 @@ += f.input :title, as: :string += f.input :begin_date, as: :date_picker += f.input :end_date, as: :date_picker += f.input :member_id, as: :hidden += link_to_remove_association t('.remove_position'), f diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index 589ad254..019fb3af 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -1,5 +1,6 @@ -- if @user.has_confirmed_member? - = simple_form_for @user.member, url: { controller: 'web/users/account', action: :update, id: @user.member.id } do |f| +- if @user.is_member? + - active_form_class_name = 'active_form_class' + = simple_form_for @member_form, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } = f.input :patronymic, as: :string = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } @@ -11,20 +12,15 @@ = f.input :municipality, as: :string = f.input :locality, as: :string = f.input :avatar, as: :string - = f.input :user_id, as: :hidden, input_html: { value: current_user.id } = f.hint t('.my_position_in_mic') = f.simple_fields_for :positions do |ff| - = ff.input :title, as: :string - = ff.input :begin_date, as: :date_picker - = ff.input :end_date, as: :date_picker - = ff.input :member_id, as: :hidden - = link_to_remove_association t('.remove_position'), ff - = link_to_add_association t('.add_position'), f, :positions + = render 'position_fields', f: ff + = link_to_add_association t('.add_position'), f, :positions, data: { 'association-insertion-position' => '0', 'association-insertion-node' => 'this' } = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' %br - attributes_need_access.each do |attribute| - = check_box_tag attribute, '', false, class: 'attribute_access', data: { id: @user.member.id } + = check_box_tag attribute, '', false, class: 'attribute_access', data: { id: @user.id } - else = simple_form_for @user, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| = f.input :first_name, as: :string From 3c3e71a0a1d6fd2bafab7fde23f06e99f872e845 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 04:57:01 +0300 Subject: [PATCH 205/227] fix action_form --- app/assets/javascripts/active_form_edited.js | 62 -------------------- app/assets/javascripts/application.js | 2 +- app/views/web/users/account/index.html.haml | 2 +- 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 app/assets/javascripts/active_form_edited.js diff --git a/app/assets/javascripts/active_form_edited.js b/app/assets/javascripts/active_form_edited.js deleted file mode 100644 index 75f734ca..00000000 --- a/app/assets/javascripts/active_form_edited.js +++ /dev/null @@ -1,62 +0,0 @@ -(function($) { - - var createNewResourceID = function() { - return new Date().getTime(); - } - - $(document).on('click', '.add_fields', function(e) { - e.preventDefault(); - - var $link = $(this); - var assoc = $link.data('association'); - var content = $link.data('association-insertion-template'); - var insertionMethod = $link.data('association-insertion-method') || $link.data('association-insertion-position') || 'before'; - var insertionNode = $link.data('association-insertion-node'); - var insertionTraversal = $link.data('association-insertion-traversal'); - var newId = createNewResourceID(); - var regex = new RegExp("new_" + assoc, "g"); - var newContent = content.replace(regex, newId); - - if (insertionNode){ - if (insertionTraversal){ - insertionNode = $link[insertionTraversal](insertionNode); - } else { - insertionNode = insertionNode == "this" ? $link : $(insertionNode); - } - } else { - insertionNode = $link.parent(); - } - - var contentNode = $(newContent); - insertionNode.trigger('before-insert', [contentNode]); - - //var addedContent = insertionNode[insertionMethod](contentNode); - contentNode.insertBefore(insertionNode) - - insertionNode.trigger('after-insert', [contentNode]); - }); - - $(document).on('click', '.remove_fields.dynamic, .remove_fields.existing', function(e) { - e.preventDefault(); - - var $link = $(this); - var wrapperClass = $link.data('wrapper-class') || 'nested-fields'; - var nodeToDelete = $link.closest('.' + wrapperClass); - var triggerNode = nodeToDelete.parent(); - - triggerNode.trigger('before-remove', [nodeToDelete]); - - var timeout = triggerNode.data('remove-timeout') || 0; - - setTimeout(function() { - if ($link.hasClass('dynamic')) { - nodeToDelete.remove(); - } else { - $link.prev("input[type=hidden]").val("1"); - nodeToDelete.hide(); - } - triggerNode.trigger('after-remove', [nodeToDelete]); - }, timeout); - }); - -})(jQuery); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 01b87110..e620a649 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,6 +17,6 @@ //= require bootstrap-datetimepicker //= require pickers //= require js-routes -//= require active_form_edited +//= require active_form //= require i18n_setup //= require_tree . diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index 019fb3af..542a2139 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -15,7 +15,7 @@ = f.hint t('.my_position_in_mic') = f.simple_fields_for :positions do |ff| = render 'position_fields', f: ff - = link_to_add_association t('.add_position'), f, :positions, data: { 'association-insertion-position' => '0', 'association-insertion-node' => 'this' } + = link_to_add_association t('.add_position'), f, :positions, data: { 'association-insertion-node' => 'this' } = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), root_path, class: 'btn btn-default' %br From af8ce7fdca2b08b1fd3c3390aacc58108cc5c66f Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 05:53:09 +0300 Subject: [PATCH 206/227] saving positions --- app/controllers/web/users/account_controller.rb | 5 ++--- app/views/web/users/account/_position_fields.html.haml | 4 ++-- app/views/web/users/account/index.html.haml | 7 +++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/controllers/web/users/account_controller.rb b/app/controllers/web/users/account_controller.rb index fac75fd2..b072a48f 100644 --- a/app/controllers/web/users/account_controller.rb +++ b/app/controllers/web/users/account_controller.rb @@ -2,14 +2,13 @@ class Web::Users::AccountController < Web::Users::ApplicationController def index @user = current_user if @user.is_member? - @member_form = MemberForm.new @user - @user.positions.build + @member = Member.find current_user.id + @member_form = MemberForm.new @member end @authentications = current_user.authentications end def update - raise if params[:user] @user = User.find params[:id] @user_form = UserForm.new @user diff --git a/app/views/web/users/account/_position_fields.html.haml b/app/views/web/users/account/_position_fields.html.haml index a345ad80..bfc1a1d1 100644 --- a/app/views/web/users/account/_position_fields.html.haml +++ b/app/views/web/users/account/_position_fields.html.haml @@ -1,5 +1,5 @@ = f.input :title, as: :string -= f.input :begin_date, as: :date_picker -= f.input :end_date, as: :date_picker += f.input :begin_date += f.input :end_date = f.input :member_id, as: :hidden = link_to_remove_association t('.remove_position'), f diff --git a/app/views/web/users/account/index.html.haml b/app/views/web/users/account/index.html.haml index 542a2139..3f242143 100644 --- a/app/views/web/users/account/index.html.haml +++ b/app/views/web/users/account/index.html.haml @@ -1,11 +1,10 @@ - if @user.is_member? - - active_form_class_name = 'active_form_class' = simple_form_for @member_form, url: { controller: 'web/users/account', action: :update, id: @user.id } do |f| - = f.input :first_name, as: :string, required: true, input_html: { value: current_user.first_name } + = f.input :first_name, as: :string = f.input :patronymic, as: :string - = f.input :last_name, as: :string, required: true, input_html: { value: current_user.last_name } + = f.input :last_name, as: :string = f.input :motto, as: :string - = f.input :ticket, as: :string, required: true + = f.input :ticket, as: :string = f.input :mobile_phone, as: :string = f.input :birth_date, as: :string = f.input :home_adress, as: :string From df633ddd93e1eea6c3ed4a646a789203d0428a8d Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 06:01:26 +0300 Subject: [PATCH 207/227] fix tests --- app/models/user.rb | 1 - test/controllers/web/admin/members_controller_test.rb | 4 ++++ test/controllers/web/members_controller_test.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index db3b42a7..ebc8fb9f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,7 +10,6 @@ class User < ActiveRecord::Base allow_blank: true validates :last_name, human_name: true, allow_blank: true - validates :ticket, uniqueness: true extend Enumerize enumerize :role, in: [ :user, :admin ], default: :user diff --git a/test/controllers/web/admin/members_controller_test.rb b/test/controllers/web/admin/members_controller_test.rb index 5899bb66..892af21d 100644 --- a/test/controllers/web/admin/members_controller_test.rb +++ b/test/controllers/web/admin/members_controller_test.rb @@ -12,6 +12,8 @@ class Web::Admin::MembersControllerTest < ActionController::TestCase test 'should create member' do attributes = attributes_for :member + attributes[:positions_attributes] ||= {} + attributes[:positions_attributes]['0'] = attributes_for :position post :create, member: attributes assert_response :redirect, @response.body assert_redirected_to admin_members_path @@ -25,6 +27,8 @@ class Web::Admin::MembersControllerTest < ActionController::TestCase test 'should patch update' do attributes = attributes_for :member + attributes[:positions_attributes] ||= {} + attributes[:positions_attributes]['0'] = attributes_for :position patch :update, member: attributes, id: @member assert_response :redirect, @response.body assert_redirected_to admin_members_path diff --git a/test/controllers/web/members_controller_test.rb b/test/controllers/web/members_controller_test.rb index 3576f750..e7fd2589 100644 --- a/test/controllers/web/members_controller_test.rb +++ b/test/controllers/web/members_controller_test.rb @@ -19,7 +19,7 @@ class Web::MembersControllerTest < ActionController::TestCase test 'should create member' do attributes = attributes_for :member - post :create, member: attributes + post :create, user: attributes assert_response :redirect, @response.body assert_redirected_to account_path assert_equal attributes[:patronymic], Member.last.patronymic From e1778c5e32b28b46ffdc0405120b0dfa8c5b8457 Mon Sep 17 00:00:00 2001 From: pavel <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 06:04:19 +0300 Subject: [PATCH 208/227] add position form to admin --- app/assets/javascripts/web/admin/application.coffee | 1 + app/views/web/admin/members/_form.html.haml | 3 +++ app/views/web/admin/members/_position_fields.html.haml | 5 +++++ 3 files changed, 9 insertions(+) create mode 100644 app/views/web/admin/members/_position_fields.html.haml diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index 60b8e156..19ca39e5 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -6,6 +6,7 @@ #= require bootstrap-datetimepicker #= require moment/ru #= require pickers +#= require active_form $ -> $('.link').click -> diff --git a/app/views/web/admin/members/_form.html.haml b/app/views/web/admin/members/_form.html.haml index 5b905b95..bd71364e 100644 --- a/app/views/web/admin/members/_form.html.haml +++ b/app/views/web/admin/members/_form.html.haml @@ -14,5 +14,8 @@ = f.input :municipality, as: :string = f.input :locality, as: :string = f.input :avatar, as: :string + = f.simple_fields_for :positions do |ff| + = render 'position_fields', f: ff + = link_to_add_association t('.add_position'), f, :positions, data: { 'association-insertion-node' => 'this' } = f.button :submit, t('helpers.links.save'), class: 'btn-success' = link_to t('helpers.links.back'), admin_members_path, class: 'btn btn-default' diff --git a/app/views/web/admin/members/_position_fields.html.haml b/app/views/web/admin/members/_position_fields.html.haml new file mode 100644 index 00000000..bfc1a1d1 --- /dev/null +++ b/app/views/web/admin/members/_position_fields.html.haml @@ -0,0 +1,5 @@ += f.input :title, as: :string += f.input :begin_date += f.input :end_date += f.input :member_id, as: :hidden += link_to_remove_association t('.remove_position'), f From 4dfd04aff7ebdce6324d29723e6dca889f2900bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB=20=D0=9A=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=D1=88=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= <kalashnikovisme@gmail.com> Date: Tue, 10 Mar 2015 06:14:21 +0300 Subject: [PATCH 209/227] Update ReadMe.md --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 64bf2f7b..80962a05 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ -[](https://travis-ci.org/ulmic/ulmicru) [](https://coveralls.io/r/ulmic/ulmicru) [](https://codeclimate.com/github/ulmic/ulmicru) +[](https://travis-ci.org/ulmic/ulmicru) [](https://coveralls.io/r/ulmic/ulmicru?branch=feature%2Ffix_travis_tests) [](https://codeclimate.com/github/ulmic/ulmicru) ```shell git clone git@github.com:ulmic/ulmicru.git cd ulmicru From 5571528964698dd13bdc9ef31c21292d4352df65 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 10:06:36 +0300 Subject: [PATCH 210/227] Add Lead --- app/decorators/news_decorator.rb | 2 +- app/views/web/admin/news/_form.html.haml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/decorators/news_decorator.rb b/app/decorators/news_decorator.rb index 7af33ac1..9df62bf1 100644 --- a/app/decorators/news_decorator.rb +++ b/app/decorators/news_decorator.rb @@ -5,7 +5,7 @@ def short_lead "#{model.body.first(50)}..." end - def lead + def generate_lead @sentences = ActionController::Base.helpers.strip_tags(model.body).split('.') @finally_sen = "#{@sentences[0]}." (1..2).each { |i| @finally_sen += "#{@sentences[i]}." } diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index 845bd6f4..73796d1d 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -4,6 +4,7 @@ .col-lg-6 = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :title, as: :string + = f.input :lead = f.input :body = f.input :published_at, as: :datetime_picker /= f.input :user_id, as: :string From a10cfe3e2f254e843aeac5605a85d442203f5c1e Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Tue, 10 Mar 2015 10:47:27 +0300 Subject: [PATCH 211/227] fix wrong route???? --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 8a9873d2..2fb44595 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,7 +5,7 @@ get '/auth/:provider/callback' => 'web/omniauth#callback' get 'account' => 'web/users/account#index' get '/admin' => 'web/admin/welcome#index' - get '/:ticket' => 'web/members#show' + #get '/:ticket' => 'web/members#show' scope module: :web do resource :session, only: [:new, :create, :destroy] From e18231c1162037890d370bd4b05c976936d5b2ed Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 11 Mar 2015 16:10:55 +0300 Subject: [PATCH 212/227] "Fix routing for MemberController" --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 2fb44595..6464ce3d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,7 +5,6 @@ get '/auth/:provider/callback' => 'web/omniauth#callback' get 'account' => 'web/users/account#index' get '/admin' => 'web/admin/welcome#index' - #get '/:ticket' => 'web/members#show' scope module: :web do resource :session, only: [:new, :create, :destroy] @@ -38,6 +37,7 @@ resources :join end end + get '/:ticket' => 'web/members#show', constraints: { ticket: /\d*/ } # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". From 1358ddd42b7616d514ead57e53eed1a12c44d93a Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 11 Mar 2015 16:11:35 +0300 Subject: [PATCH 213/227] "Add lead to NewsForm" --- app/forms/news_form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/forms/news_form.rb b/app/forms/news_form.rb index 5dd10e6f..a5202104 100644 --- a/app/forms/news_form.rb +++ b/app/forms/news_form.rb @@ -1,5 +1,5 @@ class NewsForm < ApplicationForm self.main_model = :news - attributes :title, :body, :published_at, :photo, :user_id + attributes :title, :body, :published_at, :photo, :user_id, :lead end From 3db72c3dab0a75fc96a37d67ef57282f4ac7afe5 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 11 Mar 2015 16:15:09 +0300 Subject: [PATCH 214/227] add lead precense --- app/models/news.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/news.rb b/app/models/news.rb index 8f1a5349..b0b3e3f4 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -5,6 +5,7 @@ class News < ActiveRecord::Base validates :published_at, presence: true validates :photo, presence: false validates :user_id, presence: true + validates :lead, presence: true belongs_to :user def is_published? @@ -13,7 +14,7 @@ def is_published? scope :published, -> { where('published_at <= ?', DateTime.now).where.not(state: :removed)} scope :unpublished, -> { where('published_at > ?', DateTime.now).where.not(state: :removed)} - scope :removed, -> { where state: :removed } + scope :removed, -> { where state: :removed } state_machine :state, initial: :unviewed do state :unviewed From 6cac5154e49a7ce461c751ba3cfce9d129f3b9c8 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 11 Mar 2015 22:25:27 +0300 Subject: [PATCH 215/227] some fix --- app/views/web/admin/news/_form.html.haml | 6 +++--- config/locales/ru/models.yml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index 73796d1d..a660464c 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -1,14 +1,14 @@ .page-header %h1=t '.title' .row - .col-lg-6 + .col-lg-8.col-lg-offset-2 = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| = f.input :title, as: :string - = f.input :lead + = f.input :lead, as: :string = f.input :body = f.input :published_at, as: :datetime_picker /= f.input :user_id, as: :string = f.input :photo, as: :file - = f.button :submit, class: 'btn-success' + = f.button :submit, class: 'btn-success', value: t('helpers.links.save') = link_to t('helpers.links.back'), admin_news_index_path, class: 'btn btn-default' diff --git a/config/locales/ru/models.yml b/config/locales/ru/models.yml index 2fe9a99a..aca6b300 100644 --- a/config/locales/ru/models.yml +++ b/config/locales/ru/models.yml @@ -51,5 +51,6 @@ ru: published_at: Дата публикации created_at: Создано user_id: Автор + lead: Краткое содержание photo: Фотография From 5ff98029b582c78718c88baa4486a12f39e879c8 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Wed, 11 Mar 2015 22:58:39 +0300 Subject: [PATCH 216/227] fix dropdown menu --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/web/admin/application.coffee | 1 + 2 files changed, 2 insertions(+) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 1a2c5bb1..0cb49062 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -11,6 +11,7 @@ // about supported directives. // //= require jquery +//= require bootstrap-sprockets //= require jquery_ujs //= require turbolinks //= require moment diff --git a/app/assets/javascripts/web/admin/application.coffee b/app/assets/javascripts/web/admin/application.coffee index f6d8c0a9..cb61e8b8 100644 --- a/app/assets/javascripts/web/admin/application.coffee +++ b/app/assets/javascripts/web/admin/application.coffee @@ -1,4 +1,5 @@ #= require jquery +#= require bootstrap-sprockets #= require jquery_ujs #= require turbolinks #= require moment From 570cf6c8779f79da8b7db22760ae31a83b12c61d Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 12:32:00 +0300 Subject: [PATCH 217/227] Add save unsaved params of news after wrong safe:) --- app/controllers/web/admin/news_controller.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index daf0d727..5d091bfe 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -19,12 +19,18 @@ def create if @news_form.save redirect_to admin_news_index_path else - redirect_to action: :new + flash[:errors] = @news_form.errors + flash[:model_name_class] = "News" + redirect_to action: :new, :news => params[:news] end end def new @news = News.new @news_form = NewsForm.new @news + if params[:news].present? + @news_form.submit params[:news] + end + @news_form = NewsForm.new @news end def edit From f074ab510d05c0a773a90df5fbce16a9eae339a4 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 12:32:11 +0300 Subject: [PATCH 218/227] Add flash error show --- app/helpers/application_helper.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 456ee093..af8d7944 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -44,6 +44,17 @@ def auth_path(provider) def social_network_localize(provider) I18n.t("social_networks.#{provider}") end + + def flash_show_errors + tags = "" + if flash[:errors].present? + flash[:errors].each do |attr, val| + translate_path = "#{flash[:model_name_class]}.human_attribute_name(:#{attr})" + tags += content_tag :div, "Поле \"#{eval(translate_path)}\" #{val.to_s[2..-3]}" , class: "alert alert-dismissible alert-danger" + end + end + tags + end def not_linked_social_networks(authentications) list = SocialNetworks.list From d1270abb638f2124a190870bcd46807ea2fa536e Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 12:33:22 +0300 Subject: [PATCH 219/227] Returned to older state --- app/views/layouts/web/admin/application.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/layouts/web/admin/application.html.haml b/app/views/layouts/web/admin/application.html.haml index f3e3671a..a64784b1 100644 --- a/app/views/layouts/web/admin/application.html.haml +++ b/app/views/layouts/web/admin/application.html.haml @@ -8,6 +8,4 @@ %body = render 'layouts/web/admin/navbar' .container - - flash.each do |name, msg| - = content_tag :div, msg, class: "alert alert-dismissible alert-danger" = yield From 85ac2edbf5bdd4071656dc3e238ddca2e3fb2fd9 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 12:35:39 +0300 Subject: [PATCH 220/227] add localization news + add flash_erors_show --- app/views/web/admin/news/_form.html.haml | 3 ++- app/views/web/admin/news/_news_list.html.haml | 2 +- app/views/web/admin/news/index.html.haml | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index a660464c..77225224 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -1,5 +1,6 @@ .page-header - %h1=t '.title' + %h1=t model_class.model_name.human + = flash_show_errors.html_safe .row .col-lg-8.col-lg-offset-2 = simple_form_for @news, url: { controller: 'web/admin/news', action: action }, input_html: { class: 'form-horizontal' } do |f| diff --git a/app/views/web/admin/news/_news_list.html.haml b/app/views/web/admin/news/_news_list.html.haml index f8971e72..ff493b9f 100644 --- a/app/views/web/admin/news/_news_list.html.haml +++ b/app/views/web/admin/news/_news_list.html.haml @@ -8,7 +8,7 @@ %th= model_class.human_attribute_name(:body) %th= model_class.human_attribute_name(:published_at) %th= model_class.human_attribute_name(:created_at) - %th=t '.actions', default: t('helpers.actions') + %th=t 'helpers.links.actions' %tbody - news.each do |news| %tr diff --git a/app/views/web/admin/news/index.html.haml b/app/views/web/admin/news/index.html.haml index 6c5e4514..4cce6a94 100644 --- a/app/views/web/admin/news/index.html.haml +++ b/app/views/web/admin/news/index.html.haml @@ -1,8 +1,6 @@ -= javascript_include_tag :tabs -= stylesheet_link_tag :tabs - +- model_class = News .page-header - %h1=t '.title' + %h1=t model_class.model_name.human.pluralize(:ru) %ul.nav.nav-tabs{ role: :tablist } %li = link_to t('helpers.links.published'), '#published' From 93d513b24badac6a8788963104b14c2aab2ec5ab Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 12:36:14 +0300 Subject: [PATCH 221/227] add inflection for news --- config/initializers/inflections.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index b5d27bee..c797bae3 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -7,4 +7,5 @@ inflect.plural /ль$/i, 'ли' inflect.plural /н /i, 'ны ' inflect.plural /та /i, 'ты ' + inflect.plural /ть$/i, 'ти' end From 6bec7d197b03b95fcbae7276cc0896dc7cf8178a Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 12:37:02 +0300 Subject: [PATCH 222/227] :photo need for model --- app/models/news.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/news.rb b/app/models/news.rb index b0b3e3f4..d79cc05b 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -3,7 +3,7 @@ class News < ActiveRecord::Base validates :title, presence: true validates :body, presence: true validates :published_at, presence: true - validates :photo, presence: false + validates :photo, presence: true validates :user_id, presence: true validates :lead, presence: true belongs_to :user From b342ba6edced3f8c6125e9d1a06faa379f79c334 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 21:35:39 +0300 Subject: [PATCH 223/227] some fixes --- app/views/web/admin/news/_form.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/web/admin/news/_form.html.haml b/app/views/web/admin/news/_form.html.haml index 77225224..59eaa967 100644 --- a/app/views/web/admin/news/_form.html.haml +++ b/app/views/web/admin/news/_form.html.haml @@ -1,3 +1,4 @@ +- model_class = News .page-header %h1=t model_class.model_name.human = flash_show_errors.html_safe From c70d2c1dd5c480fa495fd3d2a3311b250d2fb660 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 21:42:14 +0300 Subject: [PATCH 224/227] add lead to factories for mews --- test/factories/news.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/factories/news.rb b/test/factories/news.rb index 90e25cf1..389113a6 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -1,9 +1,10 @@ FactoryGirl.define do factory :news do - title { generate :string } - body { generate :string } + title { generate :string } + body { generate :string } published_at { DateTime.now } photo { generate :file } - user_id { generate :integer } + lead {generate :string} + user_id { generate :integer } end end From 55d7df19261256cdb0d21f1733a69dfd1c4aee33 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 22:03:45 +0300 Subject: [PATCH 225/227] fix for wrong sessions --- app/controllers/web/admin/news_controller.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/web/admin/news_controller.rb b/app/controllers/web/admin/news_controller.rb index 5d091bfe..a566c6cb 100644 --- a/app/controllers/web/admin/news_controller.rb +++ b/app/controllers/web/admin/news_controller.rb @@ -14,23 +14,27 @@ def show def create @news = News.new @news_form = NewsForm.new @news - params[:news][:user_id] = current_user.id + + #fix because sessions work wrong + params[:news][:user_id] = current_user.id if current_user.present? + @news_form.submit params[:news] if @news_form.save redirect_to admin_news_index_path else flash[:errors] = @news_form.errors - flash[:model_name_class] = "News" + flash[:model_name_class] = 'News' redirect_to action: :new, :news => params[:news] end end + def new @news = News.new @news_form = NewsForm.new @news if params[:news].present? @news_form.submit params[:news] end - @news_form = NewsForm.new @news + @news_form = NewsForm.new @news end def edit @@ -44,7 +48,7 @@ def update @news_form.submit params[:news] if @news_form.save redirect_to admin_news_index_path - else + else redirect_to action: :edit end end From cef8111006c92ebe976ead279a800f93305d003f Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 22:09:09 +0300 Subject: [PATCH 226/227] change :file to :string for fastre testing --- test/factories/news.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/factories/news.rb b/test/factories/news.rb index 389113a6..5fbc82d1 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -3,8 +3,8 @@ title { generate :string } body { generate :string } published_at { DateTime.now } - photo { generate :file } - lead {generate :string} + photo { generate :string } + lead { generate :string } user_id { generate :integer } end end From fc244ccc19f10fdbfb18810264c6449058d37ac8 Mon Sep 17 00:00:00 2001 From: AlisVero <soxat73rus@gmail.com> Date: Thu, 12 Mar 2015 22:21:35 +0300 Subject: [PATCH 227/227] fix wrong factory for news --- test/factories/news.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/factories/news.rb b/test/factories/news.rb index 5fbc82d1..58b69b49 100644 --- a/test/factories/news.rb +++ b/test/factories/news.rb @@ -3,7 +3,7 @@ title { generate :string } body { generate :string } published_at { DateTime.now } - photo { generate :string } + photo { generate :file } lead { generate :string } user_id { generate :integer } end