From 39a5778bf63569d5630dfb59d1c145669e2aefd7 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:05:04 +0100 Subject: [PATCH 01/60] removed dummy application and minitest files --- test/dummy/README.rdoc | 28 ---- test/dummy/Rakefile | 6 - test/dummy/app/assets/images/.keep | 0 .../app/assets/javascripts/application.js | 13 -- .../app/assets/stylesheets/application.css | 15 -- .../app/controllers/application_controller.rb | 5 - test/dummy/app/controllers/concerns/.keep | 0 test/dummy/app/helpers/application_helper.rb | 2 - test/dummy/app/mailers/.keep | 0 test/dummy/app/models/.keep | 0 test/dummy/app/models/concerns/.keep | 0 test/dummy/app/models/post.rb | 2 - test/dummy/app/models/setting.rb | 59 ------- test/dummy/app/models/user.rb | 19 --- .../app/views/layouts/application.html.erb | 14 -- test/dummy/bin/bundle | 3 - test/dummy/bin/rails | 4 - test/dummy/bin/rake | 4 - test/dummy/config.ru | 4 - test/dummy/config/application.rb | 25 --- test/dummy/config/boot.rb | 5 - test/dummy/config/database.yml | 25 --- test/dummy/config/environment.rb | 5 - test/dummy/config/environments/development.rb | 37 ----- test/dummy/config/environments/production.rb | 83 ---------- test/dummy/config/environments/test.rb | 34 ---- .../initializers/backtrace_silencers.rb | 7 - .../config/initializers/cookies_serializer.rb | 3 - .../initializers/filter_parameter_logging.rb | 4 - test/dummy/config/initializers/inflections.rb | 16 -- test/dummy/config/initializers/mime_types.rb | 4 - .../config/initializers/session_store.rb | 3 - .../config/initializers/setting_accessors.rb | 1 - .../config/initializers/wrap_parameters.rb | 14 -- test/dummy/config/locales/en.yml | 23 --- test/dummy/config/routes.rb | 56 ------- test/dummy/config/secrets.yml | 22 --- test/dummy/config/settings.yml | 23 --- .../db/migrate/20150102112106_create_users.rb | 9 - .../migrate/20150102115329_create_settings.rb | 12 -- .../db/migrate/20150723114600_create_posts.rb | 11 -- test/dummy/db/schema.rb | 40 ----- test/dummy/db/test.sqlite3 | Bin 28672 -> 0 bytes test/dummy/lib/assets/.keep | 0 test/dummy/log/.keep | 0 test/dummy/public/404.html | 67 -------- test/dummy/public/422.html | 67 -------- test/dummy/public/500.html | 66 -------- test/dummy/public/favicon.ico | 0 test/dummy/test/fixtures/posts.yml | 11 -- test/dummy/test/models/setting_test.rb | 143 ---------------- test/dummy/test/models/user_test.rb | 154 ------------------ test/setting_accessors_test.rb | 4 - test/test_helper.rb | 31 ---- 54 files changed, 1183 deletions(-) delete mode 100644 test/dummy/README.rdoc delete mode 100644 test/dummy/Rakefile delete mode 100644 test/dummy/app/assets/images/.keep delete mode 100644 test/dummy/app/assets/javascripts/application.js delete mode 100644 test/dummy/app/assets/stylesheets/application.css delete mode 100644 test/dummy/app/controllers/application_controller.rb delete mode 100644 test/dummy/app/controllers/concerns/.keep delete mode 100644 test/dummy/app/helpers/application_helper.rb delete mode 100644 test/dummy/app/mailers/.keep delete mode 100644 test/dummy/app/models/.keep delete mode 100644 test/dummy/app/models/concerns/.keep delete mode 100644 test/dummy/app/models/post.rb delete mode 100644 test/dummy/app/models/setting.rb delete mode 100644 test/dummy/app/models/user.rb delete mode 100644 test/dummy/app/views/layouts/application.html.erb delete mode 100755 test/dummy/bin/bundle delete mode 100755 test/dummy/bin/rails delete mode 100755 test/dummy/bin/rake delete mode 100644 test/dummy/config.ru delete mode 100644 test/dummy/config/application.rb delete mode 100644 test/dummy/config/boot.rb delete mode 100644 test/dummy/config/database.yml delete mode 100644 test/dummy/config/environment.rb delete mode 100644 test/dummy/config/environments/development.rb delete mode 100644 test/dummy/config/environments/production.rb delete mode 100644 test/dummy/config/environments/test.rb delete mode 100644 test/dummy/config/initializers/backtrace_silencers.rb delete mode 100644 test/dummy/config/initializers/cookies_serializer.rb delete mode 100644 test/dummy/config/initializers/filter_parameter_logging.rb delete mode 100644 test/dummy/config/initializers/inflections.rb delete mode 100644 test/dummy/config/initializers/mime_types.rb delete mode 100644 test/dummy/config/initializers/session_store.rb delete mode 100644 test/dummy/config/initializers/setting_accessors.rb delete mode 100644 test/dummy/config/initializers/wrap_parameters.rb delete mode 100644 test/dummy/config/locales/en.yml delete mode 100644 test/dummy/config/routes.rb delete mode 100644 test/dummy/config/secrets.yml delete mode 100644 test/dummy/config/settings.yml delete mode 100644 test/dummy/db/migrate/20150102112106_create_users.rb delete mode 100644 test/dummy/db/migrate/20150102115329_create_settings.rb delete mode 100644 test/dummy/db/migrate/20150723114600_create_posts.rb delete mode 100644 test/dummy/db/schema.rb delete mode 100644 test/dummy/db/test.sqlite3 delete mode 100644 test/dummy/lib/assets/.keep delete mode 100644 test/dummy/log/.keep delete mode 100644 test/dummy/public/404.html delete mode 100644 test/dummy/public/422.html delete mode 100644 test/dummy/public/500.html delete mode 100644 test/dummy/public/favicon.ico delete mode 100644 test/dummy/test/fixtures/posts.yml delete mode 100644 test/dummy/test/models/setting_test.rb delete mode 100644 test/dummy/test/models/user_test.rb delete mode 100644 test/setting_accessors_test.rb delete mode 100644 test/test_helper.rb diff --git a/test/dummy/README.rdoc b/test/dummy/README.rdoc deleted file mode 100644 index dd4e97e..0000000 --- a/test/dummy/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 -rake doc:app. diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile deleted file mode 100644 index ba6b733..0000000 --- a/test/dummy/Rakefile +++ /dev/null @@ -1,6 +0,0 @@ -# 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/test/dummy/app/assets/images/.keep b/test/dummy/app/assets/images/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/dummy/app/assets/javascripts/application.js b/test/dummy/app/assets/javascripts/application.js deleted file mode 100644 index 5bc2e1c..0000000 --- a/test/dummy/app/assets/javascripts/application.js +++ /dev/null @@ -1,13 +0,0 @@ -// 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 vendor/assets/javascripts of plugins, if any, 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_tree . diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css deleted file mode 100644 index a443db3..0000000 --- a/test/dummy/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 vendor/assets/stylesheets of plugins, if any, 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/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb deleted file mode 100644 index d83690e..0000000 --- a/test/dummy/app/controllers/application_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -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/test/dummy/app/controllers/concerns/.keep b/test/dummy/app/controllers/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb deleted file mode 100644 index de6be79..0000000 --- a/test/dummy/app/helpers/application_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module ApplicationHelper -end diff --git a/test/dummy/app/mailers/.keep b/test/dummy/app/mailers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/dummy/app/models/.keep b/test/dummy/app/models/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/dummy/app/models/concerns/.keep b/test/dummy/app/models/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/test/dummy/app/models/post.rb b/test/dummy/app/models/post.rb deleted file mode 100644 index 791dcb5..0000000 --- a/test/dummy/app/models/post.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Post < ActiveRecord::Base -end diff --git a/test/dummy/app/models/setting.rb b/test/dummy/app/models/setting.rb deleted file mode 100644 index 4576931..0000000 --- a/test/dummy/app/models/setting.rb +++ /dev/null @@ -1,59 +0,0 @@ -# -# This model handles the management of system wide or record specific settings -# -# @attr [String] name -# The setting's name -# -# @attr [Object] value -# The setting's value. May be anything that can be serialized through YAML -# -# You can access global settings just like a normal class method, -# please have a look at #method_missing for more information. -# -# If not absolutely necessary, please **do not** create settings yourself -# through Setting.new, instead use #create_or_update instead. -# -# There are also some usage examples in the corresponding test. -# - -class Setting < ActiveRecord::Base - belongs_to :assignable, :polymorphic => true - - serialize :value - - include SettingAccessors::SettingScaffold - - #---------------------------------------------------------------- - # Validations - #---------------------------------------------------------------- - - validates :name, - :uniqueness => {:scope => [:assignable_type, :assignable_id]}, - :presence => true - - validates_with SettingAccessors::Validator - - # - # Makes accessing settings a little easier. - # Examples: - # - # #Loading **the value** of a global setting named "my_setting" - # Setting.my_setting - # - # #Setting **the value** of a global setting named "my_setting" - # Setting.my_setting = [1,2,3,4,5] - # - # #Loading **the value** of an assigned setting named "cool_setting" - # #+some_cool_user+ is here an instance of ActiveRecord::Base - # Setting.cool_setting(some_cool_user) - # - def self.method_missing(method, *args) - super(method, *args) if args.size > 1 - method_name = method.to_s - if method_name.last == '=' - self.create_or_update(method_name[0..method_name.length-2], args.first, nil, true) - else - self.get(method_name, args.first) - end - end -end \ No newline at end of file diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb deleted file mode 100644 index 75ebfb2..0000000 --- a/test/dummy/app/models/user.rb +++ /dev/null @@ -1,19 +0,0 @@ -class User < ActiveRecord::Base - - setting_accessor :polymorphic_setting, :type => :polymorphic, :default => {}, :fallback => :default - - setting_accessor :locale, :type => :string, :validations => {:presence => true} - - setting_accessor :a_string, :fallback => :default - - setting_accessor :a_number, :fallback => :global - - setting_accessor :a_boolean, :fallback => false - - setting_accessor :class_wise_truthy_boolean, :type => :boolean, :default => true, :fallback => :default - - setting_accessor :class_wise_with_value_fallback, :type => :string, :fallback => 'Oiski Poiski!' - - setting_accessor :class_wise_with_default_fallback, :type => :string, :fallback => :default, :default => 'Kapitanski' - -end diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb deleted file mode 100644 index 593a778..0000000 --- a/test/dummy/app/views/layouts/application.html.erb +++ /dev/null @@ -1,14 +0,0 @@ - - - - Dummy - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> - - - -<%= yield %> - - - diff --git a/test/dummy/bin/bundle b/test/dummy/bin/bundle deleted file mode 100755 index 66e9889..0000000 --- a/test/dummy/bin/bundle +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -load Gem.bin_path('bundler', 'bundle') diff --git a/test/dummy/bin/rails b/test/dummy/bin/rails deleted file mode 100755 index 728cd85..0000000 --- a/test/dummy/bin/rails +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require_relative '../config/boot' -require 'rails/commands' diff --git a/test/dummy/bin/rake b/test/dummy/bin/rake deleted file mode 100755 index 1724048..0000000 --- a/test/dummy/bin/rake +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -require_relative '../config/boot' -require 'rake' -Rake.application.run diff --git a/test/dummy/config.ru b/test/dummy/config.ru deleted file mode 100644 index 5bc2a61..0000000 --- a/test/dummy/config.ru +++ /dev/null @@ -1,4 +0,0 @@ -# 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/test/dummy/config/application.rb b/test/dummy/config/application.rb deleted file mode 100644 index d2e37ad..0000000 --- a/test/dummy/config/application.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.expand_path('../boot', __FILE__) - -require 'rails/all' - -Bundler.require(*Rails.groups) -require "setting_accessors" - -module Dummy - 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 - - config.active_support.test_order = :sorted - end -end - diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb deleted file mode 100644 index 6266cfc..0000000 --- a/test/dummy/config/boot.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) - -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) -$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml deleted file mode 100644 index 1c1a37c..0000000 --- a/test/dummy/config/database.yml +++ /dev/null @@ -1,25 +0,0 @@ -# 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/test/dummy/config/environment.rb b/test/dummy/config/environment.rb deleted file mode 100644 index ee8d90d..0000000 --- a/test/dummy/config/environment.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Load the Rails application. -require File.expand_path('../application', __FILE__) - -# Initialize the Rails application. -Rails.application.initialize! diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb deleted file mode 100644 index ddf0e90..0000000 --- a/test/dummy/config/environments/development.rb +++ /dev/null @@ -1,37 +0,0 @@ -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 - - # 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/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb deleted file mode 100644 index 47d3553..0000000 --- a/test/dummy/config/environments/production.rb +++ /dev/null @@ -1,83 +0,0 @@ -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 Rails's static asset server (Apache or nginx will already do this). - config.serve_static_assets = false - - # 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 - - # Generate digests for assets URLs. - config.assets.digest = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' - - # 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 - - # Set to :debug to see everything in the log. - config.log_level = :info - - # 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" - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) - - # 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 - - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false - - # 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/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb deleted file mode 100644 index a103e91..0000000 --- a/test/dummy/config/environments/test.rb +++ /dev/null @@ -1,34 +0,0 @@ -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 asset 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 - - # 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/test/dummy/config/initializers/backtrace_silencers.rb b/test/dummy/config/initializers/backtrace_silencers.rb deleted file mode 100644 index 59385cd..0000000 --- a/test/dummy/config/initializers/backtrace_silencers.rb +++ /dev/null @@ -1,7 +0,0 @@ -# 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/test/dummy/config/initializers/cookies_serializer.rb b/test/dummy/config/initializers/cookies_serializer.rb deleted file mode 100644 index 7a06a89..0000000 --- a/test/dummy/config/initializers/cookies_serializer.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Rails.application.config.action_dispatch.cookies_serializer = :json \ No newline at end of file diff --git a/test/dummy/config/initializers/filter_parameter_logging.rb b/test/dummy/config/initializers/filter_parameter_logging.rb deleted file mode 100644 index 4a994e1..0000000 --- a/test/dummy/config/initializers/filter_parameter_logging.rb +++ /dev/null @@ -1,4 +0,0 @@ -# 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/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb deleted file mode 100644 index ac033bf..0000000 --- a/test/dummy/config/initializers/inflections.rb +++ /dev/null @@ -1,16 +0,0 @@ -# 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/test/dummy/config/initializers/mime_types.rb b/test/dummy/config/initializers/mime_types.rb deleted file mode 100644 index dc18996..0000000 --- a/test/dummy/config/initializers/mime_types.rb +++ /dev/null @@ -1,4 +0,0 @@ -# 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/test/dummy/config/initializers/session_store.rb b/test/dummy/config/initializers/session_store.rb deleted file mode 100644 index e766b67..0000000 --- a/test/dummy/config/initializers/session_store.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Rails.application.config.session_store :cookie_store, key: '_dummy_session' diff --git a/test/dummy/config/initializers/setting_accessors.rb b/test/dummy/config/initializers/setting_accessors.rb deleted file mode 100644 index 093b3b4..0000000 --- a/test/dummy/config/initializers/setting_accessors.rb +++ /dev/null @@ -1 +0,0 @@ -SettingAccessors.setting_class = Setting \ No newline at end of file diff --git a/test/dummy/config/initializers/wrap_parameters.rb b/test/dummy/config/initializers/wrap_parameters.rb deleted file mode 100644 index 33725e9..0000000 --- a/test/dummy/config/initializers/wrap_parameters.rb +++ /dev/null @@ -1,14 +0,0 @@ -# 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/test/dummy/config/locales/en.yml b/test/dummy/config/locales/en.yml deleted file mode 100644 index 0653957..0000000 --- a/test/dummy/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" diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb deleted file mode 100644 index 3f66539..0000000 --- a/test/dummy/config/routes.rb +++ /dev/null @@ -1,56 +0,0 @@ -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/test/dummy/config/secrets.yml b/test/dummy/config/secrets.yml deleted file mode 100644 index fd59d1a..0000000 --- a/test/dummy/config/secrets.yml +++ /dev/null @@ -1,22 +0,0 @@ -# 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: 9466152fe1a4826b5d3df6a72834d41c3771e28d1d9aa4bbdc46f5882bf118f2c1baba5ce51f0adeb66d1ee452c10635c5dcedcf6a94b92dfef756d3b75f95f5 - -test: - secret_key_base: 0ed95a983cd27bdf250be838d482830715643bf7f110971bdac935a2d3437c1c2f5a8f6bc7d3fc97f8819a4f1628950311952e219783d53d810f043bf8708972 - -# 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/test/dummy/config/settings.yml b/test/dummy/config/settings.yml deleted file mode 100644 index 3524374..0000000 --- a/test/dummy/config/settings.yml +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file specifies all settings used in the application -# (be it record specific or global). -# -# The keys are the setting names, the values are a hash -# containing validation options, type and default value -# - -a_string: - type: string - default: "I am a string!" - validations: - presence: true - -a_number: - type: integer - default: "I am a Number!" - -a_boolean: - type: boolean - default: true - validations: - boolean: true \ No newline at end of file diff --git a/test/dummy/db/migrate/20150102112106_create_users.rb b/test/dummy/db/migrate/20150102112106_create_users.rb deleted file mode 100644 index 2f69d27..0000000 --- a/test/dummy/db/migrate/20150102112106_create_users.rb +++ /dev/null @@ -1,9 +0,0 @@ -class CreateUsers < ActiveRecord::Migration - def change - create_table :users do |t| - t.string :first_name - t.string :last_name - t.timestamps null: false - end - end -end diff --git a/test/dummy/db/migrate/20150102115329_create_settings.rb b/test/dummy/db/migrate/20150102115329_create_settings.rb deleted file mode 100644 index ce66188..0000000 --- a/test/dummy/db/migrate/20150102115329_create_settings.rb +++ /dev/null @@ -1,12 +0,0 @@ -class CreateSettings < ActiveRecord::Migration - def change - create_table :settings do |t| - t.belongs_to :assignable, :polymorphic => true - - t.string :name - t.text :value - - t.timestamps - end - end -end \ No newline at end of file diff --git a/test/dummy/db/migrate/20150723114600_create_posts.rb b/test/dummy/db/migrate/20150723114600_create_posts.rb deleted file mode 100644 index 4bf0a8f..0000000 --- a/test/dummy/db/migrate/20150723114600_create_posts.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreatePosts < ActiveRecord::Migration - def change - create_table :posts do |t| - t.belongs_to :user - t.string :title - t.string :text - - t.timestamps null: false - end - end -end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb deleted file mode 100644 index 4fa8eaa..0000000 --- a/test/dummy/db/schema.rb +++ /dev/null @@ -1,40 +0,0 @@ -# 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: 20150723114600) do - - create_table "posts", force: :cascade do |t| - t.integer "user_id" - t.string "title" - t.string "text" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "settings", force: :cascade do |t| - t.integer "assignable_id" - t.string "assignable_type" - t.string "name" - t.text "value" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "users", force: :cascade do |t| - t.string "first_name" - t.string "last_name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - -end diff --git a/test/dummy/db/test.sqlite3 b/test/dummy/db/test.sqlite3 deleted file mode 100644 index 6a5c7cb81d01f50682a9ac7f6e8dae728ce1c9fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI#(Qnc~90%~00-eid(U(3gUix6x423c`azr=&X!2OcNeQcjX(=>;K@ zB&~>373cKGh(>z)Anx)^`?}_e^yug6y6DZWJ(jfm-EZr^)*r5Y5f3;Z009U<00Izz z00bZafm<#xRA-UbelPU6KMdGhKz&uW%MF_}oytMe zCKczk{ec+Q4L3;5A;#P!2H7-DXvoAD46~n6^@3Q4%Ds<$I`{5eAUjz>qx`aJ|7CJ= z;)U%)FCh)p z-q|fuZZ>KaM-1At9WnQ0{yZ_de#p2RcmrxoKQE$vZ$YpfQjgP)>v4m0M2q_aIy=?q zD2iUwZ;rc)s&8z_XAdsrDCd6AV{CFyCli{6Z>gQU^6%Rrs?9~wj{^b_ zfB*y_009U<00Izz00bZafh86|{lCON7pXx20uX=z1Rwwb2tWV=5P$##CIYDcu@xWy z0SG_<0uX=z1Rwwb2tWV=%P)ZXfBFA0GK2sGAOHafKmY;|fB*y_009U@^}nKhm9z`( zhq&Q@00bZa0SG_<0uX=z1Rwwb2teRg3*?lPSt?pPMXOk{tex%Bvs`L6QnHG>Ie8L! zTH3a(C%eVss>n&v&L!=;cCKCA>IAVk2tWV=5P$##AOHafKmY;|fWUt*a9=UyWc9Bk SLs9vkx~G_T; - - - The page you were looking for doesn't exist (404) - - - - - - -
-
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/test/dummy/public/422.html b/test/dummy/public/422.html deleted file mode 100644 index a21f82b..0000000 --- a/test/dummy/public/422.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - The change you wanted was rejected (422) - - - - - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/test/dummy/public/500.html b/test/dummy/public/500.html deleted file mode 100644 index 061abc5..0000000 --- a/test/dummy/public/500.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - We're sorry, but something went wrong (500) - - - - - - -
-
-

We're sorry, but something went wrong.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/test/dummy/public/favicon.ico b/test/dummy/public/favicon.ico deleted file mode 100644 index e69de29..0000000 diff --git a/test/dummy/test/fixtures/posts.yml b/test/dummy/test/fixtures/posts.yml deleted file mode 100644 index 937a0c0..0000000 --- a/test/dummy/test/fixtures/posts.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -# This model initially had no columns defined. If you add columns to the -# model remove the '{}' from the fixture names and add the columns immediately -# below each fixture, per the syntax in the comments below -# -one: {} -# column: value -# -two: {} -# column: value diff --git a/test/dummy/test/models/setting_test.rb b/test/dummy/test/models/setting_test.rb deleted file mode 100644 index 35de427..0000000 --- a/test/dummy/test/models/setting_test.rb +++ /dev/null @@ -1,143 +0,0 @@ -require_relative '../../../test_helper' - -class SettingTest < ActiveSupport::TestCase - should validate_presence_of :name - - #FIXME: This is currently setting assignable_type and assignable_id to "0" resp. "1" which breaks everything. - # should validate_uniqueness_of(:name).scoped_to([:assignable_type, :assignable_id]) - - context 'Global Setting Accessors (method missing)' do - - should 'ignore a setting function if more than 1 argument is given' do - assert_raises(NoMethodError) { Setting.my_setting(1, 2, 3) } - end - - should 'return nil for a non-existing setting' do - assert_nil Setting.gotta_catch_em_all - end - - should 'create a new setting if necessary' do - assert Setting.count.zero? - assert Setting.gotta_catch_em_all = 'Pokemon!' - assert Setting.count == 1 - end - - should "return the setting's value for an existing setting" do - assert Setting.gotta_catch_em_all = 'Pokemon!' - assert_equal Setting.gotta_catch_em_all, 'Pokemon!' - end - - should 'update an existing setting instead of creating a new one' do - assert Setting.count.zero? - assert Setting.gotta_catch_em_all = 'Pokemon!' - assert Setting.gotta_catch_em_all = 'Pokemon!' - assert Setting.count == 1 - end - - should 'return assignable specific settings if an assignable is given' do - ash = User.create(:first_name => 'Ash', :last_name => 'Ketchum') - gary = User.create(:first_name => 'Gary', :last_name => 'Oak') - team_rocket = User.create(:first_name => 'Jessie', :last_name => 'James') - - assert Setting.create_or_update(:pokedex_count, 151, ash) - assert Setting.create_or_update(:pokedex_count, 1, gary) - - assert_nil Setting.pokedex_count - - assert_equal Setting.pokedex_count(ash), 151 - assert_equal Setting.pokedex_count(gary), 1 - - #They don't want to be on file. - assert_nil Setting.pokedex_count(team_rocket) - end - end - - context 'The create_or_update function' do - setup do - @ash = User.create(:first_name => 'Ash', :last_name => 'Ketchum') - end - - should 'create a new assigned setting if it did not exist before' do - assert Setting.count.zero? - Setting.create_or_update(:pokedex_count, 151, @ash) - assert Setting.count == 1 - end - - should 'update an assigned setting if it already exists' do - Setting.create_or_update(:pokedex_count, 150, @ash) - assert Setting.count == 1 - Setting.create_or_update(:pokedex_count, 151, @ash) - assert Setting.count == 1 - assert_equal Setting.pokedex_count(@ash), 151 - end - - should 'return a setting object by default' do - assert_instance_of Setting, Setting.create_or_update(:pokedex_count, 151, @ash) - end - - should 'return just the value if wished' do - assert_equal Setting.create_or_update(:pokedex_count, 151, @ash, true), 151 - end - end - - context 'The globally_defined method' do - should 'return true for a setting which is defined in config/settings.yml' do - assert SettingAccessors::Internal.globally_defined_setting?('a_string') - end - - should 'return false for a setting which is not defined in config/settings.yml' do - assert !SettingAccessors::Internal.globally_defined_setting?('something_different') - end - end - - context 'setting_accessors' do - should 'return the default value as fallback if :fallback => :default is given' do - assert_equal 'I am a string!', User.new.a_string - end - - should 'return the global setting value as fallback if :fallback => :global is given' do - Setting.a_number = 42 - assert_equal 42, User.new.a_number - end - - should 'return the given value as fallback if :fallback => VALUE is given' do - assert_equal false, User.new.a_boolean - end - end - - context 'Class-Defined Settings' do - should 'have the type defined in the setting_accessor call' do - assert u = User.create(:first_name => 'a', :last_name => 'name', :locale => 'de') - assert s = Setting.setting_record(:locale, User.first) - assert_equal 'string', s.value_type.to_s - end - - should 'not override global settings' do - assert_raises(ArgumentError) do - User.class_eval do - setting_accessor :a_string, :type => :integer - end - end - end - - should 'use value fallbacks' do - assert_equal 'Oiski Poiski!', User.new.class_wise_with_value_fallback - end - - should 'use default fallbacks' do - assert_equal 'Kapitanski', User.new.class_wise_with_default_fallback - end - end - - context 'Boolean settings' do - should 'respect temporary settings which are set to `false`' do - user = User.new(:class_wise_truthy_boolean => false) - assert_equal user.class_wise_truthy_boolean, false - end - - should 'respect temporary settings which are set to `true`' do - user = User.new(:a_boolean => true) - assert_equal user.a_boolean, true - end - end -end diff --git a/test/dummy/test/models/user_test.rb b/test/dummy/test/models/user_test.rb deleted file mode 100644 index 0facda9..0000000 --- a/test/dummy/test/models/user_test.rb +++ /dev/null @@ -1,154 +0,0 @@ -require_relative '../../../test_helper' - -class UserTest < ActiveSupport::TestCase - context 'JSON serialization' do - setup do - @user = User.new(:a_string => 'test', :a_number => 42, :a_boolean => false) - end - - should 'include the setting accessors' do - SettingAccessors::Internal.setting_accessor_names(User).each do |setting_name| - assert_includes @user.as_json.keys, setting_name - end - end - - should 'contain the correct values' do - SettingAccessors::Internal.setting_accessor_names(User).each do |setting_name| - assert_equal @user.as_json[setting_name.to_s], @user.send(setting_name) - end - end - - context 'when using as_json with option :only' do - context 'set to a class attribute name' do - should 'not include any setting accessors' do - json = @user.as_json :only => [:first_name] - SettingAccessors::Internal.setting_accessor_names(User).each do |setting_name| - assert_not_includes json.keys, setting_name - end - end - end - - context 'set to a setting accessor name' do - should 'not include other setting accessors' do - json = @user.as_json :only => [:a_string] - assert_includes json.keys, 'a_string' - (SettingAccessors::Internal.setting_accessor_names(User) - ['a_string']).each do |setting_name| - assert_not_includes json.keys, setting_name - end - end - end - end - - context 'when using as_json with option :except' do - context 'set to a class attribute name' do - should 'include all setting accessors' do - json = @user.as_json :except => [:first_name] - SettingAccessors::Internal.setting_accessor_names(User).each do |setting_name| - assert_includes json.keys, setting_name - end - end - end - - context 'set to a setting accessor name' do - should 'include all other setting accessors' do - json = @user.as_json :except => [:a_string] - assert_not_includes json.keys, 'a_string' - (SettingAccessors::Internal.setting_accessor_names(User) - ['a_string']).each do |setting_name| - assert_includes json.keys, setting_name - end - end - end - end - end - - context 'Boolean getter methods' do - setup do - @user = User.new(:a_string => 'test', :a_number => 42, :a_boolean => false) - end - - should 'be created for boolean settings' do - assert @user.respond_to?(:a_boolean?), '?-getter is not defined for boolean settings' - end - - should 'return the same value as the original getter' do - assert_equal @user.a_boolean, @user.a_boolean? - end - - should 'not be created for non-boolean settings' do - assert !@user.respond_to?(:a_number?) - assert !@user.respond_to?(:a_string?) - end - end - - context 'the read set (@temp_settings)' do - setup do - @user = User.create - @user_alias = User.find(@user.id) - - # Use @user_alias here to ensure that the setting value is saved in the instance's read set - @user.a_boolean = !@user_alias.a_boolean - assert @user.save - end - - should 'be refreshed with the new values on #reload' do - assert @user_alias.reload - assert_equal @user.a_boolean, @user_alias.a_boolean - end - end - - context 'Polymorphic class-wise settings' do - setup do - @user = User.create - end - - context 'when being assigned an initial value' do - should 'be created in database' do - @user.polymorphic_setting = {:a => :b} - assert @user.save - assert_equal User.last, @user - assert_equal User.last.polymorphic_setting, {:a => :b} - end - - should 'be created in database if one of their properties changes' do - @user.polymorphic_setting[:new_key] = 'new_value' - assert @user.save - assert_equal User.last, @user - assert_equal({:new_key => 'new_value'}, User.last.polymorphic_setting) - end - - should 'not change the value of other assignable settings' do - @user2 = User.create - @user2.polymorphic_setting = {:foo => :bar} - assert @user2.save - assert_equal User.first.polymorphic_setting, {} - end - end - - context 'when being updated' do - setup do - @user.polymorphic_setting = {:a => :b} - assert @user.save - assert @user.reload - assert_equal({:a => :b}, @user.polymorphic_setting) - assert_equal({:a => :b}, User.last.polymorphic_setting) - end - - # Single hash value changed, etc. - should 'be saved if one of their properties changes' do - @user.polymorphic_setting[:a] = :c - assert @user.save - assert @user.reload - assert_equal({:a => :c}, @user.polymorphic_setting) - assert_equal({:a => :c}, User.last.polymorphic_setting) - end - - should 'be updated if their whole value changes' do - @user.polymorphic_setting = {:a => :c} - assert @user.save - assert @user.reload - assert_equal({:a => :c}, @user.polymorphic_setting) - assert_equal({:a => :c}, User.last.polymorphic_setting) - end - end - end -end diff --git a/test/setting_accessors_test.rb b/test/setting_accessors_test.rb deleted file mode 100644 index cde7eb0..0000000 --- a/test/setting_accessors_test.rb +++ /dev/null @@ -1,4 +0,0 @@ -require_relative 'test_helper' - -class SettingAccessorsTest < ActiveSupport::TestCase -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 25e55bd..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,31 +0,0 @@ -# Configure Rails Environment -ENV["RAILS_ENV"] = "test" - -require File.expand_path("../dummy/config/environment.rb", __FILE__) - -require 'active_support' -require 'minitest' -require 'shoulda' -require "rails/test_help" - -Rails.backtrace_cleaner.remove_silencers! - -# Load support files -Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } - -# Load fixtures from the engine -if ActiveSupport::TestCase.method_defined?(:fixture_path=) - ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) -end - -# configure shoulda matchers -Shoulda::Matchers.configure do |config| - config.integrate do |with| - with.test_framework :minitest - with.library :rails - end -end - -# For generators -require "rails/generators/test_case" -require "generators/setting_accessors/install_generator" From 2ab4af4c3cc040d71369d872fed519ab41c0ad84 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:05:50 +0100 Subject: [PATCH 02/60] Added RSpec and Rubocop dependencies, lowered rails related dependencies --- .rspec | 1 + .rubocop.yml | 3 +++ .travis.yml | 1 + Rakefile | 32 ++++------------------- setting_accessors.gemspec | 10 ++++--- test/dummy/test/models/post_test.rb | 19 -------------- test/generators/install_generator_test.rb | 15 ----------- 7 files changed, 16 insertions(+), 65 deletions(-) create mode 100644 .rspec create mode 100644 .rubocop.yml delete mode 100644 test/dummy/test/models/post_test.rb delete mode 100644 test/generators/install_generator_test.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..85c948b --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,3 @@ +AllCops: + TargetRailsVersion: 5.0 + TargetRubyVersion: 2.5 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 9927325..57f7f26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ cache: bundler rvm: - 2.2 - 2.3.1 + - 2.5.0 env: - DISABLE_DATABASE_ENVIRONMENT_CHECK=1 diff --git a/Rakefile b/Rakefile index 5cb00c4..9de9288 100644 --- a/Rakefile +++ b/Rakefile @@ -1,30 +1,8 @@ -require "bundler/gem_tasks" +# frozen_string_literal: true -begin - require 'bundler/setup' -rescue LoadError - puts 'You must `gem install bundler` and `bundle install` to run rake tasks' -end +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' -require 'rdoc/task' +RSpec::Core::RakeTask.new(:spec) -RDoc::Task.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'ArMailerRevised' - rdoc.options << '--line-numbers' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('lib/**/*.rb') -end - -Bundler::GemHelper.install_tasks - -require 'rake/testtask' - -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.verbose = false -end - -task default: :test \ No newline at end of file +task default: :spec \ No newline at end of file diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index 114905d..76bfd6b 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -24,9 +24,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.6" spec.add_development_dependency "rake", "~> 10.4" spec.add_development_dependency 'sqlite3', '~> 1.3' - spec.add_development_dependency 'shoulda', '~> 3.5' - spec.add_development_dependency 'minitest', '~> 5.5' - spec.add_development_dependency 'byebug', '~> 3.5' + spec.add_development_dependency 'rspec', '~> 3.8.0' + spec.add_development_dependency 'generator_spec', '~> 0.9.4' + spec.add_development_dependency 'with_model', '~> 2.1.2' + spec.add_development_dependency 'rubocop', '~> 0.60.0' - spec.add_dependency 'rails', ['>= 4.1', '< 5.1'] + spec.add_dependency 'activerecord', ['>= 4.1', '<= 5.2'] + spec.add_dependency 'activesupport', ['>= 4.1', '<= 5.2'] end diff --git a/test/dummy/test/models/post_test.rb b/test/dummy/test/models/post_test.rb deleted file mode 100644 index f3a4e2a..0000000 --- a/test/dummy/test/models/post_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require_relative '../../../test_helper' - -class PostTest < ActiveSupport::TestCase - context 'JSON serialization' do - setup do - @post = Post.new(:title => 'a post', :text => 'a content') - end - - should 'work without having any setting_accessors defined' do - assert @post.as_json - end - - should 'contain all original public attributes' do - [:title, :text, :id, :created_at, :updated_at].each do |attr| - assert_includes @post.as_json.keys, attr.to_s - end - end - end -end diff --git a/test/generators/install_generator_test.rb b/test/generators/install_generator_test.rb deleted file mode 100644 index ce0c281..0000000 --- a/test/generators/install_generator_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -require_relative '../test_helper' - -class InstallGeneratorTest < Rails::Generators::TestCase - tests SettingAccessors::Generators::InstallGenerator - destination File.expand_path('../dummy/tmp', File.dirname(__FILE__)) - setup :prepare_destination - - test 'Assert all files are properly created' do - run_generator - assert_file 'config/settings.yml' - assert_file 'config/initializers/setting_accessors.rb' - assert_file 'app/models/setting.rb' - assert_migration 'db/migrate/create_settings.rb' - end -end \ No newline at end of file From 3ec1375615c58dd04b61108b95d1a1598157cbcd Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:06:40 +0100 Subject: [PATCH 03/60] Added first RSpec replacements for old minitests --- spec/lib/generators/install_generator_spec.rb | 29 ++ .../lib/setting_accessors/integration_spec.rb | 266 ++++++++++++++++++ spec/spec_helper.rb | 114 ++++++++ spec/support/helpers.rb | 7 + spec/support/setting_model.rb | 54 ++++ 5 files changed, 470 insertions(+) create mode 100644 spec/lib/generators/install_generator_spec.rb create mode 100644 spec/lib/setting_accessors/integration_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 spec/support/helpers.rb create mode 100644 spec/support/setting_model.rb diff --git a/spec/lib/generators/install_generator_spec.rb b/spec/lib/generators/install_generator_spec.rb new file mode 100644 index 0000000..2337a97 --- /dev/null +++ b/spec/lib/generators/install_generator_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'generator_spec' +require "generators/setting_accessors/install_generator" + +describe SettingAccessors::Generators::InstallGenerator, type: :generator do + destination File.expand_path('../dummy/tmp', File.dirname(__FILE__)) + + before(:all) do + prepare_destination + run_generator + end + + it 'creates the default settings.yml config file' do + assert_file 'config/settings.yml' + end + + it 'creates the initializer' do + assert_file 'config/initializers/setting_accessors.rb' + end + + it 'creates the model' do + assert_file 'app/models/setting.rb' + end + + it 'creates the migration' do + assert_migration 'db/migrate/create_settings.rb' + end +end \ No newline at end of file diff --git a/spec/lib/setting_accessors/integration_spec.rb b/spec/lib/setting_accessors/integration_spec.rb new file mode 100644 index 0000000..ccda8b4 --- /dev/null +++ b/spec/lib/setting_accessors/integration_spec.rb @@ -0,0 +1,266 @@ +# frozen_string_literal: true + +describe SettingAccessors::Integration, type: :model do + include SettingModel + + #---------------------------------------------------------------- + # #as_json + #---------------------------------------------------------------- + + describe '#as_json' do + with_model 'User' do + table do |t| + t.string :first_name + t.string :last_name + t.timestamps null: false + end + + model do + setting_accessor :polymorphic_setting, type: :polymorphic, default: {}, fallback: :default + setting_accessor :locale, type: :string + setting_accessor :recently_active, type: :boolean, default: true, fallback: :default + + setting_accessor :catchphrase, type: :string, fallback: 'Oiski Poiski!' + setting_accessor :additional_catchphrase, type: :string, fallback: :default, default: 'Kapitanski' + + validates :locale, presence: true + end + end + + let(:user_attributes) { {first_name: 'Sascha', last_name: 'Desman', locale: 'RU-ru'} } + let(:user) { User.create(user_attributes) } + let(:additional_attribute_names) { %w[id created_at updated_at] } + let(:options) { {} } + let(:json) { user.as_json(options) } + let(:settings_and_attributes) { setting_names(User) + user_attributes.keys.map(&:to_s) } + + context 'when being called without options' do + it 'returns all attributes and all settings' do + settings_and_attributes.each do |name| + expect(json).to include(name => user.send(name)) + end + end + end + + context 'when being called with the :only option' do + let(:options) { super().merge(only: %i[first_name locale]) } + + it 'returns only the requested attributes and settings' do + expect(json).to eql user_attributes.slice(:first_name, :locale).stringify_keys + end + end + + context 'when being called with the :except option' do + let(:options) { super().merge(except: %w[first_name recent_activity]) } + + it 'returns all attributes and settings except for the given exclusions' do + (settings_and_attributes - options[:except]).each do |name| + expect(json).to include(name => user.send(name)) + end + + options[:except].each do |name| + expect(json).not_to include(name) + end + end + end + end + + #---------------------------------------------------------------- + # #save + #---------------------------------------------------------------- + + describe '#save' do + context 'when a normal attribute was changed' do + + end + + context 'when only a setting was changed' do + context 'by assigning ' do + + end + end + end + + #---------------------------------------------------------------- + # #reload + #---------------------------------------------------------------- + + describe '#reload' do + + end + + #---------------------------------------------------------------- + # .setting_accessor + #---------------------------------------------------------------- + + describe '.setting_accessor' do + let(:record) { TestModel.create } + + shared_examples 'getters' do + it 'creates a getter method' do + expect(record).to respond_to(:setting_with_default) + end + + context 'if a default value was set' do + context 'and no custom value was given yet' do + it 'returns the default value' do + expect(record.setting_with_default).to eql default_value + end + end + + context 'and a custom value was given' do + it 'returns the custom value' do + record.setting_with_default = custom_value + expect(record.setting_with_default).to eql custom_value + end + end + end + + context 'if no default value was set' do + it 'returns nil' do + expect(record.setting_without_default).to be nil + end + end + end + + shared_examples 'setters' do + it 'creates a setter method' do + expect(record).to respond_to(:setting_with_default=) + end + + it 'marks the attribute as changed when a new value is assigned' do + record.setting_with_default = custom_value + expect(record.setting_with_default_changed?).to be true + end + + it 'marks the record as changed when a new value is assigned' do + record.setting_with_default = custom_value + expect(record).to be_changed + end + + it 'typecasts the value correctly' do + record.setting_with_default = raw_custom_value + expect(record.setting_with_default).to eql custom_value + expect(record.setting_with_default_before_type_cast).to eql raw_custom_value + end + + it 'provides access to the old value until the record is saved' do + record.setting_with_default = custom_value + expect(record.setting_with_default_was).to eql default_value + record.save! + record.setting_with_default = default_value + expect(record.setting_with_default_was).to eql custom_value + + # Sub-test for nil values + record.setting_without_default = custom_value + expect(record.setting_without_default_was).to be nil + end + end + + context 'when setting up a boolean setting' do + with_model 'TestModel' do + model do + setting_accessor :setting_with_default, type: :boolean, default: true + setting_accessor :setting_without_default, type: :boolean + end + end + + let(:default_value) { true } + let(:custom_value) { false } + let(:raw_custom_value) { 'false' } + + context 'regarding setters' do + include_examples 'setters' + end + + context 'regarding getters' do + include_examples 'getters' do + it 'creates a ?-alias for the getter' do + expect(record).to respond_to(:setting_with_default?) + expect(record.setting_with_default?).to eql record.setting_with_default + end + end + end + end + + context 'when setting up a string setting' do + with_model 'TestModel' do + model do + setting_accessor :setting_with_default, type: :string, default: 'Oiski Poiski!' + setting_accessor :setting_without_default, type: :string + end + end + + let(:default_value) { 'Oiski Poiski!' } + let(:custom_value) { 'true' } + let(:raw_custom_value) { true } + + context 'regarding getters' do + include_examples 'getters' + end + + context 'regarding setters' do + include_examples 'setters' + end + end + + context 'when setting up an integer setting' do + with_model 'TestModel' do + model do + setting_accessor :setting_with_default, type: :integer, default: 42 + setting_accessor :setting_without_default, type: :integer + end + end + + let(:default_value) { 42 } + let(:custom_value) { 21 } + let(:raw_custom_value) { '21' } + + context 'regarding getters' do + include_examples 'getters' + end + + context 'regarding setters' do + include_examples 'setters' + end + end + + context 'when setting up a polymorphic setting' do + with_model 'TestModel' do + model do + setting_accessor :setting_with_default, type: :polymorphic, default: {} + setting_accessor :setting_without_default, type: :polymorphic + end + end + + let(:default_value) { {} } + let(:custom_value) { {oiski: 'Poiski'} } + let(:raw_custom_value) { custom_value } + + context 'regarding getters' do + include_examples 'getters' + end + + context 'regarding setters' do + include_examples 'setters' do + it 'does not share the default value between all settings' do + # yes, this happened... + another_record = TestModel.create + record.setting_with_default[:a] = :b + expect(another_record.setting_with_default).to eql default_value + end + + it 'marks the attribute as changed when the existing value is mutated' do + record.setting_with_default[:a] = :b + expect(record.setting_with_default_changed?).to be true + end + + it 'marks the record as changed when the existing value is mutated' do + record.setting_with_default[:a] = :b + expect(record).to be_changed + end + end + end + end + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..f571158 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,114 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration + +require 'active_record' + +# Hold the test sqlite database in memory without actually creating additional files +ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:" + +require 'with_model' +require 'setting_accessors' +require_relative 'support/setting_model' +require_relative 'support/helpers' + +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end + + config.extend WithModel + config.include Helpers +end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb new file mode 100644 index 0000000..cb3b572 --- /dev/null +++ b/spec/support/helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Helpers + def setting_names(klass) + SettingAccessors::Internal.setting_accessor_names(klass) + end +end \ No newline at end of file diff --git a/spec/support/setting_model.rb b/spec/support/setting_model.rb new file mode 100644 index 0000000..6dee97f --- /dev/null +++ b/spec/support/setting_model.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module SettingModel + def self.included(base) + base.with_model 'Setting' do + table do |t| + t.belongs_to :assignable, polymorphic: true, index: false + t.string :name + t.text :value + t.timestamps + end + + model do + belongs_to :assignable, :polymorphic => true + serialize :value + include SettingAccessors::SettingScaffold + + #---------------------------------------------------------------- + # Validations + #---------------------------------------------------------------- + + validates :name, + uniqueness: {scope: [:assignable_type, :assignable_id]}, + presence: true + + validates_with SettingAccessors::Validator + + # + # Makes accessing settings a little easier. + # Examples: + # + # #Loading **the value** of a global setting named "my_setting" + # Setting.my_setting + # + # #Setting **the value** of a global setting named "my_setting" + # Setting.my_setting = [1,2,3,4,5] + # + # #Loading **the value** of an assigned setting named "cool_setting" + # #+some_cool_user+ is here an instance of ActiveRecord::Base + # Setting.cool_setting(some_cool_user) + # + def self.method_missing(method, *args) + super(method, *args) if args.size > 1 + method_name = method.to_s + if method_name.last == '=' + self.create_or_update(method_name[0..method_name.length - 2], args.first, nil, true) + else + self.get(method_name, args.first) + end + end + end + end + end +end \ No newline at end of file From b4397e3f7884fc9610e6d16d5636fc1b8cc9265c Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:07:24 +0100 Subject: [PATCH 04/60] Added Helper file containing hash lookup related methods --- lib/setting_accessors.rb | 1 + lib/setting_accessors/helpers.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 lib/setting_accessors/helpers.rb diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 483fea8..7bafbee 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -1,4 +1,5 @@ require 'setting_accessors/version' +require 'setting_accessors/helpers' require 'setting_accessors/accessor' require 'setting_accessors/converter' require 'setting_accessors/integration' diff --git a/lib/setting_accessors/helpers.rb b/lib/setting_accessors/helpers.rb new file mode 100644 index 0000000..fe5d945 --- /dev/null +++ b/lib/setting_accessors/helpers.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module SettingAccessors + module Helpers + class Error < StandardError; end + class NestedHashKeyNotFoundException < Error; end + + def ensure_nested_hash!(hash, *keys) + h = hash + keys.each do |key| + h[key] ||= {} + h = h[key] + end + end + + def lookup_nested_hash(hash, *keys) + fail NestedHashKeyNotFoundException if hash.nil? + + h = hash + keys.each do |key| + fail NestedHashKeyNotFoundException unless h.key?(key) + h = h[key] + end + h + end + end +end \ No newline at end of file From 781fe4dee8408cbd858cd723821447d3de4ebdbb Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:09:37 +0100 Subject: [PATCH 05/60] Started removing global settings, started Integration/Accessor refactoring --- lib/setting_accessors/accessor.rb | 315 +++++++++++++-------------- lib/setting_accessors/integration.rb | 41 ++-- lib/setting_accessors/internal.rb | 53 +---- 3 files changed, 172 insertions(+), 237 deletions(-) diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index 5547ab8..b95c70a 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -1,189 +1,184 @@ # # Helper class to make accessing record specific settings easier # -class SettingAccessors::Accessor +module SettingAccessors + class Accessor + include ::SettingAccessors::Helpers - def initialize(record) - @record = record - @temp_settings = {} - end - - # - # Tries to retrieve the given setting's value from the temp settings - # (already read/written values in this instance). If the setting hasn't been - # used before, its value is retrieved from the database. - # - # If a setting hasn't been read by this record (instance) before, its value - # is stored in the local read set. - # - # TODO: See if this causes problems with read settings not being updated by external changes. - # User1: Read Setting X - # User2: Update Setting X - # User1: Read Setting X -> Gets old value from temp settings. - # This shouldn't be too dangerous as the system state will be refreshed with every request though. - # - def [](key) - return @temp_settings[key.to_sym] if has_key?(key) - value = SettingAccessors.setting_class.get(key, @record) - @temp_settings[key.to_sym] = value unless value.nil? - value - end - - # - # Tries to fetch a setting value using the provided key and #[]. - # It will only return the +default+ value if there is - # - no temporary setting with the given key AND - # - no already persisted setting (see #[]) - # - def fetch(key, default = nil) - result = self[key] - return default if result.nil? && !has_key?(key) - result - end + attr_reader :record - # - # Like #fetch, but it will store the default value as a temporary setting - # if no actual setting value could be found. This is useful to further work - # with default setting values. - # The default value is cloned (using #dup to avoid copying object states) before - # it is assigned. This will not work for singleton instances like true, false, etc. - # - def fetch_and_store(key, default = nil) - result = self[key] - if result.nil? && !has_key?(key) - self[key] = default.duplicable? ? default.dup : default - else - result + def initialize(record) + @record = record + @temp_settings = {} end - end - - def has_key?(key) - @temp_settings.has_key?(key.to_sym) - end - - # - # Writes a setting's value - # - def []=(key, val) - set_value_before_type_cast(key, val) - @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).convert(val) - end - # - # Tries to find a setting for this record. - # If none is found, will return the default setting value - # specified in the setting config file. - # - def get_or_default(key) - fetch_and_store(key, SettingAccessors.setting_class.get_or_default(key, @record)) - end + # + # Tries to retrieve the given setting's value from the temp settings + # (already read/written values in this instance). If the setting hasn't been + # used before, its value is retrieved from the database. + # + # If a setting hasn't been read by this record (instance) before, its value + # is stored in the local read set. + # + # TODO: See if this causes problems with read settings not being updated by external changes. + # User1: Read Setting X + # User2: Update Setting X + # User1: Read Setting X -> Gets old value from temp settings. + # This shouldn't be too dangerous as the system state will be refreshed with every request though. + # + # @param [Boolean] skip_cached + # If set to +true+, the setting value is freshly loaded from the database, + # even if a value that's not yet persisted exists. + # + def [](key, skip_cached: false) + return @temp_settings[key.to_sym] if !skip_cached && has_key?(key) + value = SettingAccessors.setting_class.get(key, record) + @temp_settings[key.to_sym] = value unless value.nil? + value + end - # - # Tries to find a setting for this record first. - # If none is found, tries to find a global setting with the same name - # - def get_or_global(key) - fetch_and_store(key, SettingAccessors.setting_class.get(key)) - end + alias_method :get, :[] - # - # Tries to find a setting for this record first, - # if none is found, it will return the given value instead. - # - def get_or_value(key, value) - fetch_and_store(key, value) - end + def has_key?(key) + @temp_settings.has_key?(key.to_sym) + end - def get_with_fallback(key, fallback = nil) - return self[key] if fallback.nil? + # + # Writes a setting's value + # + def []=(key, val) + set_value_was(key) + set_value_before_type_cast(key, val) + @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).convert(val) + end - case fallback.to_s - when 'default' then get_or_default(key) - when 'global' then get_or_global(key) - else get_or_value(key, fallback) + alias_method :set, :[]= + + # + # Tries to find a setting for this record. + # If none is found, will return the default setting value + # specified in the setting accessor call + # + # @param [Boolean] store_default + # If set to +true+, the setting's default value is written to the temporary settings + # for faster access. Otherwise, a database lookup is performed every time. + # + # @param [Boolean] skip_cached + # If set to +true+, a possible temporary setting is skipped when looking up the value. + # This means, the default value is returned even if there is a not yet persisted change to the setting. + # + def get_or_default(key, store_default: true, skip_cached: false) + result = get(key, skip_cached: skip_cached) + return result if result || (has_key?(key) && !skip_cached) + + try_dup(SettingAccessors.setting_class.get_or_default(key, record)).tap do |value| + set(key, value) if store_default + end end - end - # - # @return [String] the setting's value type in the +@record+'s context - # - def value_type(key) - SettingAccessors::Internal.setting_value_type(key, @record) - end + # + # @return [String] the setting's value type in the +record+'s context + # + def value_type(key) + SettingAccessors::Internal.setting_value_type(key, record) + end - #---------------------------------------------------------------- - # ActiveRecord Helper Methods Emulation - #---------------------------------------------------------------- + #---------------------------------------------------------------- + # ActiveRecord Helper Methods Emulation + #---------------------------------------------------------------- + + # + # @return [Object] the value the given setting had after it was last persisted + # + def value_was(key) + lookup_nested_hash(@old_values, key.to_s) + rescue NestedHashKeyNotFoundException + get(key) + end - def value_was(key, fallback = nil) - return SettingAccessors.setting_class.get(key, @record) if fallback.nil? + def value_changed?(key) + get(key) != value_was(key) + end - case fallback.to_s - when 'default' then SettingAccessors.setting_class.get_or_default(key, @record) - when 'global' then SettingAccessors.setting_class.get(key) - else fallback + def value_before_type_cast(key) + lookup_nested_hash(@values_before_type_casts, key.to_s) + rescue NestedHashKeyNotFoundException + get(key) end - end - def value_changed?(key) - self[key] != value_was(key) - end + protected - def value_before_type_cast(key) - SettingAccessors::Internal.lookup_nested_hash(@values_before_type_casts, key.to_s) || self[key] - end + # + # Keeps a record of the originally set value for a setting before it was + # automatically converted. + # + def set_value_before_type_cast(key, value) + @values_before_type_casts ||= {} + @values_before_type_casts[key.to_s] = value + end - protected + # + # Keeps a local copy of a setting's value before it was overridden. + # Once the setting is persisted, this value is cleared. + # + def set_value_was(key) + @old_values ||= {} - # - # Keeps a record of the originally set value for a setting before it was - # automatically converted. - # - def set_value_before_type_cast(key, value) - @values_before_type_casts ||= {} - @values_before_type_casts[key.to_s] = value - end + unless @old_values.key?(key.to_s) + @old_values[key.to_s] = get_or_default(key, skip_cached: true, store_default: false) + end + end - # - # Validates the new setting values. - # If there is an accessor for the setting, the errors will be - # directly forwarded to it, otherwise to :base - # - # Please do not call this method directly, use the IntegrationValidator - # class instead, e.g. - # - # validates_with SettingAccessors::IntegrationValidator - # - def validate! - @temp_settings.each do |key, value| - validation_errors = SettingAccessors.setting_class.validation_errors(key, value, @record) - validation_errors.each do |message| - if @record.respond_to?("#{key}=") - @record.errors.add(key, message) - else - @record.errors.add :base, :invalid_setting, :name => key, :message => message + # + # Validates the new setting values. + # If there is an accessor for the setting, the errors will be + # directly forwarded to it, otherwise to :base + # + # Please do not call this method directly, use the IntegrationValidator + # class instead, e.g. + # + # validates_with SettingAccessors::IntegrationValidator + # + def validate! + @temp_settings.each do |key, value| + validation_errors = SettingAccessors.setting_class.validation_errors(key, value, record) + validation_errors.each do |message| + if record.respond_to?("#{key}=") + record.errors.add(key, message) + else + record.errors.add :base, :invalid_setting, :name => key, :message => message + end end end end - end - # - # Saves the new setting values into the database - # Please note that there is no check if the values changed their - # in the meantime. - # - # Also, this method expects that the settings were validated - # before using #validate! and will therefore not perform - # validations itself. - # - def persist! - @temp_settings.each do |key, value| - Setting.create_or_update(key, value, @record) + # + # @return [Object] the duplicated value if it is in fact duplicable. The actual value otherwise + # + def try_dup(value) + value.duplicable? ? value.dup : value end - flush! - end - def flush! - @temp_settings = {} + # + # Saves the new setting values into the database + # Please note that there is no check if the values changed their + # in the meantime. + # + # Also, this method expects that the settings were validated + # before using #validate! and will therefore not perform + # validations itself. + # + def persist! + @temp_settings.each do |key, value| + Setting.create_or_update(key, value, record) + end + flush! + end + + def flush! + @temp_settings = {} + @values_before_type_casts = {} + @old_values = {} + end end -end +end \ No newline at end of file diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index d33b27d..94951d3 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -23,31 +23,17 @@ module ClassMethods # @param [Hash] options # Options to customize the behaviour of the generated accessor # - # @option options [Symbol, Object] :fallback (nil) - # If set to +:default+, the getter will return the setting's default - # value if no own value was specified for this record - # - # If set to +:global+, the getter will try to find a global - # setting if no record specific setting was found - # - # If set to another value, this value is used by default - # - # If not set at all or to +nil+, the getter will only search for a record specific - # setting and return +nil+ if none was specified previously. - # def setting_accessor(setting_name, options = {}) - fallback = options.delete(:fallback) - SettingAccessors::Internal.set_class_setting(self, setting_name, options) setting_type = SettingAccessors::Internal.setting_value_type(setting_name, self).to_sym - #Add the setting's name to the list of setting_accessors for this class + # Add the setting's name to the list of setting_accessors for this class SettingAccessors::Internal.add_setting_accessor_name(self, setting_name) # Getter define_method(setting_name) do - settings.get_with_fallback(setting_name, fallback) + settings.get_or_default(setting_name) end # Getter alias for boolean settings @@ -60,7 +46,7 @@ def setting_accessor(setting_name, options = {}) #NAME_was define_method("#{setting_name}_was") do - settings.value_was(setting_name, fallback) + settings.value_was(setting_name) end #NAME_before_type_cast @@ -96,19 +82,18 @@ def reload(*) end def as_json(options = {}) - json = super options - - setting_names = SettingAccessors::Internal.setting_accessor_names(self.class) - if only = options[:only] - setting_names &= Array(only).map(&:to_s) - elsif except = options[:except] - setting_names -= Array(except).map(&:to_s) - end + super.tap do |json| + setting_names = SettingAccessors::Internal.setting_accessor_names(self.class) + if options[:only] + setting_names &= Array(options[:only]).map(&:to_s) + elsif options[:except] + setting_names -= Array(options[:except]).map(&:to_s) + end - setting_names.each do |setting_name| - json[setting_name.to_s] = send(setting_name) + setting_names.each do |setting_name| + json[setting_name.to_s] = send(setting_name) + end end - json end def settings diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 0caeac8..69fcd38 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -3,47 +3,7 @@ # module SettingAccessors module Internal - - def self.ensure_nested_hash!(hash, *keys) - h = hash - keys.each do |key| - h[key] ||= {} - h = h[key] - end - end - - def self.lookup_nested_hash(hash, *keys) - return nil if hash.nil? - - h = hash - keys.each do |key| - return nil if h[key].nil? - h = h[key] - end - h - end - - # - # Loads information about all settings from YAML file - # These are cached in the class so they don't have to be reloaded - # every time. - # - # Note: For development / test, this is flushed every time - # - def self.global_config - if Rails.env.test? || Rails.env.development? - (YAML.load(File.open(Rails.root.join('config/settings.yml'))) || {}).deep_stringify_keys - else - @@config ||= (YAML.load(File.open(Rails.root.join('config/settings.yml'))) || {}).deep_stringify_keys - end - end - - # - # @return [TrueClass, FalseClass] +true+ if the setting is defined in config/settings.yml - # - def self.globally_defined_setting?(setting_name) - self.global_config[setting_name.to_s].present? - end + extend Helpers # # Sets a class-specific setting @@ -55,15 +15,10 @@ def self.set_class_setting(klass, setting_name, options = {}) @@class_settings ||= {} #If there are no options given, the setting *has* to be defined globally. - if options.empty? && !self.globally_defined_setting?(setting_name) - raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is neither globally defined nor did it receive options" - - #A setting which is already globally defined, may not be re-defined on class base - elsif self.globally_defined_setting?(setting_name) && options.any? - raise ArgumentError.new("The setting #{setting_name} is already defined in config/settings.yml and may not be redefined in #{klass}") - + if options.empty? + raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." #If the setting is defined on class base, we have to store its options - elsif options.any? && !self.globally_defined_setting?(setting_name) + else self.ensure_nested_hash!(@@class_settings, klass.to_s) @@class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys end From a1ff88a38bccad31349feb0818cd0023f1ba70b0 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:09:57 +0100 Subject: [PATCH 06/60] Slight refactoring in SettingScaffold --- lib/setting_accessors/setting_scaffold.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 140fd55..4012939 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -42,8 +42,8 @@ def [](name, assignable = nil) # the setting's default value is returned. # def get_or_default(name, assignable = nil) - if (val = self[name, assignable]).nil? - self.new(:name => name, :assignable => assignable).default_value + if (val = get(name, assignable)).nil? + new(name: name, assignable: assignable).default_value else val end From a27e64aa04dd52194533ec8353525bd7e5e29667 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:13:38 +0100 Subject: [PATCH 07/60] Bump required ruby version to 2.3 --- setting_accessors.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index 76bfd6b..ef33621 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] - spec.required_ruby_version = ['>= 2.2.2', '< 3'] + spec.required_ruby_version = ['>= 2.3', '< 3'] spec.add_development_dependency "bundler", "~> 1.6" spec.add_development_dependency "rake", "~> 10.4" From 879c8b18e4ea8a0fa669158cb39c51f488a7d77f Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:17:24 +0100 Subject: [PATCH 08/60] Added rubocop to travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57f7f26..af6307a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,14 @@ language: ruby cache: bundler rvm: - - 2.2 - - 2.3.1 + - 2.3.0 - 2.5.0 env: - DISABLE_DATABASE_ENVIRONMENT_CHECK=1 before_install: 'gem install bundler' +before_script: bundle exec rubocop --parallel script: 'bundle exec rake' notifications: From 712f96b14e7b5ead75c2a4a551ec59a31f02b8b8 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:22:11 +0100 Subject: [PATCH 09/60] Leave rubocop to codeclimate? --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index af6307a..f8f1e8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ env: - DISABLE_DATABASE_ENVIRONMENT_CHECK=1 before_install: 'gem install bundler' -before_script: bundle exec rubocop --parallel script: 'bundle exec rake' notifications: From 8881fcac673e8cfb052f8c6ca68fcb49317cc81c Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 19:24:20 +0100 Subject: [PATCH 10/60] Added maintainability badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7f2332b..8b48bda 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ SettingAccessors [![Gem Version](https://badge.fury.io/rb/setting_accessors.svg)](http://badge.fury.io/rb/setting_accessors) [![Build Status](https://travis-ci.org/Stex/setting_accessors.svg?branch=master)](https://travis-ci.org/Stex/setting_accessors) +[![Maintainability](https://api.codeclimate.com/v1/badges/78becd1d005aab2d1409/maintainability)](https://codeclimate.com/github/Stex/setting_accessors/maintainability) Sometimes it's handy to keep track of various settings or attributes in ActiveRecord instances and persist them through various requests (and sessions). An example would be to store a items-per-page value for multiple tables in the application per user or probably also set default sorting directions. From 2a5f2bfeecd4ed1a88b6fa1d104924353705d6d1 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 22:15:03 +0100 Subject: [PATCH 11/60] Added changed settings to changed_attributes fixes #9 --- lib/setting_accessors/accessor.rb | 4 ++++ lib/setting_accessors/integration.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index b95c70a..7d68cef 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -106,6 +106,10 @@ def value_before_type_cast(key) get(key) end + def changed_values + @temp_settings.select { |k, _| value_changed?(k) } + end + protected # diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 94951d3..e367c6f 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -81,6 +81,19 @@ def reload(*) self end + # + # Adds changed settings to ActiveModel's list of changed attributes. + # This is necessary for #changed? to work correctly without actually overriding + # the method itself. + # + # TODO: Check if it makes more sense to hook into AR5's AttributeMutationTracker instead + # + # @return [Hash] All changed attributes + # + def changed_attributes + super.merge(settings.changed_values) + end + def as_json(options = {}) super.tap do |json| setting_names = SettingAccessors::Internal.setting_accessor_names(self.class) From e3dcbbfde07168a8ddadb161e15261ff4ace05aa Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Fri, 23 Nov 2018 22:21:55 +0100 Subject: [PATCH 12/60] removed codeclimate config in favour of web config --- .codeclimate.yml | 66 ------------------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 68337d9..0000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,66 +0,0 @@ ---- -engines: - rubocop: - enabled: true - eslint: - enabled: true - csslint: - enabled: true -ratings: - paths: - - "**.rb" - - "**.js" - - "**.jsx" - - "**.css" -exclude_paths: -- test/**/* -# This is a sample .codeclimate.yml configured for Engine analysis on Code -# Climate Platform. For an overview of the Code Climate Platform, see here: -# http://docs.codeclimate.com/article/300-the-codeclimate-platform - -# Under the engines key, you can configure which engines will analyze your repo. -# Each key is an engine name. For each value, you need to specify enabled: true -# to enable the engine as well as any other engines-specific configuration. - -# For more details, see here: -# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform - -# For a list of all available engines, see here: -# http://docs.codeclimate.com/article/296-engines-available-engines - -engines: -# to turn on an engine, add it here and set enabled to `true` -# to turn off an engine, set enabled to `false` or remove it - rubocop: - enabled: true - golint: - enabled: true - gofmt: - enabled: true - eslint: - enabled: true - csslint: - enabled: true - -# Engines can analyze files and report issues on them, but you can separately -# decide which files will receive ratings based on those issues. This is -# specified by path patterns under the ratings key. - -# For more details see here: -# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform - -# Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard. - -# ratings: -# paths: -# - app/** -# - lib/** -# - "**.rb" -# - "**.go" - -# You can globally exclude files from being analyzed by any engine using the -# exclude_paths key. - -#exclude_paths: -#- spec/**/* -#- vendor/**/* From e0f3041d7cfc3aae717d238146ee25095c2082c3 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 15:22:54 +0100 Subject: [PATCH 13/60] Delete .codeclimate.yml --- .codeclimate.yml | 66 ------------------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 68337d9..0000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,66 +0,0 @@ ---- -engines: - rubocop: - enabled: true - eslint: - enabled: true - csslint: - enabled: true -ratings: - paths: - - "**.rb" - - "**.js" - - "**.jsx" - - "**.css" -exclude_paths: -- test/**/* -# This is a sample .codeclimate.yml configured for Engine analysis on Code -# Climate Platform. For an overview of the Code Climate Platform, see here: -# http://docs.codeclimate.com/article/300-the-codeclimate-platform - -# Under the engines key, you can configure which engines will analyze your repo. -# Each key is an engine name. For each value, you need to specify enabled: true -# to enable the engine as well as any other engines-specific configuration. - -# For more details, see here: -# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform - -# For a list of all available engines, see here: -# http://docs.codeclimate.com/article/296-engines-available-engines - -engines: -# to turn on an engine, add it here and set enabled to `true` -# to turn off an engine, set enabled to `false` or remove it - rubocop: - enabled: true - golint: - enabled: true - gofmt: - enabled: true - eslint: - enabled: true - csslint: - enabled: true - -# Engines can analyze files and report issues on them, but you can separately -# decide which files will receive ratings based on those issues. This is -# specified by path patterns under the ratings key. - -# For more details see here: -# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform - -# Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard. - -# ratings: -# paths: -# - app/** -# - lib/** -# - "**.rb" -# - "**.go" - -# You can globally exclude files from being analyzed by any engine using the -# exclude_paths key. - -#exclude_paths: -#- spec/**/* -#- vendor/**/* From 9bb48b2a0746cdc1a8443928a873d3d004e56025 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 15:45:41 +0100 Subject: [PATCH 14/60] extended .rubocop.yml --- .rubocop.yml | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 85c948b..e427fb0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,113 @@ AllCops: TargetRailsVersion: 5.0 - TargetRubyVersion: 2.5 \ No newline at end of file + TargetRubyVersion: 2.3 + +#--------------------------------------------- +# Layout +#--------------------------------------------- + +# Hashes do not need padding +Layout/SpaceInsideHashLiteralBraces: + Enabled: false + +# Allow 2 space indentation for when inside a case +Layout/CaseIndentation: + Enabled: false + +# Allow empty lines in classes +Layout/EmptyLinesAroundClassBody: + Enabled: false + +# Allow multiple spaces before first argument +Layout/SpaceBeforeFirstArg: + Enabled: false + +# Allow extra spacing, e.g. to align components +Layout/ExtraSpacing: + Enabled: false + +# Usually good, but in some cases not possible +Layout/AlignHash: + Enabled: false + +# Allow an empty line after do / before end +Layout/EmptyLinesAroundBlockBody: + Enabled: false + +# Again, generally a good idea, but it has problems with multiline operations in +# combination with assignments +Layout/MultilineOperationIndentation: + Enabled: false + +# See the corresponding other cops +Layout/EmptyLinesAroundModuleBody: + Enabled: false + +Layout/SpaceInLambdaLiteral: + Enabled: false + +#--------------------------------------------- +# Metrics +#--------------------------------------------- + +# Allow bigger classes +Metrics/ClassLength: + Enabled: false + +Metrics/LineLength: + Max: 120 + + # To make it possible to copy or click on URIs in the code, we allow lines + # containing a URI to be longer than Max. + AllowHeredoc: true + AllowURI: true + +Metrics/BlockLength: + Max: 75 + +# Allow longer methods +Metrics/MethodLength: + Enabled: false + +# Allow bigger modules +Metrics/ModuleLength: + Enabled: false + +#--------------------------------------------- +# Style +#--------------------------------------------- + +# Allow fail() for initial exception, raise() for re-raise +# It seems that the cop decision was mainly based on "more people use raise than fail"... +Style/SignalException: + Enabled: false + +# Allow assigning multiple variables in one line. +# This should not be overused, but comes in handy when assigning initializer values to instance variables +Style/ParallelAssignment: + Enabled: false + +# Depending on the situation, it might make more sense to use +# [:symbol1, :symbol2] over %i[symbol1 symbol2], e.g. for multiline aligning reasons. +Style/SymbolArray: + Enabled: false + +# Not all modules have to have top level comments +Style/Documentation: + Enabled: false + +# Allow class variable usage +Style/ClassVars: + Enabled: false + +# Allow block comments +Style/BlockComments: + Enabled: false + +# Allow the use of !! (conversion of nil/object to true/false) +Style/DoubleNegation: + Enabled: false + +# Allow unless/if blocks even for one-liners +Style/IfUnlessModifier: + Enabled: false \ No newline at end of file From 5be54afd23e019ced1d2201b905ce8bc8ded7f01 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 18:38:22 +0100 Subject: [PATCH 15/60] Moved method_missing from actual settings model to SettingScaffold --- .../setting_accessors/templates/model.rb.erb | 24 ------ lib/setting_accessors/setting_scaffold.rb | 74 +++++++++---------- spec/support/setting_model.rb | 24 ------ 3 files changed, 36 insertions(+), 86 deletions(-) diff --git a/lib/generators/setting_accessors/templates/model.rb.erb b/lib/generators/setting_accessors/templates/model.rb.erb index 78bfa1b..aa7ab94 100644 --- a/lib/generators/setting_accessors/templates/model.rb.erb +++ b/lib/generators/setting_accessors/templates/model.rb.erb @@ -20,28 +20,4 @@ class <%= model_name.camelize %> < ActiveRecord::Base belongs_to :assignable, :polymorphic => true include SettingAccessors::SettingScaffold - - # - # Makes accessing settings a little easier. - # Examples: - # - # #Loading **the value** of a global setting named "my_setting" - # Setting.my_setting - # - # #Setting **the value** of a global setting named "my_setting" - # Setting.my_setting = [1,2,3,4,5] - # - # #Loading **the value** of an assigned setting named "cool_setting" - # #+some_cool_user+ is here an instance of ActiveRecord::Base - # Setting.cool_setting(some_cool_user) - # - def self.method_missing(method, *args) - super(method, *args) if args.size > 1 - method_name = method.to_s - if method_name.last == '=' - self.create_or_update(method_name[0..method_name.length-2], args.first, nil, true) - else - self.get(method_name, args.first) - end - end end \ No newline at end of file diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 4012939..c125f46 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -31,17 +31,19 @@ module ClassMethods # If a setting is found, **its value** is returned. # If not, +nil+ is returned. # - def [](name, assignable = nil) + def get(name, assignable = nil) self.setting_record(name, assignable).try(:value) end - alias_method :get, :[] + alias_method :[], :get # # Tries to look the setting up using #get, if no existing setting is found, # the setting's default value is returned. # - def get_or_default(name, assignable = nil) + # This only works for class-wise settings, meaning that an assignable has to be present. + # + def get_or_default(name, assignable) if (val = get(name, assignable)).nil? new(name: name, assignable: assignable).default_value else @@ -65,47 +67,18 @@ def get_or_default(name, assignable = nil) # @param [Boolean] return_value # If set to +true+, only the setting's value is returned # - # @return [Object, Setting] - # Depending on +return_value+ either the newly created Setting record - # or the newly assigned value. - # This is due to the fact that Setting.my_setting = 'something' should - # show the same behaviour as other attribute assigns in the system while - # you might still want to get validation errors on custom settings. - # - # Please note that - if +return_value+ is set to +true+, - # #save! is used instead of #save to ensure that validation - # errors are noticed by the programmer / user. - # As this is usually only the case when coming from method_missing, - # it should not happen anyways + # @return [Object] The newly set value # # @toto: Bless the rains down in Africa! # - def create_or_update(name, value, assignable = nil, return_value = false) - setting = self.setting_record(name, assignable) - setting ||= self.new(:name => name, :assignable => assignable) - setting.set_value(value) - - if return_value - setting.save! - setting.value - else + def set(name, value, assignable: nil) + self.setting_record(name, assignable) || new(name: name, assignable: assignable).tap do |setting| + setting.set_value(value) setting.save - setting - end + end.value end - # - # Shortcut for #create_or_update - # - # @param [String, Symbol] name - # The setting's name - # - # The second argument is an optional assignable - # - def []=(name, *args) - assignable = args.size > 1 ? args.first : nil - self.create_or_update(name, args.last, assignable, true) - end + alias_method :[]=, :set # # Creates a new setting for the given name and assignable, @@ -161,6 +134,31 @@ def validation_errors(name, value, assignable = nil) s.valid? s.errors[:value] || [] end + + # + # Makes accessing settings a little easier. + # Examples: + # + # #Loading **the value** of a global setting named "my_setting" + # Setting.my_setting + # + # #Setting **the value** of a global setting named "my_setting" + # Setting.my_setting = [1,2,3,4,5] + # + # #Loading **the value** of an assigned setting named "cool_setting" + # #+some_cool_user+ is here an instance of ActiveRecord::Base + # Setting.cool_setting(some_cool_user) + # + def method_missing(method, *args) + method_name = method.to_s + + if method_name.last == '=' + set(method_name[0..-2], args.first) + else + return super(method, *args) if args.size > 1 + get(method_name, args.first) + end + end end # diff --git a/spec/support/setting_model.rb b/spec/support/setting_model.rb index 6dee97f..b7ed8ad 100644 --- a/spec/support/setting_model.rb +++ b/spec/support/setting_model.rb @@ -24,30 +24,6 @@ def self.included(base) presence: true validates_with SettingAccessors::Validator - - # - # Makes accessing settings a little easier. - # Examples: - # - # #Loading **the value** of a global setting named "my_setting" - # Setting.my_setting - # - # #Setting **the value** of a global setting named "my_setting" - # Setting.my_setting = [1,2,3,4,5] - # - # #Loading **the value** of an assigned setting named "cool_setting" - # #+some_cool_user+ is here an instance of ActiveRecord::Base - # Setting.cool_setting(some_cool_user) - # - def self.method_missing(method, *args) - super(method, *args) if args.size > 1 - method_name = method.to_s - if method_name.last == '=' - self.create_or_update(method_name[0..method_name.length - 2], args.first, nil, true) - else - self.get(method_name, args.first) - end - end end end end From 3941611af67fdd707eabca9465a964d6221e3717 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 18:39:19 +0100 Subject: [PATCH 16/60] Started setting_scaffold_spec, renamed Setting.create_or_update to Setting.set --- lib/setting_accessors/accessor.rb | 2 +- lib/setting_accessors/internal.rb | 23 +- .../setting_scaffold_spec.rb | 264 ++++++++++++++++++ 3 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 spec/lib/setting_accessors/setting_scaffold_spec.rb diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index 7d68cef..19b61d5 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -174,7 +174,7 @@ def try_dup(value) # def persist! @temp_settings.each do |key, value| - Setting.create_or_update(key, value, record) + Setting.set(key, value, assignable: record) end flush! end diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 69fcd38..1e73ec6 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -5,6 +5,12 @@ module SettingAccessors module Internal extend Helpers + class << self + def class_settings + @@class_settings ||= {} + end + end + # # Sets a class-specific setting # For global settings, this is done in config/settings.yml @@ -12,15 +18,13 @@ module Internal # by using setting_accessor in your model class # def self.set_class_setting(klass, setting_name, options = {}) - @@class_settings ||= {} - #If there are no options given, the setting *has* to be defined globally. if options.empty? raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." #If the setting is defined on class base, we have to store its options else - self.ensure_nested_hash!(@@class_settings, klass.to_s) - @@class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys + self.ensure_nested_hash!(class_settings, klass.to_s) + class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys end end @@ -35,10 +39,7 @@ def self.setting_data(setting_name, assignable_class = nil) # As a convenience function (and to keep the existing code working), # it is possible to provide a class or an instance of said class assignable_class &&= assignable_class.class unless assignable_class.is_a?(Class) - - (assignable_class && self.get_class_setting(assignable_class, setting_name)) || - self.global_config[setting_name.to_s] || - {} + (assignable_class && self.get_class_setting(assignable_class, setting_name)) || {} end # @@ -57,10 +58,12 @@ def self.converter(value_type) end # - # @return [Hash, NilClass] Information about a class specific setting or +nil+ if it wasn't set before + # @return [Hash, nil] Information about a class specific setting or +nil+ if it wasn't set before # def self.get_class_setting(klass, setting_name) - self.lookup_nested_hash(@@class_settings, klass.to_s, setting_name.to_s) + lookup_nested_hash(class_settings, klass.to_s, setting_name.to_s) + rescue ::SettingAccessors::Helpers::NestedHashKeyNotFoundException + nil end # diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb new file mode 100644 index 0000000..e3989df --- /dev/null +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -0,0 +1,264 @@ +# frozen_string_literal: true + +describe SettingAccessors::SettingScaffold, type: :model do + include SettingModel + + with_model 'User' + + #---------------------------------------------------------------- + # .method_missing + #---------------------------------------------------------------- + + describe 'method_missing' do + context 'when a possible getter is being called' do + context 'with an additional argument (assignable)' do + let(:assignable) { User.create } + + it 'forwards the call to Setting.get' do + expect(Setting).to receive(:get).with('foo', assignable) + Setting.foo(assignable) + end + end + + context 'with no additional arguments' do + it 'forwards the call to Setting.get' do + expect(Setting).to receive(:get).with('foo', nil) + Setting.foo + end + end + + context 'with more than one argument' do + it 'calls the original method_missing, leading to a NoMethodError' do + expect { Setting.foo('bar', 'baz') }.to raise_error NoMethodError + end + end + end + + context 'when a setter is being called' do + context 'with the new value as its argument' do + it 'forwards the call to Setting.create_or_update' do + expect(Setting).to receive(:set).with('foo', 'bar') + Setting.foo = 'bar' + end + end + end + end + + #---------------------------------------------------------------- + # .get + #---------------------------------------------------------------- + + describe '.get' do + context 'when being called without an assignable' do + context 'and no setting with that name exists yet' do + it 'returns nil' do + expect(Setting.get('foo')).to be nil + end + end + + context 'and a setting with that name already exists' do + it "returns the setting's value" do + Setting.create(name: 'foo', value: 'bar') + expect(Setting.get('foo')).to eql 'bar' + end + end + end + + context 'when being called with an assignable' do + let(:assignable) { User.create } + subject { Setting.get('foo', assignable) } + + before(:each) do + Setting.create(name: 'foo', value: 'bar') + Setting.create(name: 'foo', value: 'baz', assignable: User.create) + end + + context 'and no assigned setting with that name exists yet' do + it { is_expected.to be nil } + end + + context 'and an assigned setting with that name already exists' do + before(:each) { Setting.create(name: 'foo', value: 'BAM', assignable: assignable) } + + it { is_expected.to eql 'BAM' } + end + end + end + + #---------------------------------------------------------------- + # .set + #---------------------------------------------------------------- + + describe '.set' do + context 'when being called without an assignable' do + context 'and no corresponding setting already exists' do + it 'creates a new setting with the given value' do + + end + end + + context 'and a corresponding setting already exists' do + + end + end + + context 'when being called with an assignable' do + + end + end + + #---------------------------------------------------------------- + # .[]= + #---------------------------------------------------------------- + + describe '.[]=' do + + end + +end + + + +# require_relative '../../../test_helper' +# +# class SettingTest < ActiveSupport::TestCase +# should validate_presence_of :name +# +# #FIXME: This is currently setting assignable_type and assignable_id to "0" resp. "1" which breaks everything. +# # should validate_uniqueness_of(:name).scoped_to([:assignable_type, :assignable_id]) +# +# context 'Global Setting Accessors (method missing)' do +# +# should 'ignore a setting function if more than 1 argument is given' do +# assert_raises(NoMethodError) { Setting.my_setting(1, 2, 3) } +# end +# +# should 'return nil for a non-existing setting' do +# assert_nil Setting.gotta_catch_em_all +# end +# +# should 'create a new setting if necessary' do +# assert Setting.count.zero? +# assert Setting.gotta_catch_em_all = 'Pokemon!' +# assert Setting.count == 1 +# end +# +# should "return the setting's value for an existing setting" do +# assert Setting.gotta_catch_em_all = 'Pokemon!' +# assert_equal Setting.gotta_catch_em_all, 'Pokemon!' +# end +# +# should 'update an existing setting instead of creating a new one' do +# assert Setting.count.zero? +# assert Setting.gotta_catch_em_all = 'Pokemon!' +# assert Setting.gotta_catch_em_all = 'Pokemon!' +# assert Setting.count == 1 +# end +# +# should 'return assignable specific settings if an assignable is given' do +# ash = User.create(:first_name => 'Ash', :last_name => 'Ketchum') +# gary = User.create(:first_name => 'Gary', :last_name => 'Oak') +# team_rocket = User.create(:first_name => 'Jessie', :last_name => 'James') +# +# assert Setting.create_or_update(:pokedex_count, 151, ash) +# assert Setting.create_or_update(:pokedex_count, 1, gary) +# +# assert_nil Setting.pokedex_count +# +# assert_equal Setting.pokedex_count(ash), 151 +# assert_equal Setting.pokedex_count(gary), 1 +# +# #They don't want to be on file. +# assert_nil Setting.pokedex_count(team_rocket) +# end +# end +# +# context 'The create_or_update function' do +# setup do +# @ash = User.create(:first_name => 'Ash', :last_name => 'Ketchum') +# end +# +# should 'create a new assigned setting if it did not exist before' do +# assert Setting.count.zero? +# Setting.create_or_update(:pokedex_count, 151, @ash) +# assert Setting.count == 1 +# end +# +# should 'update an assigned setting if it already exists' do +# Setting.create_or_update(:pokedex_count, 150, @ash) +# assert Setting.count == 1 +# Setting.create_or_update(:pokedex_count, 151, @ash) +# assert Setting.count == 1 +# assert_equal Setting.pokedex_count(@ash), 151 +# end +# +# should 'return a setting object by default' do +# assert_instance_of Setting, Setting.create_or_update(:pokedex_count, 151, @ash) +# end +# +# should 'return just the value if wished' do +# assert_equal Setting.create_or_update(:pokedex_count, 151, @ash, true), 151 +# end +# end +# +# context 'The globally_defined method' do +# should 'return true for a setting which is defined in config/settings.yml' do +# assert SettingAccessors::Internal.globally_defined_setting?('a_string') +# end +# +# should 'return false for a setting which is not defined in config/settings.yml' do +# assert !SettingAccessors::Internal.globally_defined_setting?('something_different') +# end +# end +# +# context 'setting_accessors' do +# should 'return the default value as fallback if :fallback => :default is given' do +# assert_equal 'I am a string!', User.new.a_string +# end +# +# should 'return the global setting value as fallback if :fallback => :global is given' do +# Setting.a_number = 42 +# assert_equal 42, User.new.a_number +# end +# +# should 'return the given value as fallback if :fallback => VALUE is given' do +# assert_equal false, User.new.a_boolean +# end +# end +# +# context 'Class-Defined Settings' do +# should 'have the type defined in the setting_accessor call' do +# assert u = User.create(:first_name => 'a', :last_name => 'name', :locale => 'de') +# assert s = Setting.setting_record(:locale, User.first) +# assert_equal 'string', s.value_type.to_s +# end +# +# should 'not override global settings' do +# assert_raises(ArgumentError) do +# User.class_eval do +# setting_accessor :a_string, :type => :integer +# end +# end +# end +# +# should 'use value fallbacks' do +# assert_equal 'Oiski Poiski!', User.new.class_wise_with_value_fallback +# end +# +# should 'use default fallbacks' do +# assert_equal 'Kapitanski', User.new.class_wise_with_default_fallback +# end +# end +# +# context 'Boolean settings' do +# should 'respect temporary settings which are set to `false`' do +# user = User.new(:class_wise_truthy_boolean => false) +# assert_equal user.class_wise_truthy_boolean, false +# end +# +# should 'respect temporary settings which are set to `true`' do +# user = User.new(:a_boolean => true) +# assert_equal user.a_boolean, true +# end +# end +# end From 4a6210324cf3bdf772894b41660c01a890f10006 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 22:34:31 +0100 Subject: [PATCH 17/60] Added more ruby versions to travis config --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f8f1e8d..e5b2292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,11 @@ cache: bundler rvm: - 2.3.0 + - 2.3.3 + - 2.4.0 + - 2.4.4 - 2.5.0 - -env: - - DISABLE_DATABASE_ENVIRONMENT_CHECK=1 + - 2.5.3 before_install: 'gem install bundler' script: 'bundle exec rake' From a9557612796b39d6235d79df5c7f0dc83c992098 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 22:49:17 +0100 Subject: [PATCH 18/60] Added more SettingScaffold class method tests --- .travis.yml | 1 - lib/setting_accessors/setting_scaffold.rb | 28 +-- .../setting_scaffold_spec.rb | 177 +++--------------- 3 files changed, 25 insertions(+), 181 deletions(-) diff --git a/.travis.yml b/.travis.yml index e5b2292..604b8c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,4 +18,3 @@ notifications: - stex@sterex.de on_failure: change on_success: never - diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index c125f46..bac949c 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -72,7 +72,7 @@ def get_or_default(name, assignable) # @toto: Bless the rains down in Africa! # def set(name, value, assignable: nil) - self.setting_record(name, assignable) || new(name: name, assignable: assignable).tap do |setting| + (setting_record(name, assignable) || new(name: name, assignable: assignable)).tap do |setting| setting.set_value(value) setting.save end.value @@ -80,28 +80,6 @@ def set(name, value, assignable: nil) alias_method :[]=, :set - # - # Creates a new setting for the given name and assignable, - # using the setting's default value stored in the config file - # - # If the setting already exists, its value will not be overridden - # - # @param [String] name - # The setting's name - # - # @param [ActiveRecord::Base] assignable - # An optional assignable - # - # @example Create a global default setting 'meaning_of_life' - # Setting.create_default_setting(:meaning_of_life) - # - # @example Create a default setting for all users in the system - # User.all.each { |u| Setting.create_default_setting(:some_setting, u) } - # - def create_default_setting(name, assignable = nil) - self.create_or_update(name, self.get_or_default(name, assignable), assignable) - end - # # @return [Object] the default value for the given setting # @@ -149,13 +127,13 @@ def validation_errors(name, value, assignable = nil) # #+some_cool_user+ is here an instance of ActiveRecord::Base # Setting.cool_setting(some_cool_user) # - def method_missing(method, *args) + def method_missing(method, *args, &block) method_name = method.to_s if method_name.last == '=' set(method_name[0..-2], args.first) else - return super(method, *args) if args.size > 1 + return super(method, *args, &block) if args.size > 1 get(method_name, args.first) end end diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb index e3989df..1077720 100644 --- a/spec/lib/setting_accessors/setting_scaffold_spec.rb +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -93,172 +93,39 @@ context 'when being called without an assignable' do context 'and no corresponding setting already exists' do it 'creates a new setting with the given value' do - + expect { Setting.set('foo', 'bar') }.to change { Setting.count }.from(0).to(1) + expect(Setting.last.value).to eql 'bar' end end context 'and a corresponding setting already exists' do + let!(:setting) { Setting.create!(name: 'foo', value: 'bar') } + it 'updates the existing setting' do + expect { Setting.set('foo', 'baz') }.not_to change { Setting.count } + expect(setting.reload.value).to eql 'baz' + end end end context 'when being called with an assignable' do + let(:assignable) { User.create } - end - end - - #---------------------------------------------------------------- - # .[]= - #---------------------------------------------------------------- + context 'and no corresponding setting already exists' do + it 'creates a new setting with the given value and assignable' do + expect { Setting.set('foo', 'bar', assignable: assignable) }.to change { Setting.count }.from(0).to(1) + expect(Setting.last).to have_attributes(assignable: assignable, value: 'bar') + end + end - describe '.[]=' do + context 'and a corresponding setting already exists' do + let!(:setting) { Setting.create!(name: 'foo', value: 'bar', assignable: assignable) } + it 'updates the existing setting' do + expect { Setting.set('foo', 'baz', assignable: assignable) }.not_to change { Setting.count } + expect(setting.reload.value).to eql 'baz' + end + end + end end - end - - - -# require_relative '../../../test_helper' -# -# class SettingTest < ActiveSupport::TestCase -# should validate_presence_of :name -# -# #FIXME: This is currently setting assignable_type and assignable_id to "0" resp. "1" which breaks everything. -# # should validate_uniqueness_of(:name).scoped_to([:assignable_type, :assignable_id]) -# -# context 'Global Setting Accessors (method missing)' do -# -# should 'ignore a setting function if more than 1 argument is given' do -# assert_raises(NoMethodError) { Setting.my_setting(1, 2, 3) } -# end -# -# should 'return nil for a non-existing setting' do -# assert_nil Setting.gotta_catch_em_all -# end -# -# should 'create a new setting if necessary' do -# assert Setting.count.zero? -# assert Setting.gotta_catch_em_all = 'Pokemon!' -# assert Setting.count == 1 -# end -# -# should "return the setting's value for an existing setting" do -# assert Setting.gotta_catch_em_all = 'Pokemon!' -# assert_equal Setting.gotta_catch_em_all, 'Pokemon!' -# end -# -# should 'update an existing setting instead of creating a new one' do -# assert Setting.count.zero? -# assert Setting.gotta_catch_em_all = 'Pokemon!' -# assert Setting.gotta_catch_em_all = 'Pokemon!' -# assert Setting.count == 1 -# end -# -# should 'return assignable specific settings if an assignable is given' do -# ash = User.create(:first_name => 'Ash', :last_name => 'Ketchum') -# gary = User.create(:first_name => 'Gary', :last_name => 'Oak') -# team_rocket = User.create(:first_name => 'Jessie', :last_name => 'James') -# -# assert Setting.create_or_update(:pokedex_count, 151, ash) -# assert Setting.create_or_update(:pokedex_count, 1, gary) -# -# assert_nil Setting.pokedex_count -# -# assert_equal Setting.pokedex_count(ash), 151 -# assert_equal Setting.pokedex_count(gary), 1 -# -# #They don't want to be on file. -# assert_nil Setting.pokedex_count(team_rocket) -# end -# end -# -# context 'The create_or_update function' do -# setup do -# @ash = User.create(:first_name => 'Ash', :last_name => 'Ketchum') -# end -# -# should 'create a new assigned setting if it did not exist before' do -# assert Setting.count.zero? -# Setting.create_or_update(:pokedex_count, 151, @ash) -# assert Setting.count == 1 -# end -# -# should 'update an assigned setting if it already exists' do -# Setting.create_or_update(:pokedex_count, 150, @ash) -# assert Setting.count == 1 -# Setting.create_or_update(:pokedex_count, 151, @ash) -# assert Setting.count == 1 -# assert_equal Setting.pokedex_count(@ash), 151 -# end -# -# should 'return a setting object by default' do -# assert_instance_of Setting, Setting.create_or_update(:pokedex_count, 151, @ash) -# end -# -# should 'return just the value if wished' do -# assert_equal Setting.create_or_update(:pokedex_count, 151, @ash, true), 151 -# end -# end -# -# context 'The globally_defined method' do -# should 'return true for a setting which is defined in config/settings.yml' do -# assert SettingAccessors::Internal.globally_defined_setting?('a_string') -# end -# -# should 'return false for a setting which is not defined in config/settings.yml' do -# assert !SettingAccessors::Internal.globally_defined_setting?('something_different') -# end -# end -# -# context 'setting_accessors' do -# should 'return the default value as fallback if :fallback => :default is given' do -# assert_equal 'I am a string!', User.new.a_string -# end -# -# should 'return the global setting value as fallback if :fallback => :global is given' do -# Setting.a_number = 42 -# assert_equal 42, User.new.a_number -# end -# -# should 'return the given value as fallback if :fallback => VALUE is given' do -# assert_equal false, User.new.a_boolean -# end -# end -# -# context 'Class-Defined Settings' do -# should 'have the type defined in the setting_accessor call' do -# assert u = User.create(:first_name => 'a', :last_name => 'name', :locale => 'de') -# assert s = Setting.setting_record(:locale, User.first) -# assert_equal 'string', s.value_type.to_s -# end -# -# should 'not override global settings' do -# assert_raises(ArgumentError) do -# User.class_eval do -# setting_accessor :a_string, :type => :integer -# end -# end -# end -# -# should 'use value fallbacks' do -# assert_equal 'Oiski Poiski!', User.new.class_wise_with_value_fallback -# end -# -# should 'use default fallbacks' do -# assert_equal 'Kapitanski', User.new.class_wise_with_default_fallback -# end -# end -# -# context 'Boolean settings' do -# should 'respect temporary settings which are set to `false`' do -# user = User.new(:class_wise_truthy_boolean => false) -# assert_equal user.class_wise_truthy_boolean, false -# end -# -# should 'respect temporary settings which are set to `true`' do -# user = User.new(:a_boolean => true) -# assert_equal user.a_boolean, true -# end -# end -# end From 1877b713c4fe1208acc31f1adee15eebacc51d1b Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 22:51:12 +0100 Subject: [PATCH 19/60] rubocop: Style/HashSyntax --- lib/generators/setting_accessors/install_generator.rb | 2 +- lib/setting_accessors/accessor.rb | 2 +- lib/setting_accessors/setting_scaffold.rb | 10 +++++----- spec/support/setting_model.rb | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/generators/setting_accessors/install_generator.rb b/lib/generators/setting_accessors/install_generator.rb index d4969b3..f63d919 100644 --- a/lib/generators/setting_accessors/install_generator.rb +++ b/lib/generators/setting_accessors/install_generator.rb @@ -5,7 +5,7 @@ class InstallGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) - argument :model_name, :type => :string, :default => 'Setting' + argument :model_name, type: :string, default: 'Setting' def self.next_migration_number(path) if @prev_migration_nr diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index 19b61d5..6d8282f 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -150,7 +150,7 @@ def validate! if record.respond_to?("#{key}=") record.errors.add(key, message) else - record.errors.add :base, :invalid_setting, :name => key, :message => message + record.errors.add :base, :invalid_setting, name: key, message: message end end end diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index bac949c..969d7d7 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -12,8 +12,8 @@ def self.included(base) base.serialize :value base.validates :name, - :uniqueness => {:scope => [:assignable_type, :assignable_id]}, - :presence => true + uniqueness: {scope: [:assignable_type, :assignable_id]}, + presence: true end module ClassMethods @@ -84,7 +84,7 @@ def set(name, value, assignable: nil) # @return [Object] the default value for the given setting # def get_default_value(name, assignable = nil) - self.new(:name => name, :assignable => assignable).default_value + self.new(name: name, assignable: assignable).default_value end # @@ -95,7 +95,7 @@ def get_default_value(name, assignable = nil) # @return [Setting, NilClass] The found setting or nil if not existing # def setting_record(name, assignable = nil) - self.find_by(:name => name.to_s, :assignable => assignable) + self.find_by(name: name.to_s, assignable: assignable) end # @@ -108,7 +108,7 @@ def setting_record(name, assignable = nil) # @return [Array] The validation errors for the setting's value # def validation_errors(name, value, assignable = nil) - s = self.new(:name => name, :value => value, :assignable => assignable) + s = self.new(name: name, value: value, assignable: assignable) s.valid? s.errors[:value] || [] end diff --git a/spec/support/setting_model.rb b/spec/support/setting_model.rb index b7ed8ad..9cc6f80 100644 --- a/spec/support/setting_model.rb +++ b/spec/support/setting_model.rb @@ -11,7 +11,7 @@ def self.included(base) end model do - belongs_to :assignable, :polymorphic => true + belongs_to :assignable, polymorphic: true serialize :value include SettingAccessors::SettingScaffold From 8c634e479f398bc3915ae5af008e9060ac418321 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 22:52:34 +0100 Subject: [PATCH 20/60] rubocop: Style/FrozenStringLiteralComment --- Gemfile | 2 ++ lib/generators/setting_accessors/install_generator.rb | 2 ++ lib/setting_accessors.rb | 2 ++ lib/setting_accessors/accessor.rb | 2 ++ lib/setting_accessors/converter.rb | 2 ++ lib/setting_accessors/integration.rb | 2 ++ lib/setting_accessors/integration_validator.rb | 2 ++ lib/setting_accessors/internal.rb | 2 ++ lib/setting_accessors/setting_scaffold.rb | 2 ++ lib/setting_accessors/validator.rb | 2 ++ lib/setting_accessors/version.rb | 2 ++ lib/tasks/setting_accessors_tasks.rake | 2 ++ setting_accessors.gemspec | 2 ++ spec/spec_helper.rb | 2 ++ 14 files changed, 28 insertions(+) diff --git a/Gemfile b/Gemfile index 3b4f662..fb06592 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in setting_accessors.gemspec diff --git a/lib/generators/setting_accessors/install_generator.rb b/lib/generators/setting_accessors/install_generator.rb index f63d919..a153789 100644 --- a/lib/generators/setting_accessors/install_generator.rb +++ b/lib/generators/setting_accessors/install_generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SettingAccessors module Generators class InstallGenerator < Rails::Generators::Base diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 7bafbee..6758aa2 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'setting_accessors/version' require 'setting_accessors/helpers' require 'setting_accessors/accessor' diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index 6d8282f..f5f1902 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # Helper class to make accessing record specific settings easier # diff --git a/lib/setting_accessors/converter.rb b/lib/setting_accessors/converter.rb index 1987397..bf864b6 100644 --- a/lib/setting_accessors/converter.rb +++ b/lib/setting_accessors/converter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # This class hopefully will hopefully one day mimic ActiveRecord's # attribute assigning methods, meaning that a conversion to the column type diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index e367c6f..2a5097f 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SettingAccessors::Integration def self.included(base) base.validates_with SettingAccessors::IntegrationValidator diff --git a/lib/setting_accessors/integration_validator.rb b/lib/setting_accessors/integration_validator.rb index f031ab0..5d1c95c 100644 --- a/lib/setting_accessors/integration_validator.rb +++ b/lib/setting_accessors/integration_validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # This class handles model validations for assigned records, e.g. # if the settings are accessed using the Accessor class in this module. diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 1e73ec6..aa8a5ac 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # This module contains class methods used internally. # diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 969d7d7..19cf77c 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # Helper methods for the chosen setting model # They are in this module to leave the end developer some room for diff --git a/lib/setting_accessors/validator.rb b/lib/setting_accessors/validator.rb index dee66d7..0c1c7ba 100644 --- a/lib/setting_accessors/validator.rb +++ b/lib/setting_accessors/validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SettingAccessors::Validator < ActiveModel::Validator def validate(record) diff --git a/lib/setting_accessors/version.rb b/lib/setting_accessors/version.rb index e71e567..0d34498 100644 --- a/lib/setting_accessors/version.rb +++ b/lib/setting_accessors/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SettingAccessors VERSION = '0.3.0'.freeze end diff --git a/lib/tasks/setting_accessors_tasks.rake b/lib/tasks/setting_accessors_tasks.rake index a9760aa..4be937a 100644 --- a/lib/tasks/setting_accessors_tasks.rake +++ b/lib/tasks/setting_accessors_tasks.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # desc "Explaining what the task does" # task :setting_accessors do # # Task goes here diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index ef33621..e304f29 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -1,4 +1,6 @@ # coding: utf-8 +# frozen_string_literal: true + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'setting_accessors/version' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f571158..7422209 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause From 6af494a3f5330e09aa3a3617e55984955924c508 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 22:53:31 +0100 Subject: [PATCH 21/60] rubocop: Style/RedundantSelf --- lib/setting_accessors.rb | 2 +- lib/setting_accessors/internal.rb | 8 ++++---- lib/setting_accessors/setting_scaffold.rb | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 6758aa2..6d49d52 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -16,7 +16,7 @@ module SettingAccessors def self.setting_class - self.setting_class_name.constantize + setting_class_name.constantize end def self.setting_class=(klass) diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index aa8a5ac..00202ed 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -25,7 +25,7 @@ def self.set_class_setting(klass, setting_name, options = {}) raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." #If the setting is defined on class base, we have to store its options else - self.ensure_nested_hash!(class_settings, klass.to_s) + ensure_nested_hash!(class_settings, klass.to_s) class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys end end @@ -41,14 +41,14 @@ def self.setting_data(setting_name, assignable_class = nil) # As a convenience function (and to keep the existing code working), # it is possible to provide a class or an instance of said class assignable_class &&= assignable_class.class unless assignable_class.is_a?(Class) - (assignable_class && self.get_class_setting(assignable_class, setting_name)) || {} + (assignable_class && get_class_setting(assignable_class, setting_name)) || {} end # # @return [String] the given setting's value type # def self.setting_value_type(*args) - self.setting_data(*args)['type'] || 'polymorphic' + setting_data(*args)['type'] || 'polymorphic' end # @@ -84,7 +84,7 @@ def self.add_setting_accessor_name(klass, setting_name) # def self.setting_accessor_names(klass) @@setting_accessor_names ||= {} - self.lookup_nested_hash(@@setting_accessor_names, klass.to_s) || [] + lookup_nested_hash(@@setting_accessor_names, klass.to_s) || [] end end diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 19cf77c..12ba3a6 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -34,7 +34,7 @@ module ClassMethods # If not, +nil+ is returned. # def get(name, assignable = nil) - self.setting_record(name, assignable).try(:value) + setting_record(name, assignable).try(:value) end alias_method :[], :get @@ -86,7 +86,7 @@ def set(name, value, assignable: nil) # @return [Object] the default value for the given setting # def get_default_value(name, assignable = nil) - self.new(name: name, assignable: assignable).default_value + new(name: name, assignable: assignable).default_value end # @@ -97,7 +97,7 @@ def get_default_value(name, assignable = nil) # @return [Setting, NilClass] The found setting or nil if not existing # def setting_record(name, assignable = nil) - self.find_by(name: name.to_s, assignable: assignable) + find_by(name: name.to_s, assignable: assignable) end # @@ -110,7 +110,7 @@ def setting_record(name, assignable = nil) # @return [Array] The validation errors for the setting's value # def validation_errors(name, value, assignable = nil) - s = self.new(name: name, value: value, assignable: assignable) + s = new(name: name, value: value, assignable: assignable) s.valid? s.errors[:value] || [] end @@ -163,8 +163,8 @@ def localized_description # in 'settings.global.NAME' # def i18n_lookup(key, options = {}) - options[:scope] = [:settings, :global, self.name] - options[:scope] = [:settings, self.assignable.class.to_s.underscore, self.name] unless SettingAccessors::Internal.globally_defined_setting?(self.name) + options[:scope] = [:settings, :global, name] + options[:scope] = [:settings, assignable.class.to_s.underscore, name] unless SettingAccessors::Internal.globally_defined_setting?(name) I18n.t(key, options) end @@ -191,7 +191,7 @@ def value_type # shadow ActiveRecord's default one - which might still be needed. # def original_value - @original_value || self.value + @original_value || value end # From b4b365b9a79d2f41b952aa04aa9b8a113dce38be Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 22:54:50 +0100 Subject: [PATCH 22/60] rubocop: Layout/LeadingCommentSpace --- lib/setting_accessors/converter.rb | 6 +++--- lib/setting_accessors/integration.rb | 6 +++--- lib/setting_accessors/internal.rb | 4 ++-- lib/setting_accessors/validator.rb | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/setting_accessors/converter.rb b/lib/setting_accessors/converter.rb index bf864b6..54e7456 100644 --- a/lib/setting_accessors/converter.rb +++ b/lib/setting_accessors/converter.rb @@ -26,11 +26,11 @@ def initialize(value_type) # Converts the setting's value to the correct type # def convert(new_value) - #If the value is set to be polymorphic, we don't have to convert anything. + # If the value is set to be polymorphic, we don't have to convert anything. return new_value if @value_type.to_s == 'polymorphic' - #ActiveRecord only converts non-nil values to their database type - #during assignment + # ActiveRecord only converts non-nil values to their database type + # during assignment return new_value if new_value.nil? parse_method = :"parse_#{@value_type}" diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 2a5097f..22cff46 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -46,17 +46,17 @@ def setting_accessor(setting_name, options = {}) settings[setting_name] = new_value end - #NAME_was + # NAME_was define_method("#{setting_name}_was") do settings.value_was(setting_name) end - #NAME_before_type_cast + # NAME_before_type_cast define_method("#{setting_name}_before_type_cast") do settings.value_before_type_cast(setting_name) end - #NAME_changed? + # NAME_changed? define_method("#{setting_name}_changed?") do settings.value_changed?(setting_name) end diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 00202ed..122f92a 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -20,10 +20,10 @@ def class_settings # by using setting_accessor in your model class # def self.set_class_setting(klass, setting_name, options = {}) - #If there are no options given, the setting *has* to be defined globally. + # If there are no options given, the setting *has* to be defined globally. if options.empty? raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." - #If the setting is defined on class base, we have to store its options + # If the setting is defined on class base, we have to store its options else ensure_nested_hash!(class_settings, klass.to_s) class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys diff --git a/lib/setting_accessors/validator.rb b/lib/setting_accessors/validator.rb index 0c1c7ba..2d1c1c4 100644 --- a/lib/setting_accessors/validator.rb +++ b/lib/setting_accessors/validator.rb @@ -79,10 +79,10 @@ def validate_presence(record, requirement) # in settings.yml # def validate_numericality(record, options) - #Test if the value is Numeric in any way (float or int) + # Test if the value is Numeric in any way (float or int) add_error_if(!parse_value_as_numeric(record.value), record, :not_a_number) && return - #If the validation was set to check for integer values, do that as well + # If the validation was set to check for integer values, do that as well add_error_if(options['only_integer'] && !parse_value_as_fixnum(record.value), record, :not_an_integer) end From 73a8b0be668e297b7e76dbaf280a08fef87fbcd9 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:01:48 +0100 Subject: [PATCH 23/60] rubocop: Style/ExpandPathArguments --- .rubocop.yml | 8 +++++++- .../setting_accessors/install_generator.rb | 16 ++++++++-------- setting_accessors.gemspec | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index e427fb0..c2c2f36 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,4 @@ AllCops: - TargetRailsVersion: 5.0 TargetRubyVersion: 2.3 #--------------------------------------------- @@ -73,6 +72,13 @@ Metrics/MethodLength: Metrics/ModuleLength: Enabled: false +#--------------------------------------------- +# Naming +#--------------------------------------------- + +Naming/HeredocDelimiterNaming: + Enabled: false + #--------------------------------------------- # Style #--------------------------------------------- diff --git a/lib/generators/setting_accessors/install_generator.rb b/lib/generators/setting_accessors/install_generator.rb index a153789..a6b5796 100644 --- a/lib/generators/setting_accessors/install_generator.rb +++ b/lib/generators/setting_accessors/install_generator.rb @@ -5,11 +5,11 @@ module Generators class InstallGenerator < Rails::Generators::Base include Rails::Generators::Migration - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) argument :model_name, type: :string, default: 'Setting' - def self.next_migration_number(path) + def self.next_migration_number(_) if @prev_migration_nr @prev_migration_nr += 1 else @@ -24,12 +24,12 @@ def create_install template 'model.rb.erb', "app/models/#{model_name.classify.underscore}.rb" migration_template 'migration.rb.erb', "db/migrate/create_#{model_name.classify.underscore.pluralize}.rb" - initializer 'setting_accessors.rb', < Date: Sat, 24 Nov 2018 23:03:01 +0100 Subject: [PATCH 24/60] rubocop: Style/RaiseArgs --- lib/setting_accessors/internal.rb | 2 +- lib/setting_accessors/setting_scaffold.rb | 367 +++++++++--------- lib/setting_accessors/validator.rb | 6 +- .../setting_scaffold_spec.rb | 4 +- 4 files changed, 190 insertions(+), 189 deletions(-) diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 122f92a..863a67f 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -22,7 +22,7 @@ def class_settings def self.set_class_setting(klass, setting_name, options = {}) # If there are no options given, the setting *has* to be defined globally. if options.empty? - raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." + raise ArgumentError, "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." # If the setting is defined on class base, we have to store its options else ensure_nested_hash!(class_settings, klass.to_s) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 12ba3a6..795d678 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -6,225 +6,226 @@ # his own methods in the setting model for his own methods. # -module SettingAccessors::SettingScaffold +module SettingAccessors + module SettingScaffold - def self.included(base) - base.extend ClassMethods - base.validates_with SettingAccessors::Validator - base.serialize :value + def self.included(base) + base.extend ClassMethods + base.validates_with SettingAccessors::Validator + base.serialize :value - base.validates :name, - uniqueness: {scope: [:assignable_type, :assignable_id]}, - presence: true - end - - module ClassMethods - # - # Searches for a setting in the database and returns its value - # - # @param [String, Symbol] name - # The setting's name - # - # @param [ActiveRecord::Base] assignable - # If given, the setting searched has to be assigned to the given record - # If not given, a global setting is searched - # - # @return [Object, NilClass] - # If a setting is found, **its value** is returned. - # If not, +nil+ is returned. - # - def get(name, assignable = nil) - setting_record(name, assignable).try(:value) + base.validates :name, + uniqueness: {scope: [:assignable_type, :assignable_id]}, + presence: true end - alias_method :[], :get + module ClassMethods + # + # Searches for a setting in the database and returns its value + # + # @param [String, Symbol] name + # The setting's name + # + # @param [ActiveRecord::Base] assignable + # If given, the setting searched has to be assigned to the given record + # If not given, a global setting is searched + # + # @return [Object, NilClass] + # If a setting is found, **its value** is returned. + # If not, +nil+ is returned. + # + def get(name, assignable = nil) + setting_record(name, assignable).try(:value) + end + + alias_method :[], :get + + # + # Tries to look the setting up using #get, if no existing setting is found, + # the setting's default value is returned. + # + # This only works for class-wise settings, meaning that an assignable has to be present. + # + def get_or_default(name, assignable) + if (val = get(name, assignable)).nil? + new(name: name, assignable: assignable).default_value + else + val + end + end - # - # Tries to look the setting up using #get, if no existing setting is found, - # the setting's default value is returned. - # - # This only works for class-wise settings, meaning that an assignable has to be present. - # - def get_or_default(name, assignable) - if (val = get(name, assignable)).nil? + # + # Creates or updates the setting with the given name + # + # @param [String, Symbol] name + # The setting's name + # + # @param [Object] value + # The new setting value + # + # @param [ActiveRecord::Base] assignable + # The optional record this setting belongs to. If not + # given, the setting is global. + # + # @param [Boolean] return_value + # If set to +true+, only the setting's value is returned + # + # @return [Object] The newly set value + # + # @toto: Bless the rains down in Africa! + # + def set(name, value, assignable: nil) + (setting_record(name, assignable) || new(name: name, assignable: assignable)).tap do |setting| + setting.set_value(value) + setting.save + end.value + end + + alias_method :[]=, :set + + # + # @return [Object] the default value for the given setting + # + def get_default_value(name, assignable = nil) new(name: name, assignable: assignable).default_value - else - val + end + + # + # Looks up a setting record for the given name and assignable + # Unlike the other methods here, this one actually returns a Setting object + # instead of its value. + # + # @return [Setting, NilClass] The found setting or nil if not existing + # + def setting_record(name, assignable = nil) + find_by(name: name.to_s, assignable: assignable) + end + + # + # Tests, if the given value would be valid for the given + # setting name. This is done in this class method due to + # the process of setting creation through assigned records + # which does not allow going the "normal" way of testing whether + # a setting was saved correctly or not. + # + # @return [Array] The validation errors for the setting's value + # + def validation_errors(name, value, assignable = nil) + s = new(name: name, value: value, assignable: assignable) + s.valid? + s.errors[:value] || [] + end + + # + # Makes accessing settings a little easier. + # Examples: + # + # #Loading **the value** of a global setting named "my_setting" + # Setting.my_setting + # + # #Setting **the value** of a global setting named "my_setting" + # Setting.my_setting = [1,2,3,4,5] + # + # #Loading **the value** of an assigned setting named "cool_setting" + # #+some_cool_user+ is here an instance of ActiveRecord::Base + # Setting.cool_setting(some_cool_user) + # + def method_missing(method, *args, &block) + method_name = method.to_s + + if method_name.last == '=' + set(method_name[0..-2], args.first) + else + return super(method, *args, &block) if args.size > 1 + get(method_name, args.first) + end end end # - # Creates or updates the setting with the given name - # - # @param [String, Symbol] name - # The setting's name - # - # @param [Object] value - # The new setting value + # @return [String] the localized setting name + # they are stored in config/locales/settings.LOCALE.yml # - # @param [ActiveRecord::Base] assignable - # The optional record this setting belongs to. If not - # given, the setting is global. - # - # @param [Boolean] return_value - # If set to +true+, only the setting's value is returned - # - # @return [Object] The newly set value + def localized_name + i18n_lookup(:name) + end + # - # @toto: Bless the rains down in Africa! + # @return [String] the localized setting description + # see #localized_name # - def set(name, value, assignable: nil) - (setting_record(name, assignable) || new(name: name, assignable: assignable)).tap do |setting| - setting.set_value(value) - setting.save - end.value + def localized_description + i18n_lookup(:description) end - alias_method :[]=, :set - # - # @return [Object] the default value for the given setting + # Performs an I18n lookup in the settings locale. + # Class based settings are store in 'settings.CLASS.NAME', globally defined settings + # in 'settings.global.NAME' # - def get_default_value(name, assignable = nil) - new(name: name, assignable: assignable).default_value + def i18n_lookup(key, options = {}) + options[:scope] = [:settings, :global, name] + options[:scope] = [:settings, assignable.class.to_s.underscore, name] unless SettingAccessors::Internal.globally_defined_setting?(name) + I18n.t(key, options) end # - # Looks up a setting record for the given name and assignable - # Unlike the other methods here, this one actually returns a Setting object - # instead of its value. - # - # @return [Setting, NilClass] The found setting or nil if not existing + # @return [Object] the default value for the current setting # - def setting_record(name, assignable = nil) - find_by(name: name.to_s, assignable: assignable) + def default_value + data['default'].freeze end # - # Tests, if the given value would be valid for the given - # setting name. This is done in this class method due to - # the process of setting creation through assigned records - # which does not allow going the "normal" way of testing whether - # a setting was saved correctly or not. - # - # @return [Array] The validation errors for the setting's value + # @return [String] the setting's type as specified in settings.yml + # If the setting wasn't specified, a polymorphic type is assumed # - def validation_errors(name, value, assignable = nil) - s = new(name: name, value: value, assignable: assignable) - s.valid? - s.errors[:value] || [] + def value_type + data['type'] || 'polymorphic' end # - # Makes accessing settings a little easier. - # Examples: + # @return [Object] the setting's value before it was type casted using the defined rule in settings.yml + # See #value_before_type_cast for ActiveRecord attributes # - # #Loading **the value** of a global setting named "my_setting" - # Setting.my_setting + # We can't use the name #value_before_type_cast here as it would + # shadow ActiveRecord's default one - which might still be needed. # - # #Setting **the value** of a global setting named "my_setting" - # Setting.my_setting = [1,2,3,4,5] + def original_value + @original_value || value + end + # - # #Loading **the value** of an assigned setting named "cool_setting" - # #+some_cool_user+ is here an instance of ActiveRecord::Base - # Setting.cool_setting(some_cool_user) + # Sets the setting's value to the given one + # Performs automatic type casts # - def method_missing(method, *args, &block) - method_name = method.to_s - - if method_name.last == '=' - set(method_name[0..-2], args.first) - else - return super(method, *args, &block) if args.size > 1 - get(method_name, args.first) - end + def set_value(new_value) + @original_value = new_value + self.value = converter.convert(new_value) end - end - - # - # @return [String] the localized setting name - # they are stored in config/locales/settings.LOCALE.yml - # - def localized_name - i18n_lookup(:name) - end - - # - # @return [String] the localized setting description - # see #localized_name - # - def localized_description - i18n_lookup(:description) - end - - # - # Performs an I18n lookup in the settings locale. - # Class based settings are store in 'settings.CLASS.NAME', globally defined settings - # in 'settings.global.NAME' - # - def i18n_lookup(key, options = {}) - options[:scope] = [:settings, :global, name] - options[:scope] = [:settings, assignable.class.to_s.underscore, name] unless SettingAccessors::Internal.globally_defined_setting?(name) - I18n.t(key, options) - end - - # - # @return [Object] the default value for the current setting - # - def default_value - data['default'].freeze - end - - # - # @return [String] the setting's type as specified in settings.yml - # If the setting wasn't specified, a polymorphic type is assumed - # - def value_type - data['type'] || 'polymorphic' - end - # - # @return [Object] the setting's value before it was type casted using the defined rule in settings.yml - # See #value_before_type_cast for ActiveRecord attributes - # - # We can't use the name #value_before_type_cast here as it would - # shadow ActiveRecord's default one - which might still be needed. - # - def original_value - @original_value || value - end + private - # - # Sets the setting's value to the given one - # Performs automatic type casts - # - def set_value(new_value) - @original_value = new_value - self.value = converter.convert(new_value) - end - - private - - def converter - @converter ||= SettingAccessors::Internal.converter(value_type) - end + def converter + @converter ||= SettingAccessors::Internal.converter(value_type) + end - def value_required? - !!validations['required'] - end + def value_required? + !!validations['required'] + end - # - # Accessor to the validations part of the setting's data - # - def validations - data['validates'] || {} - end + # + # Accessor to the validations part of the setting's data + # + def validations + data['validates'] || {} + end - # - # @see {SettingAccessors::Internal#setting_data} for more information - # - def data - SettingAccessors::Internal.setting_data(name, assignable) + # + # @see {SettingAccessors::Internal#setting_data} for more information + # + def data + SettingAccessors::Internal.setting_data(name, assignable) + end end - -end +end \ No newline at end of file diff --git a/lib/setting_accessors/validator.rb b/lib/setting_accessors/validator.rb index 2d1c1c4..2868f87 100644 --- a/lib/setting_accessors/validator.rb +++ b/lib/setting_accessors/validator.rb @@ -11,7 +11,7 @@ def validate(record) elsif built_in_validation?(key) send("validate_#{key}", record, requirement) else - raise ArgumentError.new("The invalid validation '#{key}' was given in model '#{defining_model(record).to_s}'") + raise ArgumentError, "The invalid validation '#{key}' was given in model '#{defining_model(record).to_s}'" end end end @@ -38,10 +38,10 @@ def run_custom_validation(record, proc) if defining_model(record).respond_to?(proc) defining_model(record).send(proc) else - raise ArgumentError.new "The method '#{proc}' was set up as validation method in model '#{defining_model(record).name}', but doesn't exist." + raise ArgumentError, "The method '#{proc}' was set up as validation method in model '#{defining_model(record).name}', but doesn't exist." end else - raise ArgumentError.new "An invalid validations method was given ('#{proc}')" + raise ArgumentError, "An invalid validations method was given ('#{proc}')" end end diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb index 1077720..cb5bbb6 100644 --- a/spec/lib/setting_accessors/setting_scaffold_spec.rb +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -102,7 +102,7 @@ let!(:setting) { Setting.create!(name: 'foo', value: 'bar') } it 'updates the existing setting' do - expect { Setting.set('foo', 'baz') }.not_to change { Setting.count } + expect { Setting.set('foo', 'baz') }.not_to(change { Setting.count }) expect(setting.reload.value).to eql 'baz' end end @@ -122,7 +122,7 @@ let!(:setting) { Setting.create!(name: 'foo', value: 'bar', assignable: assignable) } it 'updates the existing setting' do - expect { Setting.set('foo', 'baz', assignable: assignable) }.not_to change { Setting.count } + expect { Setting.set('foo', 'baz', assignable: assignable) }.not_to(change { Setting.count }) expect(setting.reload.value).to eql 'baz' end end From 9d4f622c31b75662183f37075b2e8953a7ecb0b5 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:08:46 +0100 Subject: [PATCH 25/60] rubocop: Layout/TrailingBlankLines --- Rakefile | 2 +- lib/setting_accessors/accessor.rb | 2 +- lib/setting_accessors/helpers.rb | 2 +- lib/setting_accessors/integration_validator.rb | 2 +- lib/setting_accessors/internal.rb | 2 +- lib/setting_accessors/setting_scaffold.rb | 2 +- lib/setting_accessors/validator.rb | 2 +- spec/lib/generators/install_generator_spec.rb | 2 +- spec/lib/setting_accessors/integration_spec.rb | 2 +- spec/support/helpers.rb | 2 +- spec/support/setting_model.rb | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Rakefile b/Rakefile index 9de9288..82bb534 100644 --- a/Rakefile +++ b/Rakefile @@ -5,4 +5,4 @@ require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task default: :spec \ No newline at end of file +task default: :spec diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index f5f1902..c7ee241 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -187,4 +187,4 @@ def flush! @old_values = {} end end -end \ No newline at end of file +end diff --git a/lib/setting_accessors/helpers.rb b/lib/setting_accessors/helpers.rb index fe5d945..b630497 100644 --- a/lib/setting_accessors/helpers.rb +++ b/lib/setting_accessors/helpers.rb @@ -24,4 +24,4 @@ def lookup_nested_hash(hash, *keys) h end end -end \ No newline at end of file +end diff --git a/lib/setting_accessors/integration_validator.rb b/lib/setting_accessors/integration_validator.rb index 5d1c95c..7194d7f 100644 --- a/lib/setting_accessors/integration_validator.rb +++ b/lib/setting_accessors/integration_validator.rb @@ -14,4 +14,4 @@ class SettingAccessors::IntegrationValidator < ActiveModel::Validator def validate(record) record.settings.send(:validate!) end -end \ No newline at end of file +end diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 863a67f..631d84b 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -88,4 +88,4 @@ def self.setting_accessor_names(klass) end end -end \ No newline at end of file +end diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 795d678..8f9a136 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -228,4 +228,4 @@ def data SettingAccessors::Internal.setting_data(name, assignable) end end -end \ No newline at end of file +end diff --git a/lib/setting_accessors/validator.rb b/lib/setting_accessors/validator.rb index 2868f87..66b5550 100644 --- a/lib/setting_accessors/validator.rb +++ b/lib/setting_accessors/validator.rb @@ -143,4 +143,4 @@ def parse_value_as_boolean(raw_value) nil end end -end \ No newline at end of file +end diff --git a/spec/lib/generators/install_generator_spec.rb b/spec/lib/generators/install_generator_spec.rb index 2337a97..bec6b4f 100644 --- a/spec/lib/generators/install_generator_spec.rb +++ b/spec/lib/generators/install_generator_spec.rb @@ -26,4 +26,4 @@ it 'creates the migration' do assert_migration 'db/migrate/create_settings.rb' end -end \ No newline at end of file +end diff --git a/spec/lib/setting_accessors/integration_spec.rb b/spec/lib/setting_accessors/integration_spec.rb index ccda8b4..307e884 100644 --- a/spec/lib/setting_accessors/integration_spec.rb +++ b/spec/lib/setting_accessors/integration_spec.rb @@ -263,4 +263,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index cb3b572..29eaefb 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -4,4 +4,4 @@ module Helpers def setting_names(klass) SettingAccessors::Internal.setting_accessor_names(klass) end -end \ No newline at end of file +end diff --git a/spec/support/setting_model.rb b/spec/support/setting_model.rb index 9cc6f80..e5d8b7b 100644 --- a/spec/support/setting_model.rb +++ b/spec/support/setting_model.rb @@ -27,4 +27,4 @@ def self.included(base) end end end -end \ No newline at end of file +end From dcf1125b45cf527c29cc60413ca48caa90bdd8a7 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:15:00 +0100 Subject: [PATCH 26/60] Removed last occurrences of shorthand module nesting --- lib/setting_accessors/converter.rb | 61 ++--- lib/setting_accessors/integration.rb | 202 ++++++++-------- .../integration_validator.rb | 8 +- lib/setting_accessors/validator.rb | 222 +++++++++--------- lib/setting_accessors/version.rb | 2 +- 5 files changed, 251 insertions(+), 244 deletions(-) diff --git a/lib/setting_accessors/converter.rb b/lib/setting_accessors/converter.rb index 54e7456..702f426 100644 --- a/lib/setting_accessors/converter.rb +++ b/lib/setting_accessors/converter.rb @@ -16,37 +16,38 @@ # # If the type is 'polymorphic', it is not converted at all. # -class SettingAccessors::Converter +module SettingAccessors + class Converter - def initialize(value_type) - @value_type = value_type - end + def initialize(value_type) + @value_type = value_type + end - # - # Converts the setting's value to the correct type - # - def convert(new_value) - # If the value is set to be polymorphic, we don't have to convert anything. - return new_value if @value_type.to_s == 'polymorphic' + # + # Converts the setting's value to the correct type + # + def convert(new_value) + # If the value is set to be polymorphic, we don't have to convert anything. + return new_value if @value_type.to_s == 'polymorphic' - # ActiveRecord only converts non-nil values to their database type - # during assignment - return new_value if new_value.nil? + # ActiveRecord only converts non-nil values to their database type + # during assignment + return new_value if new_value.nil? - parse_method = :"parse_#{@value_type}" + parse_method = :"parse_#{@value_type}" - if private_methods.include?(parse_method) - send(parse_method, new_value) - else - Rails.logger.warn("Invalid Setting type: #{@value_type}") - new_value + if private_methods.include?(parse_method) + send(parse_method, new_value) + else + Rails.logger.warn("Invalid Setting type: #{@value_type}") + new_value + end end - end - private + private - def parse_boolean(value) - case value + def parse_boolean(value) + case value when TrueClass, FalseClass value when String @@ -59,15 +60,15 @@ def parse_boolean(value) nil else nil + end end - end - def parse_integer(value) - value.to_i - end + def parse_integer(value) + value.to_i + end - def parse_string(value) - value.to_s + def parse_string(value) + value.to_s + end end - end diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 22cff46..2c1ac9b 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -1,117 +1,119 @@ # frozen_string_literal: true -module SettingAccessors::Integration - def self.included(base) - base.validates_with SettingAccessors::IntegrationValidator - - # After the main record was saved, we can save its settings. - # This is necessary as the record might have been a new record - # without an ID yet - base.after_save do - settings.send(:persist!) +module SettingAccessors + module Integration + def self.included(base) + base.validates_with SettingAccessors::IntegrationValidator + + # After the main record was saved, we can save its settings. + # This is necessary as the record might have been a new record + # without an ID yet + base.after_save do + settings.send(:persist!) + end + + base.extend ClassMethods end - base.extend ClassMethods - end + module ClassMethods + + # + # Generates a new accessor (=getter and setter) for the given setting + # + # @param [String, Symbol] setting_name + # The setting's name + # + # @param [Hash] options + # Options to customize the behaviour of the generated accessor + # + def setting_accessor(setting_name, options = {}) + SettingAccessors::Internal.set_class_setting(self, setting_name, options) + + setting_type = SettingAccessors::Internal.setting_value_type(setting_name, self).to_sym + + # Add the setting's name to the list of setting_accessors for this class + SettingAccessors::Internal.add_setting_accessor_name(self, setting_name) + + # Getter + define_method(setting_name) do + settings.get_or_default(setting_name) + end + + # Getter alias for boolean settings + alias_method "#{setting_name}?", setting_name if setting_type == :boolean + + # Setter + define_method("#{setting_name}=") do |new_value| + settings[setting_name] = new_value + end + + # NAME_was + define_method("#{setting_name}_was") do + settings.value_was(setting_name) + end + + # NAME_before_type_cast + define_method("#{setting_name}_before_type_cast") do + settings.value_before_type_cast(setting_name) + end + + # NAME_changed? + define_method("#{setting_name}_changed?") do + settings.value_changed?(setting_name) + end + end + end - module ClassMethods + # + # Previously read setting values have to be refreshed if a record is reloaded. + # Without this, #reload'ing a record would not update its setting values to the + # latest database version if they were previously read. + # + # Example to demonstrate the problem with this override: + # user = User.create(:a_boolean => true) + # user_alias = User.find(user.id) + # user.a_boolean = !user_alias.a_boolean + # user.save + # user_alias.reload + # user_alias.a_boolean + # #=> true + # + def reload(*) + super + @settings_accessor = nil + self + end # - # Generates a new accessor (=getter and setter) for the given setting + # Adds changed settings to ActiveModel's list of changed attributes. + # This is necessary for #changed? to work correctly without actually overriding + # the method itself. # - # @param [String, Symbol] setting_name - # The setting's name + # TODO: Check if it makes more sense to hook into AR5's AttributeMutationTracker instead # - # @param [Hash] options - # Options to customize the behaviour of the generated accessor + # @return [Hash] All changed attributes # - def setting_accessor(setting_name, options = {}) - SettingAccessors::Internal.set_class_setting(self, setting_name, options) - - setting_type = SettingAccessors::Internal.setting_value_type(setting_name, self).to_sym - - # Add the setting's name to the list of setting_accessors for this class - SettingAccessors::Internal.add_setting_accessor_name(self, setting_name) - - # Getter - define_method(setting_name) do - settings.get_or_default(setting_name) - end - - # Getter alias for boolean settings - alias_method "#{setting_name}?", setting_name if setting_type == :boolean - - # Setter - define_method("#{setting_name}=") do |new_value| - settings[setting_name] = new_value - end - - # NAME_was - define_method("#{setting_name}_was") do - settings.value_was(setting_name) - end - - # NAME_before_type_cast - define_method("#{setting_name}_before_type_cast") do - settings.value_before_type_cast(setting_name) - end - - # NAME_changed? - define_method("#{setting_name}_changed?") do - settings.value_changed?(setting_name) - end + def changed_attributes + super.merge(settings.changed_values) end - end - # - # Previously read setting values have to be refreshed if a record is reloaded. - # Without this, #reload'ing a record would not update its setting values to the - # latest database version if they were previously read. - # - # Example to demonstrate the problem with this override: - # user = User.create(:a_boolean => true) - # user_alias = User.find(user.id) - # user.a_boolean = !user_alias.a_boolean - # user.save - # user_alias.reload - # user_alias.a_boolean - # #=> true - # - def reload(*) - super - @settings_accessor = nil - self - end - - # - # Adds changed settings to ActiveModel's list of changed attributes. - # This is necessary for #changed? to work correctly without actually overriding - # the method itself. - # - # TODO: Check if it makes more sense to hook into AR5's AttributeMutationTracker instead - # - # @return [Hash] All changed attributes - # - def changed_attributes - super.merge(settings.changed_values) - end - - def as_json(options = {}) - super.tap do |json| - setting_names = SettingAccessors::Internal.setting_accessor_names(self.class) - if options[:only] - setting_names &= Array(options[:only]).map(&:to_s) - elsif options[:except] - setting_names -= Array(options[:except]).map(&:to_s) - end - - setting_names.each do |setting_name| - json[setting_name.to_s] = send(setting_name) + def as_json(options = {}) + super.tap do |json| + setting_names = SettingAccessors::Internal.setting_accessor_names(self.class) + if options[:only] + setting_names &= Array(options[:only]).map(&:to_s) + elsif options[:except] + setting_names -= Array(options[:except]).map(&:to_s) + end + + setting_names.each do |setting_name| + json[setting_name.to_s] = send(setting_name) + end end end - end - def settings - @settings_accessor ||= SettingAccessors::Accessor.new(self) + def settings + @settings_accessor ||= SettingAccessors::Accessor.new(self) + end end end diff --git a/lib/setting_accessors/integration_validator.rb b/lib/setting_accessors/integration_validator.rb index 7194d7f..001ba6c 100644 --- a/lib/setting_accessors/integration_validator.rb +++ b/lib/setting_accessors/integration_validator.rb @@ -10,8 +10,10 @@ # for Rails' validation chain # -class SettingAccessors::IntegrationValidator < ActiveModel::Validator - def validate(record) - record.settings.send(:validate!) +module SettingAccessors + class IntegrationValidator < ActiveModel::Validator + def validate(record) + record.settings.send(:validate!) + end end end diff --git a/lib/setting_accessors/validator.rb b/lib/setting_accessors/validator.rb index 66b5550..cfc2b39 100644 --- a/lib/setting_accessors/validator.rb +++ b/lib/setting_accessors/validator.rb @@ -1,146 +1,148 @@ # frozen_string_literal: true -class SettingAccessors::Validator < ActiveModel::Validator - - def validate(record) - record.send(:validations).each do |key, requirement| - if key.to_s == 'custom' - Array(requirement).each do |validation| - run_custom_validation(record, validation) +module SettingAccessors + class Validator < ActiveModel::Validator + + def validate(record) + record.send(:validations).each do |key, requirement| + if key.to_s == 'custom' + Array(requirement).each do |validation| + run_custom_validation(record, validation) + end + elsif built_in_validation?(key) + send("validate_#{key}", record, requirement) + else + raise ArgumentError, "The invalid validation '#{key}' was given in model '#{defining_model(record).to_s}'" end - elsif built_in_validation?(key) - send("validate_#{key}", record, requirement) - else - raise ArgumentError, "The invalid validation '#{key}' was given in model '#{defining_model(record).to_s}'" end end - end - private + private - def defining_model(record) - if SettingAccessors::Internal.globally_defined_setting?(record.name) || !record.assignable - SettingAccessors.setting_class - else - record.assignable.class + def defining_model(record) + if SettingAccessors::Internal.globally_defined_setting?(record.name) || !record.assignable + SettingAccessors.setting_class + else + record.assignable.class + end end - end - # - # Runs a custom validation method - # The method may either be a Proc or an instance method in +record+.+class+ - # - def run_custom_validation(record, proc) - case proc - when Proc - proc.call(record) - when Symbol - if defining_model(record).respond_to?(proc) - defining_model(record).send(proc) + # + # Runs a custom validation method + # The method may either be a Proc or an instance method in +record+.+class+ + # + def run_custom_validation(record, proc) + case proc + when Proc + proc.call(record) + when Symbol + if defining_model(record).respond_to?(proc) + defining_model(record).send(proc) + else + raise ArgumentError, "The method '#{proc}' was set up as validation method in model '#{defining_model(record).name}', but doesn't exist." + end else - raise ArgumentError, "The method '#{proc}' was set up as validation method in model '#{defining_model(record).name}', but doesn't exist." + raise ArgumentError, "An invalid validations method was given ('#{proc}')" end - else - raise ArgumentError, "An invalid validations method was given ('#{proc}')" end - end - # - # @return [TrueClass, FalseClass] +true+ if the given validation - # is a built-in one. - # - def built_in_validation?(validation_name) - private_methods.include?("validate_#{validation_name}".to_sym) - end + # + # @return [TrueClass, FalseClass] +true+ if the given validation + # is a built-in one. + # + def built_in_validation?(validation_name) + private_methods.include?("validate_#{validation_name}".to_sym) + end - # - # Validates that the setting's value is given - # accepts :allow_blank and :allow_nil as options - # - def validate_presence(record, requirement) - return true unless requirement - - if requirement.is_a?(Hash) - if requirement['allow_blank'] && !record.value.nil? || - requirement['allow_nil'] && record.value.nil? || - record.value.present? - true + # + # Validates that the setting's value is given + # accepts :allow_blank and :allow_nil as options + # + def validate_presence(record, requirement) + return true unless requirement + + if requirement.is_a?(Hash) + if requirement['allow_blank'] && !record.value.nil? || + requirement['allow_nil'] && record.value.nil? || + record.value.present? + true + else + add_error record, :blank + false + end else - add_error record, :blank - false + add_error_if record.value.nil? || record.value == '', record, :blank end - else - add_error_if record.value.nil? || record.value == '', record, :blank end - end - # - # Validates numericality of the setting's value based on the options given - # in settings.yml - # - def validate_numericality(record, options) - # Test if the value is Numeric in any way (float or int) - add_error_if(!parse_value_as_numeric(record.value), record, :not_a_number) && return + # + # Validates numericality of the setting's value based on the options given + # in settings.yml + # + def validate_numericality(record, options) + # Test if the value is Numeric in any way (float or int) + add_error_if(!parse_value_as_numeric(record.value), record, :not_a_number) && return - # If the validation was set to check for integer values, do that as well - add_error_if(options['only_integer'] && !parse_value_as_fixnum(record.value), record, :not_an_integer) - end + # If the validation was set to check for integer values, do that as well + add_error_if(options['only_integer'] && !parse_value_as_fixnum(record.value), record, :not_an_integer) + end - # - # Validates whether the given value is a valid boolean - # - def validate_boolean(record, requirement) - add_error_if(requirement && parse_value_as_boolean(record.value).nil?, record, :not_a_boolean) - end + # + # Validates whether the given value is a valid boolean + # + def validate_boolean(record, requirement) + add_error_if(requirement && parse_value_as_boolean(record.value).nil?, record, :not_a_boolean) + end - #---------------------------------------------------------------- - # Helper Methods - #---------------------------------------------------------------- + #---------------------------------------------------------------- + # Helper Methods + #---------------------------------------------------------------- - def add_error(record, validation, options = {}) - record.errors.add :value, validation, options - end + def add_error(record, validation, options = {}) + record.errors.add :value, validation, options + end - def add_error_if(cond, record, validation, options = {}) - add_error(record, validation, options) if cond - cond - end + def add_error_if(cond, record, validation, options = {}) + add_error(record, validation, options) if cond + cond + end - # - # Borrowed from Rails' numericality validator - # @return [Float, NilClass] the given String value as a float or nil - # if the value was not a valid Numeric - # - def parse_value_as_numeric(raw_value) - Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ - rescue ArgumentError, TypeError - nil - end + # + # Borrowed from Rails' numericality validator + # @return [Float, NilClass] the given String value as a float or nil + # if the value was not a valid Numeric + # + def parse_value_as_numeric(raw_value) + Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ + rescue ArgumentError, TypeError + nil + end - # - # Borrowed from Rails' numericality validator - # - # @return [Fixnum, NilClass] the given String value as an int or nil - # - def parse_value_as_fixnum(raw_value) - raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ - end + # + # Borrowed from Rails' numericality validator + # + # @return [Fixnum, NilClass] the given String value as an int or nil + # + def parse_value_as_fixnum(raw_value) + raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ + end - # - # Tries to parse the given value as a boolean value - # - # @return [TrueClass, FalseClass, NilClass] - # - def parse_value_as_boolean(raw_value) - case raw_value + # + # Tries to parse the given value as a boolean value + # + # @return [TrueClass, FalseClass, NilClass] + # + def parse_value_as_boolean(raw_value) + case raw_value when TrueClass, FalseClass raw_value when String - return true if %w[true 1].include?(raw_value.downcase) + return true if %w[true 1].include?(raw_value.downcase) return false if %w[false 0].include?(raw_value.downcase) nil else nil + end end end end diff --git a/lib/setting_accessors/version.rb b/lib/setting_accessors/version.rb index 0d34498..5ffd7ae 100644 --- a/lib/setting_accessors/version.rb +++ b/lib/setting_accessors/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SettingAccessors - VERSION = '0.3.0'.freeze + VERSION = '0.3.0' end From 29ad8ec2bce061d538c6b70fae4cf30c59232dcf Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:18:08 +0100 Subject: [PATCH 27/60] rubocop: OCD mode --- setting_accessors.gemspec | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index 3d5057c..628d54f 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -1,4 +1,3 @@ -# coding: utf-8 # frozen_string_literal: true lib = File.expand_path('lib', __dir__) @@ -8,28 +7,28 @@ require 'setting_accessors/version' Gem::Specification.new do |spec| spec.name = 'setting_accessors' spec.version = SettingAccessors::VERSION - spec.authors = ["Stefan Exner"] - spec.email = ["stex@sterex.de"] - spec.summary = %q{A global key-value-store and virtual model columns} - spec.description = %q{Adds a global key-value-store to Rails applications and allows adding typed columns - to model classes without having to change the database layout.} + spec.authors = ['Stefan Exner'] + spec.email = ['stex@sterex.de'] + spec.summary = 'A global key-value-store and virtual model columns' + spec.description = 'Adds a global key-value-store to Rails applications and allows adding typed columns + to model classes without having to change the database layout.' spec.homepage = 'https://www.github.com/stex/setting_accessors' - spec.license = "MIT" + spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] spec.required_ruby_version = ['>= 2.3', '< 3'] - spec.add_development_dependency "bundler", "~> 1.6" - spec.add_development_dependency "rake", "~> 10.4" - spec.add_development_dependency 'sqlite3', '~> 1.3' - spec.add_development_dependency 'rspec', '~> 3.8.0' + spec.add_development_dependency 'bundler', '~> 1.6' spec.add_development_dependency 'generator_spec', '~> 0.9.4' - spec.add_development_dependency 'with_model', '~> 2.1.2' + spec.add_development_dependency 'rake', '~> 10.4' + spec.add_development_dependency 'rspec', '~> 3.8.0' spec.add_development_dependency 'rubocop', '~> 0.60.0' + spec.add_development_dependency 'sqlite3', '~> 1.3' + spec.add_development_dependency 'with_model', '~> 2.1.2' spec.add_dependency 'activerecord', ['>= 4.1', '<= 5.2'] spec.add_dependency 'activesupport', ['>= 4.1', '<= 5.2'] From bfb8c85f85c1b796eace3c9a7e335403a39fec21 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:26:22 +0100 Subject: [PATCH 28/60] Added bin/console and bin/setup, slight requirement refactoring --- .editorconfig | 10 ++++++++++ bin/console | 15 +++++++++++++++ bin/setup | 10 ++++++++++ lib/setting_accessors.rb | 3 +++ lib/setting_accessors/integration_validator.rb | 2 +- setting_accessors.gemspec | 10 +++++++--- 6 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 .editorconfig create mode 100755 bin/console create mode 100755 bin/setup diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d0f3432 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..25159b4 --- /dev/null +++ b/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' +require 'setting_accessors' + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require 'irb' +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..bba3144 --- /dev/null +++ b/bin/setup @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# frozen_string_literal: true + +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 6d49d52..da2d69b 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'active_record' +require 'active_model/validations' + require 'setting_accessors/version' require 'setting_accessors/helpers' require 'setting_accessors/accessor' diff --git a/lib/setting_accessors/integration_validator.rb b/lib/setting_accessors/integration_validator.rb index 001ba6c..de65617 100644 --- a/lib/setting_accessors/integration_validator.rb +++ b/lib/setting_accessors/integration_validator.rb @@ -11,7 +11,7 @@ # module SettingAccessors - class IntegrationValidator < ActiveModel::Validator + class IntegrationValidator < ::ActiveModel::Validator def validate(record) record.settings.send(:validate!) end diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index 628d54f..22160b1 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -15,9 +15,12 @@ Gem::Specification.new do |spec| spec.homepage = 'https://www.github.com/stex/setting_accessors' spec.license = 'MIT' - spec.files = `git ls-files -z`.split("\x0") - spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end + + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.required_ruby_version = ['>= 2.3', '< 3'] @@ -30,6 +33,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'sqlite3', '~> 1.3' spec.add_development_dependency 'with_model', '~> 2.1.2' + spec.add_dependency 'activemodel', ['>= 4.1', '<= 5.2'] spec.add_dependency 'activerecord', ['>= 4.1', '<= 5.2'] spec.add_dependency 'activesupport', ['>= 4.1', '<= 5.2'] end From 491a35b496e86ae6581f548d9dd0b79c0cd89781 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:30:47 +0100 Subject: [PATCH 29/60] Fixed generator test destination --- spec/lib/generators/install_generator_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/generators/install_generator_spec.rb b/spec/lib/generators/install_generator_spec.rb index bec6b4f..0e51879 100644 --- a/spec/lib/generators/install_generator_spec.rb +++ b/spec/lib/generators/install_generator_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require 'generator_spec' -require "generators/setting_accessors/install_generator" +require 'generators/setting_accessors/install_generator' describe SettingAccessors::Generators::InstallGenerator, type: :generator do - destination File.expand_path('../dummy/tmp', File.dirname(__FILE__)) + destination File.expand_path('../../../tmp', File.dirname(__FILE__)) before(:all) do prepare_destination From 6d47e6e2cc0cce09c7a9be54f83e1b8117448985 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sat, 24 Nov 2018 23:41:01 +0100 Subject: [PATCH 30/60] Initial appraisal setup, bumped required rails version to 4.2 to use with_model 2.x --- .travis.yml | 6 ++ Appraisals | 17 +++ gemfiles/rails_4.1.gemfile | 7 ++ gemfiles/rails_4.2.gemfile | 7 ++ gemfiles/rails_4.2.gemfile.lock | 162 +++++++++++++++++++++++++++++ gemfiles/rails_5.0.gemfile | 7 ++ gemfiles/rails_5.0.gemfile.lock | 169 ++++++++++++++++++++++++++++++ gemfiles/rails_5.1.gemfile | 7 ++ gemfiles/rails_5.1.gemfile.lock | 169 ++++++++++++++++++++++++++++++ gemfiles/rails_5.2.gemfile | 7 ++ gemfiles/rails_5.2.gemfile.lock | 177 ++++++++++++++++++++++++++++++++ setting_accessors.gemspec | 7 +- spec/support/setting_model.rb | 2 +- 13 files changed, 740 insertions(+), 4 deletions(-) create mode 100644 Appraisals create mode 100644 gemfiles/rails_4.1.gemfile create mode 100644 gemfiles/rails_4.2.gemfile create mode 100644 gemfiles/rails_4.2.gemfile.lock create mode 100644 gemfiles/rails_5.0.gemfile create mode 100644 gemfiles/rails_5.0.gemfile.lock create mode 100644 gemfiles/rails_5.1.gemfile create mode 100644 gemfiles/rails_5.1.gemfile.lock create mode 100644 gemfiles/rails_5.2.gemfile create mode 100644 gemfiles/rails_5.2.gemfile.lock diff --git a/.travis.yml b/.travis.yml index 604b8c4..4d52754 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,12 @@ rvm: - 2.5.0 - 2.5.3 +gemfile: + - gemfiles/rails_4.2.gemfile + - gemfiles/rails_5.0.gemfile + - gemfiles/rails_5.1.gemfile + - gemfiles/rails_5.2.gemfile + before_install: 'gem install bundler' script: 'bundle exec rake' diff --git a/Appraisals b/Appraisals new file mode 100644 index 0000000..b1589c8 --- /dev/null +++ b/Appraisals @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +appraise 'rails-4.2' do + gem 'rails', '~> 4.2.0' +end + +appraise 'rails-5.0' do + gem 'rails', '~> 5.0.0' +end + +appraise 'rails-5.1' do + gem 'rails', '~> 5.1.0' +end + +appraise 'rails-5.2' do + gem 'rails', '~> 5.2.0' +end diff --git a/gemfiles/rails_4.1.gemfile b/gemfiles/rails_4.1.gemfile new file mode 100644 index 0000000..f4a887f --- /dev/null +++ b/gemfiles/rails_4.1.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 4.1.0" + +gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile new file mode 100644 index 0000000..6977eb0 --- /dev/null +++ b/gemfiles/rails_4.2.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 4.2.0" + +gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock new file mode 100644 index 0000000..a2b2059 --- /dev/null +++ b/gemfiles/rails_4.2.gemfile.lock @@ -0,0 +1,162 @@ +PATH + remote: .. + specs: + setting_accessors (0.3.0) + activemodel (>= 4.2, <= 5.2) + activerecord (>= 4.2, <= 5.2) + activesupport (>= 4.2, <= 5.2) + +GEM + remote: https://rubygems.org/ + specs: + actionmailer (4.2.10) + actionpack (= 4.2.10) + actionview (= 4.2.10) + activejob (= 4.2.10) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.10) + actionview (= 4.2.10) + activesupport (= 4.2.10) + rack (~> 1.6) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.10) + activesupport (= 4.2.10) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (4.2.10) + activesupport (= 4.2.10) + globalid (>= 0.3.0) + activemodel (4.2.10) + activesupport (= 4.2.10) + builder (~> 3.1) + activerecord (4.2.10) + activemodel (= 4.2.10) + activesupport (= 4.2.10) + arel (~> 6.0) + activesupport (4.2.10) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (6.0.4) + ast (2.4.0) + builder (3.2.3) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubis (2.7.0) + generator_spec (0.9.4) + activesupport (>= 3.0.0) + railties (>= 3.0.0) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jaro_winkler (1.5.1-x86_64-darwin-17) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + parallel (1.12.1) + parser (2.5.3.0) + ast (~> 2.4.0) + powerpack (0.1.2) + rack (1.6.11) + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.10) + actionmailer (= 4.2.10) + actionpack (= 4.2.10) + actionview (= 4.2.10) + activejob (= 4.2.10) + activemodel (= 4.2.10) + activerecord (= 4.2.10) + activesupport (= 4.2.10) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.10) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.9) + activesupport (>= 4.2.0, < 5.0) + nokogiri (~> 1.6) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (4.2.10) + actionpack (= 4.2.10) + activesupport (= 4.2.10) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rainbow (3.0.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + rubocop (0.60.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5, != 2.5.1.1) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.4.0) + ruby-progressbar (1.10.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + unicode-display_width (1.4.0) + with_model (2.1.2) + activerecord (>= 4.2, < 6.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.2.0) + bundler (~> 1.6) + generator_spec (~> 0.9.4) + rails (~> 4.2.0) + rake (~> 10.4) + rspec (~> 3.8.0) + rubocop (~> 0.60.0) + setting_accessors! + sqlite3 (~> 1.3) + with_model (~> 2.1.2) + +BUNDLED WITH + 1.17.1 diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile new file mode 100644 index 0000000..10f52e7 --- /dev/null +++ b/gemfiles/rails_5.0.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 5.0.0" + +gemspec path: "../" diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock new file mode 100644 index 0000000..9940d5c --- /dev/null +++ b/gemfiles/rails_5.0.gemfile.lock @@ -0,0 +1,169 @@ +PATH + remote: .. + specs: + setting_accessors (0.3.0) + activemodel (>= 4.2, <= 5.2) + activerecord (>= 4.2, <= 5.2) + activesupport (>= 4.2, <= 5.2) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.0.7) + actionpack (= 5.0.7) + nio4r (>= 1.2, < 3.0) + websocket-driver (~> 0.6.1) + actionmailer (5.0.7) + actionpack (= 5.0.7) + actionview (= 5.0.7) + activejob (= 5.0.7) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.0.7) + actionview (= 5.0.7) + activesupport (= 5.0.7) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.7) + activesupport (= 5.0.7) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.0.7) + activesupport (= 5.0.7) + globalid (>= 0.3.6) + activemodel (5.0.7) + activesupport (= 5.0.7) + activerecord (5.0.7) + activemodel (= 5.0.7) + activesupport (= 5.0.7) + arel (~> 7.0) + activesupport (5.0.7) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (7.1.4) + ast (2.4.0) + builder (3.2.3) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubis (2.7.0) + generator_spec (0.9.4) + activesupport (>= 3.0.0) + railties (>= 3.0.0) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (1.0.1) + concurrent-ruby (~> 1.0) + jaro_winkler (1.5.1-x86_64-darwin-17) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + method_source (0.9.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + parallel (1.12.1) + parser (2.5.3.0) + ast (~> 2.4.0) + powerpack (0.1.2) + rack (2.0.6) + rack-test (0.6.3) + rack (>= 1.0) + rails (5.0.7) + actioncable (= 5.0.7) + actionmailer (= 5.0.7) + actionpack (= 5.0.7) + actionview (= 5.0.7) + activejob (= 5.0.7) + activemodel (= 5.0.7) + activerecord (= 5.0.7) + activesupport (= 5.0.7) + bundler (>= 1.3.0) + railties (= 5.0.7) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.0.7) + actionpack (= 5.0.7) + activesupport (= 5.0.7) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rainbow (3.0.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + rubocop (0.60.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5, != 2.5.1.1) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.4.0) + ruby-progressbar (1.10.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + unicode-display_width (1.4.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + with_model (2.1.2) + activerecord (>= 4.2, < 6.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.2.0) + bundler (~> 1.6) + generator_spec (~> 0.9.4) + rails (~> 5.0.0) + rake (~> 10.4) + rspec (~> 3.8.0) + rubocop (~> 0.60.0) + setting_accessors! + sqlite3 (~> 1.3) + with_model (~> 2.1.2) + +BUNDLED WITH + 1.17.1 diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile new file mode 100644 index 0000000..6100e83 --- /dev/null +++ b/gemfiles/rails_5.1.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 5.1.0" + +gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock new file mode 100644 index 0000000..f4eed1f --- /dev/null +++ b/gemfiles/rails_5.1.gemfile.lock @@ -0,0 +1,169 @@ +PATH + remote: .. + specs: + setting_accessors (0.3.0) + activemodel (>= 4.2, <= 5.2) + activerecord (>= 4.2, <= 5.2) + activesupport (>= 4.2, <= 5.2) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.6) + actionpack (= 5.1.6) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.6) + actionpack (= 5.1.6) + actionview (= 5.1.6) + activejob (= 5.1.6) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.6) + actionview (= 5.1.6) + activesupport (= 5.1.6) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.6) + activesupport (= 5.1.6) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.6) + activesupport (= 5.1.6) + globalid (>= 0.3.6) + activemodel (5.1.6) + activesupport (= 5.1.6) + activerecord (5.1.6) + activemodel (= 5.1.6) + activesupport (= 5.1.6) + arel (~> 8.0) + activesupport (5.1.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (8.0.0) + ast (2.4.0) + builder (3.2.3) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubi (1.7.1) + generator_spec (0.9.4) + activesupport (>= 3.0.0) + railties (>= 3.0.0) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + jaro_winkler (1.5.1) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + method_source (0.9.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + parallel (1.12.1) + parser (2.5.3.0) + ast (~> 2.4.0) + powerpack (0.1.2) + rack (2.0.6) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.1.6) + actioncable (= 5.1.6) + actionmailer (= 5.1.6) + actionpack (= 5.1.6) + actionview (= 5.1.6) + activejob (= 5.1.6) + activemodel (= 5.1.6) + activerecord (= 5.1.6) + activesupport (= 5.1.6) + bundler (>= 1.3.0) + railties (= 5.1.6) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.1.6) + actionpack (= 5.1.6) + activesupport (= 5.1.6) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rainbow (3.0.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + rubocop (0.60.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5, != 2.5.1.1) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.4.0) + ruby-progressbar (1.10.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + unicode-display_width (1.4.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + with_model (2.1.2) + activerecord (>= 4.2, < 6.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.2.0) + bundler (~> 1.6) + generator_spec (~> 0.9.4) + rails (~> 5.1.0) + rake (~> 10.4) + rspec (~> 3.8.0) + rubocop (~> 0.60.0) + setting_accessors! + sqlite3 (~> 1.3) + with_model (~> 2.1.2) + +BUNDLED WITH + 1.17.1 diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile new file mode 100644 index 0000000..5a706dc --- /dev/null +++ b/gemfiles/rails_5.2.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 5.2.0" + +gemspec path: "../" diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock new file mode 100644 index 0000000..8eefac5 --- /dev/null +++ b/gemfiles/rails_5.2.gemfile.lock @@ -0,0 +1,177 @@ +PATH + remote: .. + specs: + setting_accessors (0.3.0) + activemodel (>= 4.2, <= 5.2) + activerecord (>= 4.2, <= 5.2) + activesupport (>= 4.2, <= 5.2) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.0) + actionpack (= 5.2.0) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.0) + actionpack (= 5.2.0) + actionview (= 5.2.0) + activejob (= 5.2.0) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.0) + actionview (= 5.2.0) + activesupport (= 5.2.0) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.0) + activesupport (= 5.2.0) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.0) + activesupport (= 5.2.0) + globalid (>= 0.3.6) + activemodel (5.2.0) + activesupport (= 5.2.0) + activerecord (5.2.0) + activemodel (= 5.2.0) + activesupport (= 5.2.0) + arel (>= 9.0) + activestorage (5.2.0) + actionpack (= 5.2.0) + activerecord (= 5.2.0) + marcel (~> 0.3.1) + activesupport (5.2.0) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (9.0.0) + ast (2.4.0) + builder (3.2.3) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubi (1.7.1) + generator_spec (0.9.4) + activesupport (>= 3.0.0) + railties (>= 3.0.0) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + jaro_winkler (1.5.1) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + parallel (1.12.1) + parser (2.5.3.0) + ast (~> 2.4.0) + powerpack (0.1.2) + rack (2.0.6) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.0) + actioncable (= 5.2.0) + actionmailer (= 5.2.0) + actionpack (= 5.2.0) + actionview (= 5.2.0) + activejob (= 5.2.0) + activemodel (= 5.2.0) + activerecord (= 5.2.0) + activestorage (= 5.2.0) + activesupport (= 5.2.0) + bundler (>= 1.3.0) + railties (= 5.2.0) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.0) + actionpack (= 5.2.0) + activesupport (= 5.2.0) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rainbow (3.0.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + rubocop (0.60.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5, != 2.5.1.1) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.4.0) + ruby-progressbar (1.10.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + unicode-display_width (1.4.0) + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + with_model (2.1.2) + activerecord (>= 4.2, < 6.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.2.0) + bundler (~> 1.6) + generator_spec (~> 0.9.4) + rails (~> 5.2.0) + rake (~> 10.4) + rspec (~> 3.8.0) + rubocop (~> 0.60.0) + setting_accessors! + sqlite3 (~> 1.3) + with_model (~> 2.1.2) + +BUNDLED WITH + 1.17.1 diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index 22160b1..c0f52fb 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -25,6 +25,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ['>= 2.3', '< 3'] + spec.add_development_dependency "appraisal", '~> 2.2.0' spec.add_development_dependency 'bundler', '~> 1.6' spec.add_development_dependency 'generator_spec', '~> 0.9.4' spec.add_development_dependency 'rake', '~> 10.4' @@ -33,7 +34,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'sqlite3', '~> 1.3' spec.add_development_dependency 'with_model', '~> 2.1.2' - spec.add_dependency 'activemodel', ['>= 4.1', '<= 5.2'] - spec.add_dependency 'activerecord', ['>= 4.1', '<= 5.2'] - spec.add_dependency 'activesupport', ['>= 4.1', '<= 5.2'] + spec.add_dependency 'activemodel', ['>= 4.2', '<= 5.2'] + spec.add_dependency 'activerecord', ['>= 4.2', '<= 5.2'] + spec.add_dependency 'activesupport', ['>= 4.2', '<= 5.2'] end diff --git a/spec/support/setting_model.rb b/spec/support/setting_model.rb index e5d8b7b..f9266bd 100644 --- a/spec/support/setting_model.rb +++ b/spec/support/setting_model.rb @@ -7,7 +7,7 @@ def self.included(base) t.belongs_to :assignable, polymorphic: true, index: false t.string :name t.text :value - t.timestamps + t.timestamps null: false end model do From 5bd3a15d1540bc0740b53f290b6e168c1436fa87 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 12:18:54 +0100 Subject: [PATCH 31/60] split converters into multiple classes for better maintainability --- lib/setting_accessors.rb | 6 +- lib/setting_accessors/accessor.rb | 2 +- lib/setting_accessors/converter.rb | 74 ------------------- lib/setting_accessors/converters/base.rb | 24 ++++++ .../converters/boolean_converter.rb | 34 +++++++++ .../converters/integer_converter.rb | 11 +++ .../converters/polymorphic_converter.rb | 11 +++ .../converters/string_converter.rb | 11 +++ lib/setting_accessors/internal.rb | 3 +- lib/setting_accessors/setting_scaffold.rb | 2 +- 10 files changed, 99 insertions(+), 79 deletions(-) delete mode 100644 lib/setting_accessors/converter.rb create mode 100644 lib/setting_accessors/converters/base.rb create mode 100644 lib/setting_accessors/converters/boolean_converter.rb create mode 100644 lib/setting_accessors/converters/integer_converter.rb create mode 100644 lib/setting_accessors/converters/polymorphic_converter.rb create mode 100644 lib/setting_accessors/converters/string_converter.rb diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index da2d69b..0409463 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -6,7 +6,11 @@ require 'setting_accessors/version' require 'setting_accessors/helpers' require 'setting_accessors/accessor' -require 'setting_accessors/converter' +require 'setting_accessors/converters/base' +require 'setting_accessors/converters/boolean_converter' +require 'setting_accessors/converters/integer_converter' +require 'setting_accessors/converters/string_converter' +require 'setting_accessors/converters/polymorphic_converter' require 'setting_accessors/integration' require 'setting_accessors/integration_validator' require 'setting_accessors/internal' diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index c7ee241..aca11a7 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -51,7 +51,7 @@ def has_key?(key) def []=(key, val) set_value_was(key) set_value_before_type_cast(key, val) - @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).convert(val) + @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).new(val).convert end alias_method :set, :[]= diff --git a/lib/setting_accessors/converter.rb b/lib/setting_accessors/converter.rb deleted file mode 100644 index 702f426..0000000 --- a/lib/setting_accessors/converter.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -# -# This class hopefully will hopefully one day mimic ActiveRecord's -# attribute assigning methods, meaning that a conversion to the column type -# is done as soon as a new value is assigned by the programmer. -# -# If the value cannot be parsed in the required type, +nil+ is assigned. -# Please make sure that you specify the correct validations in settings.yml -# or assigned model to avoid this. -# -# Currently supported types: -# - Fixnum -# - String -# - Boolean -# -# If the type is 'polymorphic', it is not converted at all. -# -module SettingAccessors - class Converter - - def initialize(value_type) - @value_type = value_type - end - - # - # Converts the setting's value to the correct type - # - def convert(new_value) - # If the value is set to be polymorphic, we don't have to convert anything. - return new_value if @value_type.to_s == 'polymorphic' - - # ActiveRecord only converts non-nil values to their database type - # during assignment - return new_value if new_value.nil? - - parse_method = :"parse_#{@value_type}" - - if private_methods.include?(parse_method) - send(parse_method, new_value) - else - Rails.logger.warn("Invalid Setting type: #{@value_type}") - new_value - end - end - - private - - def parse_boolean(value) - case value - when TrueClass, FalseClass - value - when String - return true if %w[true 1].include?(value.downcase) - return false if %w[false 0].include?(value.downcase) - nil - when Fixnum - return true if value == 1 - return false if value.zero? - nil - else - nil - end - end - - def parse_integer(value) - value.to_i - end - - def parse_string(value) - value.to_s - end - end -end diff --git a/lib/setting_accessors/converters/base.rb b/lib/setting_accessors/converters/base.rb new file mode 100644 index 0000000..0750989 --- /dev/null +++ b/lib/setting_accessors/converters/base.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SettingAccessors + module Converters + class Base + attr_reader :value + + def initialize(value) + @value = value + end + + def convert + # ActiveRecord does not convert +nil+ values to the corresponding database type + return value if value.nil? + + parse_value + end + + def self.parse_value + raise NotImplementedError + end + end + end +end diff --git a/lib/setting_accessors/converters/boolean_converter.rb b/lib/setting_accessors/converters/boolean_converter.rb new file mode 100644 index 0000000..3a3d14c --- /dev/null +++ b/lib/setting_accessors/converters/boolean_converter.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module SettingAccessors + module Converters + class BooleanConverter < Base + def parse_value + case value + when TrueClass, FalseClass + value + when String + parse_string + when Integer + parse_integer + end + end + + private + + def parse_integer + return true if value == 1 + return false if value.zero? + + nil + end + + def parse_string + case value.downcase + when 'true', '1' then true + when 'false', '0' then false + end + end + end + end +end diff --git a/lib/setting_accessors/converters/integer_converter.rb b/lib/setting_accessors/converters/integer_converter.rb new file mode 100644 index 0000000..b037565 --- /dev/null +++ b/lib/setting_accessors/converters/integer_converter.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SettingAccessors + module Converters + class IntegerConverter < Base + def parse_value + value.to_i + end + end + end +end diff --git a/lib/setting_accessors/converters/polymorphic_converter.rb b/lib/setting_accessors/converters/polymorphic_converter.rb new file mode 100644 index 0000000..3bafe6a --- /dev/null +++ b/lib/setting_accessors/converters/polymorphic_converter.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SettingAccessors + module Converters + class PolymorphicConverter < Base + def parse_value + value + end + end + end +end diff --git a/lib/setting_accessors/converters/string_converter.rb b/lib/setting_accessors/converters/string_converter.rb new file mode 100644 index 0000000..4ac756d --- /dev/null +++ b/lib/setting_accessors/converters/string_converter.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SettingAccessors + module Converters + class StringConverter < Base + def parse_value + value.to_s + end + end + end +end diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 631d84b..a091326 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -55,8 +55,7 @@ def self.setting_value_type(*args) # @return [SettingAccessors::Converter] A value converter for the given type # def self.converter(value_type) - @@converters ||= {} - @@converters[value_type.to_sym] ||= SettingAccessors::Converter.new(value_type) + Converters.const_get("#{value_type.to_s.camelize}Converter") end # diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 8f9a136..82d1b94 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -201,7 +201,7 @@ def original_value # def set_value(new_value) @original_value = new_value - self.value = converter.convert(new_value) + self.value = converter.new(new_value).convert end private From a02652997e9afc9c192f414d6e75637c11d95e4f Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 12:33:06 +0100 Subject: [PATCH 32/60] Removed custom validator as we don't have globally configured settings any more and can simply use ActiveModel --- .rubocop.yml | 6 +- lib/setting_accessors.rb | 2 - lib/setting_accessors/accessor.rb | 23 --- lib/setting_accessors/integration.rb | 2 - .../integration_validator.rb | 19 --- lib/setting_accessors/setting_scaffold.rb | 16 -- lib/setting_accessors/validator.rb | 148 ------------------ spec/support/setting_model.rb | 2 - 8 files changed, 5 insertions(+), 213 deletions(-) delete mode 100644 lib/setting_accessors/integration_validator.rb delete mode 100644 lib/setting_accessors/validator.rb diff --git a/.rubocop.yml b/.rubocop.yml index c2c2f36..bd615de 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,7 @@ AllCops: TargetRubyVersion: 2.3 + Exclude: + - tmp/**/* #--------------------------------------------- # Layout @@ -63,6 +65,8 @@ Metrics/LineLength: Metrics/BlockLength: Max: 75 + Exclude: + - spec/**/*.rb # Allow longer methods Metrics/MethodLength: @@ -116,4 +120,4 @@ Style/DoubleNegation: # Allow unless/if blocks even for one-liners Style/IfUnlessModifier: - Enabled: false \ No newline at end of file + Enabled: false diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 0409463..2d6e59f 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -12,10 +12,8 @@ require 'setting_accessors/converters/string_converter' require 'setting_accessors/converters/polymorphic_converter' require 'setting_accessors/integration' -require 'setting_accessors/integration_validator' require 'setting_accessors/internal' require 'setting_accessors/setting_scaffold' -require 'setting_accessors/validator' ActiveRecord::Base.class_eval do include SettingAccessors::Integration diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index aca11a7..a41bd8f 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -135,29 +135,6 @@ def set_value_was(key) end end - # - # Validates the new setting values. - # If there is an accessor for the setting, the errors will be - # directly forwarded to it, otherwise to :base - # - # Please do not call this method directly, use the IntegrationValidator - # class instead, e.g. - # - # validates_with SettingAccessors::IntegrationValidator - # - def validate! - @temp_settings.each do |key, value| - validation_errors = SettingAccessors.setting_class.validation_errors(key, value, record) - validation_errors.each do |message| - if record.respond_to?("#{key}=") - record.errors.add(key, message) - else - record.errors.add :base, :invalid_setting, name: key, message: message - end - end - end - end - # # @return [Object] the duplicated value if it is in fact duplicable. The actual value otherwise # diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 2c1ac9b..6786e9c 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -3,8 +3,6 @@ module SettingAccessors module Integration def self.included(base) - base.validates_with SettingAccessors::IntegrationValidator - # After the main record was saved, we can save its settings. # This is necessary as the record might have been a new record # without an ID yet diff --git a/lib/setting_accessors/integration_validator.rb b/lib/setting_accessors/integration_validator.rb deleted file mode 100644 index de65617..0000000 --- a/lib/setting_accessors/integration_validator.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -# -# This class handles model validations for assigned records, e.g. -# if the settings are accessed using the Accessor class in this module. -# Only the new temp values are validated using the setting config. -# -# The main work is still done in the Accessor class, so we don't have -# to access its instance variables here, this class acts as a wrapper -# for Rails' validation chain -# - -module SettingAccessors - class IntegrationValidator < ::ActiveModel::Validator - def validate(record) - record.settings.send(:validate!) - end - end -end diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 82d1b94..4772c86 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -11,7 +11,6 @@ module SettingScaffold def self.included(base) base.extend ClassMethods - base.validates_with SettingAccessors::Validator base.serialize :value base.validates :name, @@ -101,21 +100,6 @@ def setting_record(name, assignable = nil) find_by(name: name.to_s, assignable: assignable) end - # - # Tests, if the given value would be valid for the given - # setting name. This is done in this class method due to - # the process of setting creation through assigned records - # which does not allow going the "normal" way of testing whether - # a setting was saved correctly or not. - # - # @return [Array] The validation errors for the setting's value - # - def validation_errors(name, value, assignable = nil) - s = new(name: name, value: value, assignable: assignable) - s.valid? - s.errors[:value] || [] - end - # # Makes accessing settings a little easier. # Examples: diff --git a/lib/setting_accessors/validator.rb b/lib/setting_accessors/validator.rb deleted file mode 100644 index cfc2b39..0000000 --- a/lib/setting_accessors/validator.rb +++ /dev/null @@ -1,148 +0,0 @@ -# frozen_string_literal: true - -module SettingAccessors - class Validator < ActiveModel::Validator - - def validate(record) - record.send(:validations).each do |key, requirement| - if key.to_s == 'custom' - Array(requirement).each do |validation| - run_custom_validation(record, validation) - end - elsif built_in_validation?(key) - send("validate_#{key}", record, requirement) - else - raise ArgumentError, "The invalid validation '#{key}' was given in model '#{defining_model(record).to_s}'" - end - end - end - - private - - def defining_model(record) - if SettingAccessors::Internal.globally_defined_setting?(record.name) || !record.assignable - SettingAccessors.setting_class - else - record.assignable.class - end - end - - # - # Runs a custom validation method - # The method may either be a Proc or an instance method in +record+.+class+ - # - def run_custom_validation(record, proc) - case proc - when Proc - proc.call(record) - when Symbol - if defining_model(record).respond_to?(proc) - defining_model(record).send(proc) - else - raise ArgumentError, "The method '#{proc}' was set up as validation method in model '#{defining_model(record).name}', but doesn't exist." - end - else - raise ArgumentError, "An invalid validations method was given ('#{proc}')" - end - end - - # - # @return [TrueClass, FalseClass] +true+ if the given validation - # is a built-in one. - # - def built_in_validation?(validation_name) - private_methods.include?("validate_#{validation_name}".to_sym) - end - - # - # Validates that the setting's value is given - # accepts :allow_blank and :allow_nil as options - # - def validate_presence(record, requirement) - return true unless requirement - - if requirement.is_a?(Hash) - if requirement['allow_blank'] && !record.value.nil? || - requirement['allow_nil'] && record.value.nil? || - record.value.present? - true - else - add_error record, :blank - false - end - else - add_error_if record.value.nil? || record.value == '', record, :blank - end - end - - # - # Validates numericality of the setting's value based on the options given - # in settings.yml - # - def validate_numericality(record, options) - # Test if the value is Numeric in any way (float or int) - add_error_if(!parse_value_as_numeric(record.value), record, :not_a_number) && return - - # If the validation was set to check for integer values, do that as well - add_error_if(options['only_integer'] && !parse_value_as_fixnum(record.value), record, :not_an_integer) - end - - # - # Validates whether the given value is a valid boolean - # - def validate_boolean(record, requirement) - add_error_if(requirement && parse_value_as_boolean(record.value).nil?, record, :not_a_boolean) - end - - #---------------------------------------------------------------- - # Helper Methods - #---------------------------------------------------------------- - - def add_error(record, validation, options = {}) - record.errors.add :value, validation, options - end - - def add_error_if(cond, record, validation, options = {}) - add_error(record, validation, options) if cond - cond - end - - # - # Borrowed from Rails' numericality validator - # @return [Float, NilClass] the given String value as a float or nil - # if the value was not a valid Numeric - # - def parse_value_as_numeric(raw_value) - Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ - rescue ArgumentError, TypeError - nil - end - - # - # Borrowed from Rails' numericality validator - # - # @return [Fixnum, NilClass] the given String value as an int or nil - # - def parse_value_as_fixnum(raw_value) - raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ - end - - # - # Tries to parse the given value as a boolean value - # - # @return [TrueClass, FalseClass, NilClass] - # - def parse_value_as_boolean(raw_value) - case raw_value - when TrueClass, FalseClass - raw_value - when String - return true if %w[true 1].include?(raw_value.downcase) - return false if %w[false 0].include?(raw_value.downcase) - nil - else - nil - end - end - end -end diff --git a/spec/support/setting_model.rb b/spec/support/setting_model.rb index f9266bd..df6eaf2 100644 --- a/spec/support/setting_model.rb +++ b/spec/support/setting_model.rb @@ -22,8 +22,6 @@ def self.included(base) validates :name, uniqueness: {scope: [:assignable_type, :assignable_id]}, presence: true - - validates_with SettingAccessors::Validator end end end From cec77504d96ee7ac2ea590c206f6a95514546ccf Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 19:17:56 +0100 Subject: [PATCH 33/60] Added record persistence specs, added some more or less creepy workarounds for Rails >= 5.1 --- .rubocop.yml | 3 ++ lib/setting_accessors/accessor.rb | 11 ++-- lib/setting_accessors/integration.rb | 20 ++++++- .../lib/setting_accessors/integration_spec.rb | 54 +++++++++++++++++-- 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index bd615de..71d27cf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -121,3 +121,6 @@ Style/DoubleNegation: # Allow unless/if blocks even for one-liners Style/IfUnlessModifier: Enabled: false + +Style/GuardClause: + Enabled: false diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index a41bd8f..e3b5dcd 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -33,7 +33,8 @@ def initialize(record) # even if a value that's not yet persisted exists. # def [](key, skip_cached: false) - return @temp_settings[key.to_sym] if !skip_cached && has_key?(key) + return @temp_settings[key.to_sym] if !skip_cached && key?(key) + value = SettingAccessors.setting_class.get(key, record) @temp_settings[key.to_sym] = value unless value.nil? value @@ -41,8 +42,8 @@ def [](key, skip_cached: false) alias_method :get, :[] - def has_key?(key) - @temp_settings.has_key?(key.to_sym) + def key?(key) + @temp_settings.key?(key.to_sym) end # @@ -71,7 +72,7 @@ def []=(key, val) # def get_or_default(key, store_default: true, skip_cached: false) result = get(key, skip_cached: skip_cached) - return result if result || (has_key?(key) && !skip_cached) + return result if result || (key?(key) && !skip_cached) try_dup(SettingAccessors.setting_class.get_or_default(key, record)).tap do |value| set(key, value) if store_default @@ -108,7 +109,7 @@ def value_before_type_cast(key) get(key) end - def changed_values + def changed_settings @temp_settings.select { |k, _| value_changed?(k) } end diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 6786e9c..d683570 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -8,6 +8,18 @@ def self.included(base) # without an ID yet base.after_save do settings.send(:persist!) + + # From AR 5.1 on, #_update actually checks whether the changed "attributes" are actually + # table columns or not. If no actual changed columns were found, the record is not changed and + # only the after_* callbacks are executed. + # This means that the settings are persisted, but the record's +updated_at+ column is not updated. + # + # This workaround triggers a #touch on the record in case no actual column change already + # triggered a timestamp update. + # + # TODO: This might lead to #after_commit being triggered twice, once by #update_* and once by #touch + touch if @_setting_accessors_touch_assignable + @_setting_accessors_touch_assignable = false end base.extend ClassMethods @@ -92,7 +104,13 @@ def reload(*) # @return [Hash] All changed attributes # def changed_attributes - super.merge(settings.changed_values) + super.merge(settings.changed_settings) + end + + def _update_record(*) + super.tap do |affected_rows| + @_setting_accessors_touch_assignable = affected_rows.zero? + end end def as_json(options = {}) diff --git a/spec/lib/setting_accessors/integration_spec.rb b/spec/lib/setting_accessors/integration_spec.rb index 307e884..7bcdae1 100644 --- a/spec/lib/setting_accessors/integration_spec.rb +++ b/spec/lib/setting_accessors/integration_spec.rb @@ -66,19 +66,65 @@ end #---------------------------------------------------------------- - # #save + # Record Persistence #---------------------------------------------------------------- describe '#save' do - context 'when a normal attribute was changed' do + with_model 'TestModel' do + table do |t| + t.string :string_attribute + t.timestamps null: false + end + model do + setting_accessor :string_setting, type: :string + end end - context 'when only a setting was changed' do - context 'by assigning ' do + let!(:record) do + TestModel.create(string_attribute: 'string_value', string_setting: 'string_setting_value', updated_at: 1.day.ago) + end + + shared_examples 'attribute update and touch' do |attribute_name| + it 'persists the record accordingly' do + expect(TestModel.find(record.id).send(attribute_name)).to eql 'Poiski' + end + it 'updates the +updated_at+ column accordingly' do + expect(TestModel.find(record.id).updated_at).to be_within(5.seconds).of(Time.now) end end + + shared_examples 'attribute setter variations' do |attribute_name| + context 'using a normal attribute setter and #save' do + before(:each) do + record.send("#{attribute_name}=", 'Poiski') + record.save! + end + + include_examples 'attribute update and touch', attribute_name + end + + context 'using #update_attribute' do + before(:each) { record.update_attribute(attribute_name, 'Poiski') } + + include_examples 'attribute update and touch', attribute_name + end + + context 'using mass assignment (update_attributes)' do + before(:each) { record.update_attributes(attribute_name => 'Poiski') } + + include_examples 'attribute update and touch', attribute_name + end + end + + context 'when a normal attribute was changed' do + include_examples 'attribute setter variations', :string_attribute + end + + context 'when only a setting was changed' do + include_examples 'attribute setter variations', :string_setting + end end #---------------------------------------------------------------- From a20392077696af9f2da7b6769ecb7fbdbfb9bf18 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 19:24:43 +0100 Subject: [PATCH 34/60] Only trigger an additional #touch if AR is actually >= 5.1 --- lib/setting_accessors/integration.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index d683570..bc24f90 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -109,7 +109,10 @@ def changed_attributes def _update_record(*) super.tap do |affected_rows| - @_setting_accessors_touch_assignable = affected_rows.zero? + # Workaround to trigger a #touch if necessary, see +after_save+ callback further up + if Gem.loaded_specs['activerecord'].version >= Gem::Version.create('5.1') + @_setting_accessors_touch_assignable = affected_rows.zero? + end end end From 49d4de22a9e3b11a651f5858e315cdd0500a88b8 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 20:31:59 +0100 Subject: [PATCH 35/60] Only even define the override for #_update if the loaded AR version is high enough --- lib/setting_accessors/integration.rb | 14 ++++++++++---- spec/lib/setting_accessors/integration_spec.rb | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index bc24f90..98d3723 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -107,10 +107,16 @@ def changed_attributes super.merge(settings.changed_settings) end - def _update_record(*) - super.tap do |affected_rows| - # Workaround to trigger a #touch if necessary, see +after_save+ callback further up - if Gem.loaded_specs['activerecord'].version >= Gem::Version.create('5.1') + # + # Marks the record to be #touch'd after updating it in case no actual + # attributes were involved and only settings were changed. + # This is necessary for ActiveRecord >= 5.1 which will not perform the + # timestamp updates if it thinks nothing actually changed. + # + if Gem.loaded_specs['activerecord'].version >= Gem::Version.create('5.1') + def _update_record(*) + super.tap do |affected_rows| + # Workaround to trigger a #touch if necessary, see +after_save+ callback further up @_setting_accessors_touch_assignable = affected_rows.zero? end end diff --git a/spec/lib/setting_accessors/integration_spec.rb b/spec/lib/setting_accessors/integration_spec.rb index 7bcdae1..fa5faf5 100644 --- a/spec/lib/setting_accessors/integration_spec.rb +++ b/spec/lib/setting_accessors/integration_spec.rb @@ -66,10 +66,10 @@ end #---------------------------------------------------------------- - # Record Persistence + # #create_or_update #---------------------------------------------------------------- - describe '#save' do + describe '#create_or_update' do with_model 'TestModel' do table do |t| t.string :string_attribute From 69c3d38c5c0af77e5832ebc912684acf42133f8a Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 21:04:57 +0100 Subject: [PATCH 36/60] Added simple specs for Integration#reload --- spec/lib/setting_accessors/integration_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/lib/setting_accessors/integration_spec.rb b/spec/lib/setting_accessors/integration_spec.rb index fa5faf5..fa806c9 100644 --- a/spec/lib/setting_accessors/integration_spec.rb +++ b/spec/lib/setting_accessors/integration_spec.rb @@ -132,7 +132,24 @@ #---------------------------------------------------------------- describe '#reload' do + with_model 'TestModel' do + model do + setting_accessor :a_string, type: :string, default: 'Oiski' + end + end + it 'refreshes settings from the database' do + record = TestModel.create + expect(record.a_string).to eql 'Oiski' + TestModel.find(record.id).update_attributes(a_string: 'Poiski') + expect { record.reload }.to change(record, :a_string).from('Oiski').to('Poiski') + end + + it 'discards locally changed but not persisted settings' do + record = TestModel.create + record.a_string = 'Poiski' + expect { record.reload }.to change(record, :a_string).from('Poiski').to('Oiski') + end end #---------------------------------------------------------------- From 77cadcf5977f9eac917300fddebd5724d0e600fd Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 21:10:31 +0100 Subject: [PATCH 37/60] Removed more no longer used methods from 0.x --- lib/setting_accessors/helpers.rb | 1 + lib/setting_accessors/integration.rb | 4 +-- lib/setting_accessors/internal.rb | 6 ++-- lib/setting_accessors/setting_scaffold.rb | 38 ----------------------- 4 files changed, 4 insertions(+), 45 deletions(-) diff --git a/lib/setting_accessors/helpers.rb b/lib/setting_accessors/helpers.rb index b630497..1e8fd39 100644 --- a/lib/setting_accessors/helpers.rb +++ b/lib/setting_accessors/helpers.rb @@ -19,6 +19,7 @@ def lookup_nested_hash(hash, *keys) h = hash keys.each do |key| fail NestedHashKeyNotFoundException unless h.key?(key) + h = h[key] end h diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 98d3723..01d7e51 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -89,9 +89,7 @@ def setting_accessor(setting_name, options = {}) # #=> true # def reload(*) - super - @settings_accessor = nil - self + super.tap { @settings_accessor = nil } end # diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index a091326..ec507de 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -7,10 +7,8 @@ module SettingAccessors module Internal extend Helpers - class << self - def class_settings - @@class_settings ||= {} - end + def self.class_settings + @@class_settings ||= {} end # diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 4772c86..e8e95e3 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -126,33 +126,6 @@ def method_missing(method, *args, &block) end end - # - # @return [String] the localized setting name - # they are stored in config/locales/settings.LOCALE.yml - # - def localized_name - i18n_lookup(:name) - end - - # - # @return [String] the localized setting description - # see #localized_name - # - def localized_description - i18n_lookup(:description) - end - - # - # Performs an I18n lookup in the settings locale. - # Class based settings are store in 'settings.CLASS.NAME', globally defined settings - # in 'settings.global.NAME' - # - def i18n_lookup(key, options = {}) - options[:scope] = [:settings, :global, name] - options[:scope] = [:settings, assignable.class.to_s.underscore, name] unless SettingAccessors::Internal.globally_defined_setting?(name) - I18n.t(key, options) - end - # # @return [Object] the default value for the current setting # @@ -194,17 +167,6 @@ def converter @converter ||= SettingAccessors::Internal.converter(value_type) end - def value_required? - !!validations['required'] - end - - # - # Accessor to the validations part of the setting's data - # - def validations - data['validates'] || {} - end - # # @see {SettingAccessors::Internal#setting_data} for more information # From fa91b8b964b1c21c9597e44e0c18688a0fab03e9 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:02:57 +0100 Subject: [PATCH 38/60] Added specs for converters and learned a lot about ActiveRecord's internal conversions. --- lib/setting_accessors/accessor.rb | 8 +-- .../converters/boolean_converter.rb | 21 +++++++- .../converters/integer_converter.rb | 10 ++++ .../converters/boolean_converter_spec.rb | 23 ++++++++ .../converters/integer_converter_spec.rb | 18 +++++++ .../converters/polymorphic_converter_spec.rb | 13 +++++ .../converters/string_converter_spec.rb | 17 ++++++ spec/spec_helper.rb | 1 + spec/support/matchers/converters.rb | 53 +++++++++++++++++++ 9 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 spec/lib/setting_accessors/converters/boolean_converter_spec.rb create mode 100644 spec/lib/setting_accessors/converters/integer_converter_spec.rb create mode 100644 spec/lib/setting_accessors/converters/polymorphic_converter_spec.rb create mode 100644 spec/lib/setting_accessors/converters/string_converter_spec.rb create mode 100644 spec/support/matchers/converters.rb diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index e3b5dcd..29454d4 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -32,7 +32,7 @@ def initialize(record) # If set to +true+, the setting value is freshly loaded from the database, # even if a value that's not yet persisted exists. # - def [](key, skip_cached: false) + def get(key, skip_cached: false) return @temp_settings[key.to_sym] if !skip_cached && key?(key) value = SettingAccessors.setting_class.get(key, record) @@ -40,7 +40,7 @@ def [](key, skip_cached: false) value end - alias_method :get, :[] + alias_method :[], :get def key?(key) @temp_settings.key?(key.to_sym) @@ -49,13 +49,13 @@ def key?(key) # # Writes a setting's value # - def []=(key, val) + def set(key, val) set_value_was(key) set_value_before_type_cast(key, val) @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).new(val).convert end - alias_method :set, :[]= + alias_method :[]=, :set # # Tries to find a setting for this record. diff --git a/lib/setting_accessors/converters/boolean_converter.rb b/lib/setting_accessors/converters/boolean_converter.rb index 3a3d14c..52560ad 100644 --- a/lib/setting_accessors/converters/boolean_converter.rb +++ b/lib/setting_accessors/converters/boolean_converter.rb @@ -11,14 +11,29 @@ def parse_value parse_string when Integer parse_integer + else + parse_other_value end end private + # + # Special handler for Rails 4.2, see the following deprecation warning: + # + # DEPRECATION WARNING: You attempted to assign a value which is not explicitly `true` or `false` (0.0) + # to a boolean column. Currently this value casts to `false`. + # This will change to match Ruby's semantics, and will cast to `true` in Rails 5. + # If you would like to maintain the current behavior, you should explicitly handle + # the values you would like cast to `false`. + # + def parse_other_value + Gem.loaded_specs['activerecord'].version >= Gem::Version.create('5.0') + end + def parse_integer - return true if value == 1 - return false if value.zero? + return true if value.to_i == 1 + return false if value.to_i.zero? nil end @@ -27,6 +42,8 @@ def parse_string case value.downcase when 'true', '1' then true when 'false', '0' then false + when '' then nil + else parse_other_value end end end diff --git a/lib/setting_accessors/converters/integer_converter.rb b/lib/setting_accessors/converters/integer_converter.rb index b037565..80fc086 100644 --- a/lib/setting_accessors/converters/integer_converter.rb +++ b/lib/setting_accessors/converters/integer_converter.rb @@ -4,7 +4,17 @@ module SettingAccessors module Converters class IntegerConverter < Base def parse_value + return parse_boolean if value == true || value == false + value.to_i + rescue NoMethodError + nil + end + + private + + def parse_boolean + value ? 1 : 0 end end end diff --git a/spec/lib/setting_accessors/converters/boolean_converter_spec.rb b/spec/lib/setting_accessors/converters/boolean_converter_spec.rb new file mode 100644 index 0000000..a847150 --- /dev/null +++ b/spec/lib/setting_accessors/converters/boolean_converter_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +describe SettingAccessors::Converters::BooleanConverter do + subject { described_class } + + with_model 'TestModel' do + table do |t| + t.boolean :boolean_attribute + end + end + + it { is_expected.to convert(1).similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert(1.0).similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert('true').similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert('false').similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert('1').similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert('0').similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert('').similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert('Oiski').similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert(true).similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert(false).similar_to(TestModel.new).on(:boolean_attribute) } + it { is_expected.to convert(nil).similar_to(TestModel.new).on(:boolean_attribute) } +end diff --git a/spec/lib/setting_accessors/converters/integer_converter_spec.rb b/spec/lib/setting_accessors/converters/integer_converter_spec.rb new file mode 100644 index 0000000..91d6c48 --- /dev/null +++ b/spec/lib/setting_accessors/converters/integer_converter_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +describe SettingAccessors::Converters::IntegerConverter do + subject { described_class } + + with_model 'TestModel' do + table do |t| + t.integer :integer_attribute + end + end + + it { is_expected.to convert(1).similar_to(TestModel.new).on(:integer_attribute) } + it { is_expected.to convert(1.0).similar_to(TestModel.new).on(:integer_attribute) } + it { is_expected.to convert('Oiski').similar_to(TestModel.new).on(:integer_attribute) } + it { is_expected.to convert(true).similar_to(TestModel.new).on(:integer_attribute) } + it { is_expected.to convert(false).similar_to(TestModel.new).on(:integer_attribute) } + it { is_expected.to convert(nil).similar_to(TestModel.new).on(:integer_attribute) } +end diff --git a/spec/lib/setting_accessors/converters/polymorphic_converter_spec.rb b/spec/lib/setting_accessors/converters/polymorphic_converter_spec.rb new file mode 100644 index 0000000..88109e3 --- /dev/null +++ b/spec/lib/setting_accessors/converters/polymorphic_converter_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +describe SettingAccessors::Converters::PolymorphicConverter do + subject { described_class } + + it { is_expected.to convert(1).to(1) } + it { is_expected.to convert('Oiski').to('Oiski') } + it { is_expected.to convert(true).to(true) } + it { is_expected.to convert(false).to(false) } + it { is_expected.to convert(nil).to(nil) } + it { is_expected.to convert(a: :b).to(a: :b) } + it { is_expected.to convert([1, 2, 3]).to([1, 2, 3]) } +end diff --git a/spec/lib/setting_accessors/converters/string_converter_spec.rb b/spec/lib/setting_accessors/converters/string_converter_spec.rb new file mode 100644 index 0000000..ff16f2c --- /dev/null +++ b/spec/lib/setting_accessors/converters/string_converter_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +describe SettingAccessors::Converters::StringConverter do + subject { described_class } + + with_model 'TestModel' do + table do |t| + t.string :string_attribute + end + end + + it { is_expected.to convert(1).similar_to(TestModel.new).on(:string_attribute) } + it { is_expected.to convert('Oiski').similar_to(TestModel.new).on(:string_attribute) } + it { is_expected.to convert(true).to('true') } + it { is_expected.to convert(false).to('false') } + it { is_expected.to convert(nil).similar_to(TestModel.new).on(:string_attribute) } +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7422209..39d9e37 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,6 +25,7 @@ require 'setting_accessors' require_relative 'support/setting_model' require_relative 'support/helpers' +require_relative 'support/matchers/converters' RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate diff --git a/spec/support/matchers/converters.rb b/spec/support/matchers/converters.rb new file mode 100644 index 0000000..15f8428 --- /dev/null +++ b/spec/support/matchers/converters.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# +# Specialized matcher to test whether a Converter processes its inputs correctly. +# It can either test for a given expected value or compare the result +# to ActiveRecord's internal mechanisms. +# Please note that the latter may differ between database adapters, so use with care. +# +# @example +# expect(SettingAccessors::Converters::StringConverter).to convert(1).to('1') +# +# @example Test whether the converter produces the same output as ActiveRecord +# expect(SettingAccessors::Converters::StringConverter).to convert(1).similar_to(User.first).on(:first_name) +# +RSpec::Matchers.define :convert do |value| + match do |converter_class| + @errors = [] + converter = converter_class.new(value) + converted_value = converter.convert + + unless instance_variable_defined?('@expected_value') + @expected_value = begin + @record.send("#{@attribute}=", value) + @record.send(@attribute) + end + end + + if converted_value != @expected_value + message = "Expected #{converter_class}" + message += "\n to convert #{value.inspect} to #{@expected_value.inspect}" + message += ",\n but actual output was #{converted_value.inspect}" + @errors << message + end + + @errors.empty? + end + + chain :to do |expected_value| + @expected_value = expected_value + end + + chain :similar_to do |record| + @record = record + end + + chain :on do |attribute| + @attribute = attribute + end + + failure_message do + @errors.join("\n") + end +end From 60e750ea28f4185054e6b6a2fa9f51868a21b2a8 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:06:29 +0100 Subject: [PATCH 39/60] rubocop: Style/Alias --- lib/setting_accessors/accessor.rb | 4 ++-- lib/setting_accessors/setting_scaffold.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/accessor.rb index 29454d4..76c3c88 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/accessor.rb @@ -40,7 +40,7 @@ def get(key, skip_cached: false) value end - alias_method :[], :get + alias [] get def key?(key) @temp_settings.key?(key.to_sym) @@ -55,7 +55,7 @@ def set(key, val) @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).new(val).convert end - alias_method :[]=, :set + alias []= set # # Tries to find a setting for this record. diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index e8e95e3..f98a7db 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -37,7 +37,7 @@ def get(name, assignable = nil) setting_record(name, assignable).try(:value) end - alias_method :[], :get + alias [] get # # Tries to look the setting up using #get, if no existing setting is found, @@ -80,7 +80,7 @@ def set(name, value, assignable: nil) end.value end - alias_method :[]=, :set + alias []= set # # @return [Object] the default value for the given setting From 76b434f89482d91ea8b565e941d624f42111cd61 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:06:56 +0100 Subject: [PATCH 40/60] rubocop: Layout/EmptyLineAfterGuardClause --- lib/setting_accessors/setting_scaffold.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index f98a7db..2ef031a 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -121,6 +121,7 @@ def method_missing(method, *args, &block) set(method_name[0..-2], args.first) else return super(method, *args, &block) if args.size > 1 + get(method_name, args.first) end end From a1f0c22fd191c38a174510bdba0bcafae937de8d Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:08:21 +0100 Subject: [PATCH 41/60] rubocop: Style/StringLiterals --- .rubocop.yml | 1 + gemfiles/rails_4.1.gemfile | 6 +++--- gemfiles/rails_4.2.gemfile | 6 +++--- gemfiles/rails_5.0.gemfile | 6 +++--- gemfiles/rails_5.1.gemfile | 6 +++--- gemfiles/rails_5.2.gemfile | 6 +++--- setting_accessors.gemspec | 2 +- spec/spec_helper.rb | 2 +- 8 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 71d27cf..477aad1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,7 @@ AllCops: TargetRubyVersion: 2.3 Exclude: - tmp/**/* + - gemfiles/**/* #--------------------------------------------- # Layout diff --git a/gemfiles/rails_4.1.gemfile b/gemfiles/rails_4.1.gemfile index f4a887f..6c79f3c 100644 --- a/gemfiles/rails_4.1.gemfile +++ b/gemfiles/rails_4.1.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "rails", "~> 4.1.0" +gem 'rails', '~> 4.1.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 6977eb0..dbed7dd 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "rails", "~> 4.2.0" +gem 'rails', '~> 4.2.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile index 10f52e7..49df964 100644 --- a/gemfiles/rails_5.0.gemfile +++ b/gemfiles/rails_5.0.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "rails", "~> 5.0.0" +gem 'rails', '~> 5.0.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile index 6100e83..953d45c 100644 --- a/gemfiles/rails_5.1.gemfile +++ b/gemfiles/rails_5.1.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "rails", "~> 5.1.0" +gem 'rails', '~> 5.1.0' -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile index 5a706dc..0b87fd8 100644 --- a/gemfiles/rails_5.2.gemfile +++ b/gemfiles/rails_5.2.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source "https://rubygems.org" +source 'https://rubygems.org' -gem "rails", "~> 5.2.0" +gem 'rails', '~> 5.2.0' -gemspec path: "../" +gemspec path: '../' diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index c0f52fb..800e608 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ['>= 2.3', '< 3'] - spec.add_development_dependency "appraisal", '~> 2.2.0' + spec.add_development_dependency 'appraisal', '~> 2.2.0' spec.add_development_dependency 'bundler', '~> 1.6' spec.add_development_dependency 'generator_spec', '~> 0.9.4' spec.add_development_dependency 'rake', '~> 10.4' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 39d9e37..107d610 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,7 +19,7 @@ require 'active_record' # Hold the test sqlite database in memory without actually creating additional files -ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:" +ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:' require 'with_model' require 'setting_accessors' From 550f7893aaf248ff73c663f86b2bce75a982d211 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:08:45 +0100 Subject: [PATCH 42/60] rubocop: Layout/IndentationWidth --- .../setting_accessors/setting_scaffold_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb index cb5bbb6..fc0d03e 100644 --- a/spec/lib/setting_accessors/setting_scaffold_spec.rb +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -11,14 +11,14 @@ describe 'method_missing' do context 'when a possible getter is being called' do - context 'with an additional argument (assignable)' do - let(:assignable) { User.create } - - it 'forwards the call to Setting.get' do - expect(Setting).to receive(:get).with('foo', assignable) - Setting.foo(assignable) - end - end + context 'with an additional argument (assignable)' do + let(:assignable) { User.create } + + it 'forwards the call to Setting.get' do + expect(Setting).to receive(:get).with('foo', assignable) + Setting.foo(assignable) + end + end context 'with no additional arguments' do it 'forwards the call to Setting.get' do From 48e4cfd82b5f0e87e0159fc67e62edb9772aa76f Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:09:13 +0100 Subject: [PATCH 43/60] rubocop: Layout/BlockAlignment --- spec/lib/setting_accessors/setting_scaffold_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb index fc0d03e..3c2bd9c 100644 --- a/spec/lib/setting_accessors/setting_scaffold_spec.rb +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -32,7 +32,7 @@ expect { Setting.foo('bar', 'baz') }.to raise_error NoMethodError end end - end + end context 'when a setter is being called' do context 'with the new value as its argument' do From 8bc48702d13aa5156d98575caf2789530ab1c69f Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:09:39 +0100 Subject: [PATCH 44/60] rubocop: Layout/IndentationConsistency --- .../setting_scaffold_spec.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb index 3c2bd9c..1f11f06 100644 --- a/spec/lib/setting_accessors/setting_scaffold_spec.rb +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -20,18 +20,18 @@ end end - context 'with no additional arguments' do - it 'forwards the call to Setting.get' do - expect(Setting).to receive(:get).with('foo', nil) - Setting.foo - end - end + context 'with no additional arguments' do + it 'forwards the call to Setting.get' do + expect(Setting).to receive(:get).with('foo', nil) + Setting.foo + end + end - context 'with more than one argument' do - it 'calls the original method_missing, leading to a NoMethodError' do - expect { Setting.foo('bar', 'baz') }.to raise_error NoMethodError - end - end + context 'with more than one argument' do + it 'calls the original method_missing, leading to a NoMethodError' do + expect { Setting.foo('bar', 'baz') }.to raise_error NoMethodError + end + end end context 'when a setter is being called' do From 3f7d2a3f10d4043e47df7d28c78be567775fb997 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:11:52 +0100 Subject: [PATCH 45/60] rubocop: Style/MethodMissingSuper --- lib/setting_accessors/setting_scaffold.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 2ef031a..2682679 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -118,12 +118,12 @@ def method_missing(method, *args, &block) method_name = method.to_s if method_name.last == '=' - set(method_name[0..-2], args.first) - else - return super(method, *args, &block) if args.size > 1 - - get(method_name, args.first) + return set(method_name[0..-2], args.first) + elsif args.size <= 1 + return get(method_name, args.first) end + + super end end From 3ef4bacae780f823d708dcfb68a5a5ec3a019551 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:16:26 +0100 Subject: [PATCH 46/60] Disabled Style/MissingRespondToMissing for Setting as it really doesn't make much sense here --- lib/setting_accessors/setting_scaffold.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 2682679..2d6dc67 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -114,7 +114,7 @@ def setting_record(name, assignable = nil) # #+some_cool_user+ is here an instance of ActiveRecord::Base # Setting.cool_setting(some_cool_user) # - def method_missing(method, *args, &block) + def method_missing(method, *args, &block) # rubocop:disable Style/MissingRespondToMissing method_name = method.to_s if method_name.last == '=' From 266b62a466c9e80e2d3803837f1257b2ecdba493 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:17:44 +0100 Subject: [PATCH 47/60] rubocop: remaining minor fixes --- lib/generators/setting_accessors/install_generator.rb | 2 +- lib/setting_accessors/internal.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/generators/setting_accessors/install_generator.rb b/lib/generators/setting_accessors/install_generator.rb index a6b5796..884cc1a 100644 --- a/lib/generators/setting_accessors/install_generator.rb +++ b/lib/generators/setting_accessors/install_generator.rb @@ -9,7 +9,7 @@ class InstallGenerator < Rails::Generators::Base argument :model_name, type: :string, default: 'Setting' - def self.next_migration_number(_) + def self.next_migration_number(_nbr) if @prev_migration_nr @prev_migration_nr += 1 else diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index ec507de..a5b177a 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -20,7 +20,7 @@ def self.class_settings def self.set_class_setting(klass, setting_name, options = {}) # If there are no options given, the setting *has* to be defined globally. if options.empty? - raise ArgumentError, "The setting '#{setting_name}' in model '#{klass.to_s}' is lacking options." + raise ArgumentError, "The setting '#{setting_name}' in model '#{klass}' is lacking options." # If the setting is defined on class base, we have to store its options else ensure_nested_hash!(class_settings, klass.to_s) From 2782739cbf0ae8bed9cefd8833040c70324e556a Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Sun, 25 Nov 2018 22:21:01 +0100 Subject: [PATCH 48/60] codeclimate does not respect it when cops are disabled in the source --- .rubocop.yml | 4 ++++ lib/setting_accessors/setting_scaffold.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 477aad1..0b58bf2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -125,3 +125,7 @@ Style/IfUnlessModifier: Style/GuardClause: Enabled: false + +Style/MissingRespondToMissing: + Exclude: + - lib/setting_accessors/setting_scaffold.rb diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 2d6dc67..2682679 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -114,7 +114,7 @@ def setting_record(name, assignable = nil) # #+some_cool_user+ is here an instance of ActiveRecord::Base # Setting.cool_setting(some_cool_user) # - def method_missing(method, *args, &block) # rubocop:disable Style/MissingRespondToMissing + def method_missing(method, *args, &block) method_name = method.to_s if method_name.last == '=' From 04176c56b6c99b1c7a98adc0ab1bd33db4a46ff3 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 13:00:09 +0100 Subject: [PATCH 49/60] Re-Added SettingScaffold.[]=, turns out, it was there for a reason... --- lib/setting_accessors/setting_scaffold.rb | 10 ++- .../setting_scaffold_spec.rb | 82 ++++++++++++++----- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index 2682679..bac5c58 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -80,7 +80,15 @@ def set(name, value, assignable: nil) end.value end - alias []= set + # + # An alias for #set with a slightly different API. + # This allows the following usage: + # Setting['my_setting', my_assignable] ||= new_value + # + def []=(name, *args) + assignable = args.size > 1 ? args.first : nil + set(name, args.last, assignable: assignable) + end # # @return [Object] the default value for the given setting diff --git a/spec/lib/setting_accessors/setting_scaffold_spec.rb b/spec/lib/setting_accessors/setting_scaffold_spec.rb index 1f11f06..8d38f5f 100644 --- a/spec/lib/setting_accessors/setting_scaffold_spec.rb +++ b/spec/lib/setting_accessors/setting_scaffold_spec.rb @@ -11,27 +11,27 @@ describe 'method_missing' do context 'when a possible getter is being called' do - context 'with an additional argument (assignable)' do - let(:assignable) { User.create } - - it 'forwards the call to Setting.get' do - expect(Setting).to receive(:get).with('foo', assignable) - Setting.foo(assignable) - end - end - - context 'with no additional arguments' do - it 'forwards the call to Setting.get' do - expect(Setting).to receive(:get).with('foo', nil) - Setting.foo - end - end - - context 'with more than one argument' do - it 'calls the original method_missing, leading to a NoMethodError' do - expect { Setting.foo('bar', 'baz') }.to raise_error NoMethodError - end - end + context 'with an additional argument (assignable)' do + let(:assignable) { User.create } + + it 'forwards the call to Setting.get' do + expect(Setting).to receive(:get).with('foo', assignable) + Setting.foo(assignable) + end + end + + context 'with no additional arguments' do + it 'forwards the call to Setting.get' do + expect(Setting).to receive(:get).with('foo', nil) + Setting.foo + end + end + + context 'with more than one argument' do + it 'calls the original method_missing, leading to a NoMethodError' do + expect { Setting.foo('bar', 'baz') }.to raise_error NoMethodError + end + end end context 'when a setter is being called' do @@ -128,4 +128,44 @@ end end end + + #---------------------------------------------------------------- + # .[]= + #---------------------------------------------------------------- + + describe '.[]=' do + context 'when being called without an assignable' do + it 'sets the global setting accordingly' do + expect(Setting).to receive(:set).with('foo', 'bar', assignable: nil) + Setting['foo'] = 'bar' + end + end + + context 'when being called with an assignable' do + let(:assignable) { User.create } + + it 'sets the assigned setting accordingly' do + expect(Setting).to receive(:set).with('foo', 'bar', assignable: assignable) + Setting['foo', assignable] = 'bar' + end + end + + context 'when being used with ||=' do + it "sets the setting value if it doesn't exist yet" do + expect(Setting.foo).to be nil + Setting['foo'] ||= 'bar' + expect(Setting.foo).to eql 'bar' + Setting['foo'] ||= 'baz' + expect(Setting.foo).to eql 'bar' + + assignable = User.create + + expect(Setting.foo(assignable)).to be nil + Setting['foo', assignable] ||= 'bar' + expect(Setting.foo(assignable)).to eql 'bar' + Setting['foo', assignable] ||= 'baz' + expect(Setting.foo(assignable)).to eql 'bar' + end + end + end end From 60a43c2f4fc94406736a57ef2598348da77dea29 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 13:31:48 +0100 Subject: [PATCH 50/60] Refactored setting_accessor method --- lib/setting_accessors.rb | 1 + lib/setting_accessors/accessor_generator.rb | 59 +++++++++++++++++++++ lib/setting_accessors/integration.rb | 38 ++----------- lib/setting_accessors/internal.rb | 1 + 4 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 lib/setting_accessors/accessor_generator.rb diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 2d6e59f..0f46652 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -5,6 +5,7 @@ require 'setting_accessors/version' require 'setting_accessors/helpers' +require 'setting_accessors/accessor_generator' require 'setting_accessors/accessor' require 'setting_accessors/converters/base' require 'setting_accessors/converters/boolean_converter' diff --git a/lib/setting_accessors/accessor_generator.rb b/lib/setting_accessors/accessor_generator.rb new file mode 100644 index 0000000..bf3c2c0 --- /dev/null +++ b/lib/setting_accessors/accessor_generator.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# +# This class handles creating everything necessary for a new setting_accessor +# and the actual method assignment in the calling class. +# +module SettingAccessors + class AccessorGenerator + attr_reader :setting_name + attr_reader :options + + def initialize(setting_name, **options) + @setting_name = setting_name + @options = options + end + + def assign_setting!(klass) + SettingAccessors::Internal.set_class_setting(klass, setting_name, options) + define_getters(klass) + define_setter(klass) + define_active_record_helpers(klass) + end + + private + + def define_getters(klass, outer_setting_name = setting_name) + klass.define_method(setting_name) do + settings.get_or_default(outer_setting_name) + end + + # Getter alias for boolean settings + setting_type = SettingAccessors::Internal.setting_value_type(setting_name, klass).to_sym + klass.alias_method "#{outer_setting_name}?", outer_setting_name if setting_type == :boolean + end + + def define_setter(klass, outer_setting_name = setting_name) + klass.define_method("#{setting_name}=") do |new_value| + settings[outer_setting_name] = new_value + end + end + + def define_active_record_helpers(klass, outer_setting_name = setting_name) + # NAME_was + klass.define_method("#{setting_name}_was") do + settings.value_was(outer_setting_name) + end + + # NAME_before_type_cast + klass.define_method("#{setting_name}_before_type_cast") do + settings.value_before_type_cast(outer_setting_name) + end + + # NAME_changed? + klass.define_method("#{setting_name}_changed?") do + settings.value_changed?(outer_setting_name) + end + end + end +end diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index 01d7e51..c695694 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -36,41 +36,9 @@ module ClassMethods # @param [Hash] options # Options to customize the behaviour of the generated accessor # - def setting_accessor(setting_name, options = {}) - SettingAccessors::Internal.set_class_setting(self, setting_name, options) - - setting_type = SettingAccessors::Internal.setting_value_type(setting_name, self).to_sym - - # Add the setting's name to the list of setting_accessors for this class - SettingAccessors::Internal.add_setting_accessor_name(self, setting_name) - - # Getter - define_method(setting_name) do - settings.get_or_default(setting_name) - end - - # Getter alias for boolean settings - alias_method "#{setting_name}?", setting_name if setting_type == :boolean - - # Setter - define_method("#{setting_name}=") do |new_value| - settings[setting_name] = new_value - end - - # NAME_was - define_method("#{setting_name}_was") do - settings.value_was(setting_name) - end - - # NAME_before_type_cast - define_method("#{setting_name}_before_type_cast") do - settings.value_before_type_cast(setting_name) - end - - # NAME_changed? - define_method("#{setting_name}_changed?") do - settings.value_changed?(setting_name) - end + def setting_accessor(setting_name, **options) + generator = AccessorGenerator.new(setting_name, **options) + generator.assign_setting!(self) end end diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index a5b177a..7ac07d6 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -25,6 +25,7 @@ def self.set_class_setting(klass, setting_name, options = {}) else ensure_nested_hash!(class_settings, klass.to_s) class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys + add_setting_accessor_name(klass, setting_name) end end From 01d17f1851749986b8d3c7e8c50c1d2da780bac6 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 13:35:49 +0100 Subject: [PATCH 51/60] Renamed Accessor class to SettingSet --- lib/setting_accessors.rb | 2 +- lib/setting_accessors/integration.rb | 4 ++-- lib/setting_accessors/{accessor.rb => setting_set.rb} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename lib/setting_accessors/{accessor.rb => setting_set.rb} (99%) diff --git a/lib/setting_accessors.rb b/lib/setting_accessors.rb index 0f46652..cb4a81b 100644 --- a/lib/setting_accessors.rb +++ b/lib/setting_accessors.rb @@ -6,7 +6,6 @@ require 'setting_accessors/version' require 'setting_accessors/helpers' require 'setting_accessors/accessor_generator' -require 'setting_accessors/accessor' require 'setting_accessors/converters/base' require 'setting_accessors/converters/boolean_converter' require 'setting_accessors/converters/integer_converter' @@ -15,6 +14,7 @@ require 'setting_accessors/integration' require 'setting_accessors/internal' require 'setting_accessors/setting_scaffold' +require 'setting_accessors/setting_set' ActiveRecord::Base.class_eval do include SettingAccessors::Integration diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index c695694..bc6ac33 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -57,7 +57,7 @@ def setting_accessor(setting_name, **options) # #=> true # def reload(*) - super.tap { @settings_accessor = nil } + super.tap { @settings = nil } end # @@ -104,7 +104,7 @@ def as_json(options = {}) end def settings - @settings_accessor ||= SettingAccessors::Accessor.new(self) + @settings ||= SettingAccessors::SettingSet.new(self) end end end diff --git a/lib/setting_accessors/accessor.rb b/lib/setting_accessors/setting_set.rb similarity index 99% rename from lib/setting_accessors/accessor.rb rename to lib/setting_accessors/setting_set.rb index 76c3c88..7d7603b 100644 --- a/lib/setting_accessors/accessor.rb +++ b/lib/setting_accessors/setting_set.rb @@ -4,7 +4,7 @@ # Helper class to make accessing record specific settings easier # module SettingAccessors - class Accessor + class SettingSet include ::SettingAccessors::Helpers attr_reader :record From fccf37f3e0282621149bb2d9afc087e8d2c840aa Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 14:16:11 +0100 Subject: [PATCH 52/60] Refactored #as_json --- lib/setting_accessors/integration.rb | 9 +-------- lib/setting_accessors/internal.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/setting_accessors/integration.rb b/lib/setting_accessors/integration.rb index bc6ac33..b95d1aa 100644 --- a/lib/setting_accessors/integration.rb +++ b/lib/setting_accessors/integration.rb @@ -90,14 +90,7 @@ def _update_record(*) def as_json(options = {}) super.tap do |json| - setting_names = SettingAccessors::Internal.setting_accessor_names(self.class) - if options[:only] - setting_names &= Array(options[:only]).map(&:to_s) - elsif options[:except] - setting_names -= Array(options[:except]).map(&:to_s) - end - - setting_names.each do |setting_name| + SettingAccessors::Internal.json_setting_names(self.class, **options).each do |setting_name| json[setting_name.to_s] = send(setting_name) end end diff --git a/lib/setting_accessors/internal.rb b/lib/setting_accessors/internal.rb index 7ac07d6..ff5ad1a 100644 --- a/lib/setting_accessors/internal.rb +++ b/lib/setting_accessors/internal.rb @@ -85,5 +85,20 @@ def self.setting_accessor_names(klass) lookup_nested_hash(@@setting_accessor_names, klass.to_s) || [] end + # + # Mainly a helper for #as_json calls. + # Evaluates the given options and determines the setting names to be returned. + # + def self.json_setting_names(klass, **options) + setting_names = setting_accessor_names(klass) + + if options[:only] + setting_names & Array(options[:only]).map(&:to_s) + elsif options[:except] + setting_names - Array(options[:except]).map(&:to_s) + else + setting_names + end + end end end From 9a0df5a9424bcf482f1495438a6f3bb2969279df Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 14:22:16 +0100 Subject: [PATCH 53/60] Refactored set_old_value and set_value --- lib/setting_accessors/setting_scaffold.rb | 13 ++++++------- lib/setting_accessors/setting_set.rb | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/setting_accessors/setting_scaffold.rb b/lib/setting_accessors/setting_scaffold.rb index bac5c58..a5a8ddb 100644 --- a/lib/setting_accessors/setting_scaffold.rb +++ b/lib/setting_accessors/setting_scaffold.rb @@ -75,7 +75,7 @@ def get_or_default(name, assignable) # def set(name, value, assignable: nil) (setting_record(name, assignable) || new(name: name, assignable: assignable)).tap do |setting| - setting.set_value(value) + setting.raw_value = value setting.save end.value end @@ -157,16 +157,15 @@ def value_type # We can't use the name #value_before_type_cast here as it would # shadow ActiveRecord's default one - which might still be needed. # - def original_value - @original_value || value + def raw_value + @raw_value || value end # - # Sets the setting's value to the given one - # Performs automatic type casts + # Sets the new setting value by converting the raw value automatically. # - def set_value(new_value) - @original_value = new_value + def raw_value=(new_value) + @raw_value = new_value self.value = converter.new(new_value).convert end diff --git a/lib/setting_accessors/setting_set.rb b/lib/setting_accessors/setting_set.rb index 7d7603b..4d7dfb5 100644 --- a/lib/setting_accessors/setting_set.rb +++ b/lib/setting_accessors/setting_set.rb @@ -50,7 +50,7 @@ def key?(key) # Writes a setting's value # def set(key, val) - set_value_was(key) + track_old_value(key) set_value_before_type_cast(key, val) @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).new(val).convert end @@ -128,7 +128,7 @@ def set_value_before_type_cast(key, value) # Keeps a local copy of a setting's value before it was overridden. # Once the setting is persisted, this value is cleared. # - def set_value_was(key) + def track_old_value(key) @old_values ||= {} unless @old_values.key?(key.to_s) From 2848ebf044a1c5e456eee8e787839b52fb41e840 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 16:09:52 +0100 Subject: [PATCH 54/60] #define_method is private up until ruby 2.5 --- lib/setting_accessors/accessor_generator.rb | 45 ++++++++++++--------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/setting_accessors/accessor_generator.rb b/lib/setting_accessors/accessor_generator.rb index bf3c2c0..364ab5b 100644 --- a/lib/setting_accessors/accessor_generator.rb +++ b/lib/setting_accessors/accessor_generator.rb @@ -24,35 +24,42 @@ def assign_setting!(klass) private def define_getters(klass, outer_setting_name = setting_name) - klass.define_method(setting_name) do - settings.get_or_default(outer_setting_name) - end - - # Getter alias for boolean settings setting_type = SettingAccessors::Internal.setting_value_type(setting_name, klass).to_sym - klass.alias_method "#{outer_setting_name}?", outer_setting_name if setting_type == :boolean + + klass.class_eval do + define_method(outer_setting_name) do + settings.get_or_default(outer_setting_name) + end + + # Getter alias for boolean settings + alias_method "#{outer_setting_name}?", outer_setting_name if setting_type == :boolean + end end def define_setter(klass, outer_setting_name = setting_name) - klass.define_method("#{setting_name}=") do |new_value| - settings[outer_setting_name] = new_value + klass.class_eval do + define_method("#{outer_setting_name}=") do |new_value| + settings[outer_setting_name] = new_value + end end end def define_active_record_helpers(klass, outer_setting_name = setting_name) - # NAME_was - klass.define_method("#{setting_name}_was") do - settings.value_was(outer_setting_name) - end + klass.class_eval do + # NAME_was + define_method("#{outer_setting_name}_was") do + settings.value_was(outer_setting_name) + end - # NAME_before_type_cast - klass.define_method("#{setting_name}_before_type_cast") do - settings.value_before_type_cast(outer_setting_name) - end + # NAME_before_type_cast + define_method("#{outer_setting_name}_before_type_cast") do + settings.value_before_type_cast(outer_setting_name) + end - # NAME_changed? - klass.define_method("#{setting_name}_changed?") do - settings.value_changed?(outer_setting_name) + # NAME_changed? + define_method("#{outer_setting_name}_changed?") do + settings.value_changed?(outer_setting_name) + end end end end From c0160f5d5377dd496eb9dfecda3c1b1364a7b410 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 16:38:01 +0100 Subject: [PATCH 55/60] Updated README --- README.md | 187 ++++++++++++++++++------------------------------------ 1 file changed, 63 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 8b48bda..b68edc5 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,20 @@ The only problem is, that it would be necessary to either keep hundreds of colum This gem consists of a global key-value-store, allowing to use both global and record bound settings and ActiveRecord integration to add virtual columns to models without having to change the database layout. +Version Information / Backwards Compatibility +--------------------------------------------- + +The current version (1.x) is only compatible with Ruby >= 2.3 and Rails >= 4.2. +Versions 0.x support older versions of both Ruby and Rails. + +Version 0.x also supported validations and type conversions for globally defined settings +(`Setting.my_setting = 5`). +This was removed in this version, so developers should make sure that the assigned +value has the correct type themselves. + +Validations for class-wise defined `setting_accessor`s can still be realized using +ActiveModel's integrated validators as described below. + Installation ------------ @@ -38,7 +52,6 @@ The gem requires a few additional files to work properly: - A migration to create the settings table - A setting model - An initializer -- A settings config file (config/settings.yml) All of them can be generated using the provided generator: @@ -48,12 +61,15 @@ $ rails g setting_accessors:install MODEL_NAME If no model name is given, the default one (`Setting`) is used. -Usage as a global key-value-store (globally defined and anonymous settings) +Usage as a global key-value-store ----- -In the following, the model name will be assumed as `Setting`, though you may choose its name freely using the provided generator (see above). +In the following, the model name will be assumed as `Setting`, +though you may choose its name freely using the provided generator (see above). -Settings can either be global or assigned to an instance of ActiveRecord::Base. They consist of a name which is unique either in the global scope or within the instance and a value which can be everything that's serializable through YAML. +Settings can either be global or assigned to an instance of ActiveRecord::Base. +They consist of a name which is unique either in the global scope or within the instance +and a value which can be everything that's serializable through YAML. The easiest way to use the settings model is as a global key-value-store without validations and typecasting. @@ -65,7 +81,9 @@ Setting.the_meaning_of_life #=> 42 ``` -If the name contains characters which are not allowed due to ruby naming rules, you can also use the methods`Setting[KEY]` resp. `Setting.get(KEY)` to get a setting's value and `Setting[KEY] = VALUE` resp.`Setting.create_or_update(KEY, VALUE)`. +If the name contains characters which are not allowed due to ruby naming rules, +you can also use the methods`Setting[KEY]` resp. `Setting.get(KEY)` to get a setting's value +and `Setting[KEY] = VALUE` resp.`Setting.set(KEY, VALUE)`. Therefore, the following getter methods are equivalent: @@ -80,54 +98,23 @@ For the corresponding setters: ```ruby Setting.meaning_of_life = 42 Setting[:meaning_of_life] = 42 -Setting.create_or_update(:meaning_of_life, 42) +Setting.set(:meaning_of_life, 42) ``` -### Globally defined settings with types and validations - -As stated above, the initializer will generate a file called `settings.yml` in your application's `config` directory. This file is used to define global settings, an entry consists of: - -- The setting's name -- The setting's type (optional) -- Validations to be performed when the setting is saved (optional) -- A default value (optional) - -An example would be a simple string setting: - -```yaml -a_string: - type: string - default: "I am a string!" - validations: - presence: true -``` - -If a setting is defined with a type, automatic type conversion will happen, similar to what ActiveRecord does: +This makes it also possible to use Ruby's or-equals operator: ```ruby -Setting.a_string = 42 -#=> "42" -``` - -The default value is used by the functions `Setting.get_or_default` and `Setting.create_default_setting` and a fallback option for assigned settings (see below). - -The built-in validations currently support, this might be extended in the future. - -| Base Validation | Options | -|:----------------|:---------------------------| -| `presence` | `allow_nil`, `allow_blank` | -| `numericality` | `only_integer` | -| `boolean` |   | - +Setting.meaning_of_life ||= 42 +Setting[:meaning_of_life] ||= 42 +``` ### Assigned Records -Both globally defined settings and "anonymous" settings can also be assigned to -a instances of `ActiveRecord::Base` without having to define them in the model first. +Global settings can also be assigned to an instance of `ActiveRecord::Base` without +having to define them in the model first. -An example would be the above mentioned saving of "items per page" values for -various views: Let's say we have an events view and want to save how many -rows/items each user would like to display. +An example would be to save "items per page" values for various views: +Let's say we have an events view and want to save how many rows/items each user would like to display. As there might be many views requiring this functionality, it wouldn't make sense to define global settings for each of them. Instead, we would use anonymous @@ -139,122 +126,74 @@ def items_per_page default_value = 30 Setting.get(key, current_user) || default_value end -``` - -ActiveRecord Integration ------------------------- - -The gem adds the method `setting_accessor` to each class which inherits from `ActiveRecord::Base`. - -`setting_accessor` takes a setting name and a set of options to customize the accessor's behaviour: -```ruby -class MyModel < ActiveRecord::Base - setting_accessor :a_string, :fallback => :default +def set_items_per_page + key = [controller_path, action_name].join('_') + Setting.set(key, params[:per_page], assignable: current_user) || default_value end ``` -This automatically adds most of the helper methods to the model instances which are available for database columns: +The following calls are equivalent: ```ruby -my_model = MyModel.new - -#Setter -my_model.a_string = 1234 - -#Getter -my_model.a_string -#=> "1234" - -#Value before type cast -my_model.a_string_before_type_cast -#=> 1234 - -#Old value -my_model.a_string_was -#=> "I am a string!" (fallback value, see below) - -#Check if the value was changed -my_model.a_string_changed? -#=> true +Setting[:meaning_of_life, current_universe] = 42 +Setting.set(:meaning_of_life, 42, assignable: current_universe) ``` -The setting records are only persisted if all settings *and* the main record were valid. - -### Integration of globally defined settings +`[]=` is not an exact alias of `set` here to still support `or-else`: -If a setting with the given name is defined in `config/settings.yml`, validations and type settings are automatically fetched from it, in this case, only the `:fallback` option is allowed. +```ruby +Setting[:meaning_of_life, current_universe] ||= 42 +``` -As of now, this means that custom validations are also not supported for globally defined settings, this may be changed in the future. +ActiveRecord Integration +------------------------ -### Class-wise definition of settings +The gem adds the method `setting_accessor` to each class which inherits from `ActiveRecord::Base`. -If a setting is defined using `setting_accessor` which is not part of `config/settings.yml`, it will only be available in this class. It however may be defined in multiple classes independently. +It basically generates an `attribute_accessor` with the given name, but also makes sure that +the value is persisted when the actual record is saved. It also provides the standard attribute helper +methods ActiveRecord adds for each database column (`ATTRIBUTE_changed?`, `ATTRIBUTE_was`, ...). -Defining a class setting accepts the same options as the config file: +It aims to simulate an actual database column. ```ruby class MyModel < ActiveRecord::Base - setting_accessor :my_setting, :type => :boolean, :default => false + setting_accessor :my_setting, type: :boolean, default: false end ``` ### Options ```ruby -:type => :string | :integer | :boolean | :polymorphic +type: :string | :integer | :boolean | :polymorphic ``` -`:type` defines the setting's data type. If no type is given, `:polymorhpic` -is used automatically. +`:type` defines the setting's data type. If no type is given, `:polymorhpic` is used automatically. For every other type, the setting's value is automatically converted accordingly -upon setting it, mimicking ActiveRecord's behaviour regarding database columns. +upon setting it, mimicking ActiveRecord's type casting based on database types. Please note that `:polymorphic` values are saved as is, meaning that you can store everything that's serializable. -More types will be added in the future, most likely all database types -ActiveRecord can handle as well. - ```ruby -:default => "I am a string!" +default: Object ``` -`:default` sets the setting's default value. It can be retrieved either by -calling `Setting.get_or_default(...)` or defining a `:fallback` on a class setting. - -```ruby -:fallback => :global | :default | Object -``` +`:default` sets the setting's default value that is returned as long no actual setting exists for +that name/assignable. -The `:fallback` option specifies which value should be returned in case the -setting did not receive a value yet: - -- `:global` will try to retrieve a global setting (`Setting.NAME`) with the same - name as the accessor and return `nil` if it isn't defined. -- `:default` will return the default value set up either in the accessor method - or `config/settings.yml` for globally defined settings. -- Every other value will be returned as is - - -```ruby -:validations => {:presence => true} -:validations => {:numericality => {:only_integer => true}} -:validations => {:custom => [lambda {|setting| setting.errors.add(:not_42) if setting.value != 42}]} -:validations => {:custom => [:must_be_42]} -``` +### Validations -There are some built-in validations (see above), but they should be mostly used for globally defined -and globally used settings (`Setting.somethingsomething`). -For model specific settings, you may treat the setting accessors just like normal columns -and define your validations accordingly, e.g. +Attributes created by `setting_accessor` can simply be used with `ActiveModel::Validations` the same way +as other attributes: ```ruby -setting_accessor :my_string, :type => :string -setting_accessor :my_number, :type => :integer +setting_accessor :my_string, type: :string +setting_accessor :my_number, type: :integer -validates :my_string, :presence => true -validates :my_number, :numericality => {:only_integer => true} +validates :my_string, presence: true +validates :my_number, numericality: {only_integer: true} ``` Contributing From 95b8bafecd91b99afc57ea574c99ddf665a8439e Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 16:51:38 +0100 Subject: [PATCH 56/60] Updated CHANGELOG --- CHANGELOG.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d539ca7..84a417d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 1.0.0 + +This is almost a complete refactoring of the gem which also removes some not really needed functionality. +It is the first release fully compatible with Rails 5. + +New minimum versions: + +* Ruby: 2.3 +* Rails: 4.2 + +#### General + +* Removed globally defined settings (`config/settings.yml`) and therefore also global validations and type conversions. + It is still possible to create global Settings, but each developer has to make sure that the assigned + value is valid and correctly typed. +* Type conversion follows AR's type conversions more strictly now. + This means that e.g. assigning `1.0` to a boolean setting will result in `true` instead of `nil` as before. +* Updating an attribute created through `setting_accessor` now leads to the record being touched, + similar to what would happen when a normal database attribute was changed. (#4) +* Setting a new value for an attribute created through `attribute_accessor` will now mark + the record as "dirty", meaning `changed?` returns true. (#9) + This was especially needed for Rails 5 as it only even performs a database operation in this case. + +#### Tests + +* Tests are now written in RSpec +* The dummy Rails application has been removed and replaced with `with_model` +* Appraisal was added to test against multiple Rails versions + +#### API Changes + +* `method_missing` is now part of the SettingScaffold and therefore no longer needed in the actual Setting model. + You should remove `method_missing` from your model. +* Setting.create_or_update was renamed to Setting.set and now uses a keyword argument for `assignable` +* `setting_accessor` no longer supports the `fallback` option. + If a `default` option is given, it is automatically used as fallback. + ## 0.3.0 -* Fixed a bug that caused setting accessors not being initialized when being called as part of a rake task chain (#6) \ No newline at end of file +* Fixed a bug that caused setting accessors not being initialized when being called as part of a rake task chain (#6) From 09f352ca790e512d6eefe4531fc0e10b165a1aa9 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 17:16:50 +0100 Subject: [PATCH 57/60] removed recipient email from travis config --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d52754..468418f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,5 @@ script: 'bundle exec rake' notifications: email: - recipients: - - stex@sterex.de on_failure: change on_success: never From ca806a979bd84b2afd0c9ce30ea53320271fb0b7 Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 17:35:08 +0100 Subject: [PATCH 58/60] Bumped Version to 1.0 --- gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/setting_accessors/version.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index a2b2059..7cd463f 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - setting_accessors (0.3.0) + setting_accessors (1.0.0) activemodel (>= 4.2, <= 5.2) activerecord (>= 4.2, <= 5.2) activesupport (>= 4.2, <= 5.2) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 9940d5c..a3d39fa 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - setting_accessors (0.3.0) + setting_accessors (1.0.0) activemodel (>= 4.2, <= 5.2) activerecord (>= 4.2, <= 5.2) activesupport (>= 4.2, <= 5.2) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index f4eed1f..2d021e2 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - setting_accessors (0.3.0) + setting_accessors (1.0.0) activemodel (>= 4.2, <= 5.2) activerecord (>= 4.2, <= 5.2) activesupport (>= 4.2, <= 5.2) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 8eefac5..3cb936a 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - setting_accessors (0.3.0) + setting_accessors (1.0.0) activemodel (>= 4.2, <= 5.2) activerecord (>= 4.2, <= 5.2) activesupport (>= 4.2, <= 5.2) diff --git a/lib/setting_accessors/version.rb b/lib/setting_accessors/version.rb index 5ffd7ae..4f5a967 100644 --- a/lib/setting_accessors/version.rb +++ b/lib/setting_accessors/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SettingAccessors - VERSION = '0.3.0' + VERSION = '1.0.0' end From f6e05d7e50f38b33cb0d8e4a9c4a4943505f9b1c Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 17:54:15 +0100 Subject: [PATCH 59/60] use less pessimistic requirements in development dependencies --- setting_accessors.gemspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setting_accessors.gemspec b/setting_accessors.gemspec index 800e608..2936d64 100644 --- a/setting_accessors.gemspec +++ b/setting_accessors.gemspec @@ -25,14 +25,14 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ['>= 2.3', '< 3'] - spec.add_development_dependency 'appraisal', '~> 2.2.0' + spec.add_development_dependency 'appraisal', '~> 2.2' spec.add_development_dependency 'bundler', '~> 1.6' - spec.add_development_dependency 'generator_spec', '~> 0.9.4' + spec.add_development_dependency 'generator_spec', '~> 0.9' spec.add_development_dependency 'rake', '~> 10.4' - spec.add_development_dependency 'rspec', '~> 3.8.0' - spec.add_development_dependency 'rubocop', '~> 0.60.0' + spec.add_development_dependency 'rspec', '~> 3.8' + spec.add_development_dependency 'rubocop', '~> 0.60' spec.add_development_dependency 'sqlite3', '~> 1.3' - spec.add_development_dependency 'with_model', '~> 2.1.2' + spec.add_development_dependency 'with_model', '~> 2.1' spec.add_dependency 'activemodel', ['>= 4.2', '<= 5.2'] spec.add_dependency 'activerecord', ['>= 4.2', '<= 5.2'] From 5f4168310532cfe7e21b323f91954408a2c900df Mon Sep 17 00:00:00 2001 From: Stefan Exner Date: Mon, 26 Nov 2018 18:20:58 +0100 Subject: [PATCH 60/60] Upated gemfiles using appraisal install --- gemfiles/rails_4.2.gemfile | 6 +++--- gemfiles/rails_4.2.gemfile.lock | 10 +++++----- gemfiles/rails_5.0.gemfile | 6 +++--- gemfiles/rails_5.0.gemfile.lock | 10 +++++----- gemfiles/rails_5.1.gemfile | 6 +++--- gemfiles/rails_5.1.gemfile.lock | 10 +++++----- gemfiles/rails_5.2.gemfile | 6 +++--- gemfiles/rails_5.2.gemfile.lock | 10 +++++----- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index dbed7dd..6977eb0 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'rails', '~> 4.2.0' +gem "rails", "~> 4.2.0" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 7cd463f..3431c43 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -147,16 +147,16 @@ PLATFORMS ruby DEPENDENCIES - appraisal (~> 2.2.0) + appraisal (~> 2.2) bundler (~> 1.6) - generator_spec (~> 0.9.4) + generator_spec (~> 0.9) rails (~> 4.2.0) rake (~> 10.4) - rspec (~> 3.8.0) - rubocop (~> 0.60.0) + rspec (~> 3.8) + rubocop (~> 0.60) setting_accessors! sqlite3 (~> 1.3) - with_model (~> 2.1.2) + with_model (~> 2.1) BUNDLED WITH 1.17.1 diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile index 49df964..10f52e7 100644 --- a/gemfiles/rails_5.0.gemfile +++ b/gemfiles/rails_5.0.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'rails', '~> 5.0.0' +gem "rails", "~> 5.0.0" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index a3d39fa..f07a6bd 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -154,16 +154,16 @@ PLATFORMS ruby DEPENDENCIES - appraisal (~> 2.2.0) + appraisal (~> 2.2) bundler (~> 1.6) - generator_spec (~> 0.9.4) + generator_spec (~> 0.9) rails (~> 5.0.0) rake (~> 10.4) - rspec (~> 3.8.0) - rubocop (~> 0.60.0) + rspec (~> 3.8) + rubocop (~> 0.60) setting_accessors! sqlite3 (~> 1.3) - with_model (~> 2.1.2) + with_model (~> 2.1) BUNDLED WITH 1.17.1 diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile index 953d45c..6100e83 100644 --- a/gemfiles/rails_5.1.gemfile +++ b/gemfiles/rails_5.1.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'rails', '~> 5.1.0' +gem "rails", "~> 5.1.0" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 2d021e2..4a05162 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -154,16 +154,16 @@ PLATFORMS ruby DEPENDENCIES - appraisal (~> 2.2.0) + appraisal (~> 2.2) bundler (~> 1.6) - generator_spec (~> 0.9.4) + generator_spec (~> 0.9) rails (~> 5.1.0) rake (~> 10.4) - rspec (~> 3.8.0) - rubocop (~> 0.60.0) + rspec (~> 3.8) + rubocop (~> 0.60) setting_accessors! sqlite3 (~> 1.3) - with_model (~> 2.1.2) + with_model (~> 2.1) BUNDLED WITH 1.17.1 diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile index 0b87fd8..5a706dc 100644 --- a/gemfiles/rails_5.2.gemfile +++ b/gemfiles/rails_5.2.gemfile @@ -1,7 +1,7 @@ # This file was generated by Appraisal -source 'https://rubygems.org' +source "https://rubygems.org" -gem 'rails', '~> 5.2.0' +gem "rails", "~> 5.2.0" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 3cb936a..30edaa2 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -162,16 +162,16 @@ PLATFORMS ruby DEPENDENCIES - appraisal (~> 2.2.0) + appraisal (~> 2.2) bundler (~> 1.6) - generator_spec (~> 0.9.4) + generator_spec (~> 0.9) rails (~> 5.2.0) rake (~> 10.4) - rspec (~> 3.8.0) - rubocop (~> 0.60.0) + rspec (~> 3.8) + rubocop (~> 0.60) setting_accessors! sqlite3 (~> 1.3) - with_model (~> 2.1.2) + with_model (~> 2.1) BUNDLED WITH 1.17.1