├── .travis.yml ├── lib ├── flavors │ ├── version.rb │ ├── preferences │ │ ├── preference.rb │ │ └── preferences.rb │ └── engine.rb ├── flavors.rb └── generators │ └── flavors │ ├── templates │ └── create_preferences.rb │ └── migration_generator.rb ├── spec ├── support │ ├── post.rb │ ├── user.rb │ └── schema.rb ├── preference_spec.rb ├── spec_helper.rb └── preferences_spec.rb ├── Gemfile ├── Rakefile ├── .gitignore ├── LICENSE.txt ├── flavors.gemspec └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.4 4 | - 2.3.3 5 | -------------------------------------------------------------------------------- /lib/flavors/version.rb: -------------------------------------------------------------------------------- 1 | module Flavors 2 | VERSION = "1.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ActiveRecord::Base 2 | 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in flavors.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/flavors.rb: -------------------------------------------------------------------------------- 1 | require "flavors/version" 2 | 3 | module Flavors 4 | module Rails 5 | require "flavors/engine" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /spec/preference_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Flavors::Preference, type: :model do 4 | it { is_expected.to belong_to(:prefered) } 5 | end 6 | -------------------------------------------------------------------------------- /lib/flavors/preferences/preference.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | module Flavors 4 | class Preference < ::ActiveRecord::Base 5 | belongs_to :prefered, :polymorphic => true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | log 19 | -------------------------------------------------------------------------------- /lib/flavors/engine.rb: -------------------------------------------------------------------------------- 1 | require 'flavors/preferences/preferences.rb' 2 | require 'flavors/preferences/preference.rb' 3 | 4 | require 'rails' 5 | 6 | module Flavors 7 | class Engine < ::Rails::Engine 8 | initializer "flavors" do 9 | ::ActiveRecord::Base.send :include, Flavors::Preferences 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/flavors/templates/create_preferences.rb: -------------------------------------------------------------------------------- 1 | class CreatePreferences < ActiveRecord::Migration 2 | def change 3 | create_table :preferences do |t| 4 | t.string :name 5 | t.boolean :value 6 | t.integer :prefered_id 7 | t.string :prefered_type 8 | 9 | t.timestamps 10 | end 11 | 12 | add_index :preferences, :prefered_id 13 | add_index :preferences, :prefered_type 14 | add_index :preferences, :name 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/generators/flavors/migration_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | require 'rails/generators/migration' 3 | 4 | module Flavors 5 | class MigrationGenerator < ::Rails::Generators::Base 6 | include ::Rails::Generators::Migration 7 | source_root File.expand_path('../templates', __FILE__) 8 | 9 | def self.next_migration_number( dirname ) 10 | next_migration_number = current_migration_number(dirname) + 1 11 | if ActiveRecord::Base.timestamped_migrations 12 | [Time.now.utc.strftime("%Y%m%d%H%M%S%6N"), "%.20d" % next_migration_number].max 13 | else 14 | "%.3d" % next_migration_number 15 | end 16 | end 17 | 18 | def create_model_file 19 | migration_template "create_preferences.rb", "db/migrate/create_preferences.rb" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | 4 | require 'rspec' 5 | require 'flavors' 6 | require 'shoulda-matchers' 7 | 8 | require "rails/test_unit/railtie" 9 | 10 | RSpec.configure do |config| 11 | config.color = true 12 | config.formatter = 'documentation' 13 | end 14 | 15 | Shoulda::Matchers.configure do |config| 16 | config.integrate do |with| 17 | with.test_framework :rspec 18 | with.library :active_record 19 | with.library :active_model 20 | end 21 | end 22 | 23 | # Define the application and configuration 24 | module Config 25 | class Application < ::Rails::Application 26 | end 27 | end 28 | 29 | Config::Application.initialize! 30 | 31 | # Setup test environment for ActiveRecord 32 | ActiveRecord::Base.establish_connection( 33 | :adapter => 'sqlite3', 34 | :database => ':memory:' 35 | ) 36 | 37 | load File.dirname(__FILE__) + '/support/schema.rb' 38 | load File.dirname(__FILE__) + '/support/user.rb' 39 | load File.dirname(__FILE__) + '/support/post.rb' 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Polydice, Inc. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /flavors.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'flavors/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "flavors" 8 | gem.version = Flavors::VERSION 9 | gem.authors = ["David Yun", "Richard Lee"] 10 | gem.email = ["david.yun@polydice.com", "rl@polydice.com"] 11 | gem.description = %q{Simple and flexible preferences integration for ActiveRecord models.} 12 | gem.summary = %q{Add preferences to ActiveRecord models.} 13 | gem.homepage = "https://github.com/polydice/flavors" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.add_dependency "railties", ">= 5.0.0" 21 | gem.add_dependency "activesupport", ">= 5.0.0" 22 | gem.add_dependency "activerecord", ">= 5.0.0" 23 | 24 | gem.add_development_dependency "rspec" 25 | gem.add_development_dependency "rake" 26 | gem.add_development_dependency "shoulda-matchers", ">= 3.1" 27 | gem.add_development_dependency "sqlite3" 28 | gem.add_development_dependency "coveralls" 29 | end 30 | -------------------------------------------------------------------------------- /lib/flavors/preferences/preferences.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'active_support/core_ext/module' 3 | 4 | module Flavors 5 | module Preferences 6 | extend ::ActiveSupport::Concern 7 | 8 | module ClassMethods 9 | def preference(name, options = {}, &callback) 10 | has_many :preferences, :as => :prefered, :class_name => "::Flavors::Preference" 11 | 12 | define_method(name) do 13 | read_preference(name, options[:default]) 14 | end 15 | 16 | define_method("#{name}?") do 17 | read_preference(name, options[:default]) 18 | end 19 | 20 | define_method("#{name}=") do |value| 21 | write_preference(name, value) 22 | callback.call(self, value) if callback 23 | end 24 | 25 | (@@preferences ||= Set.new).add name 26 | end 27 | 28 | def reflections_of_preferences 29 | @@preferences.select { |name| self.new.respond_to? name } 30 | end 31 | end 32 | 33 | def read_preference(name, default = nil) 34 | if p = self.preferences.where(:name => name).first 35 | p.value 36 | else 37 | default 38 | end 39 | end 40 | 41 | def write_preference(name, value) 42 | p = self.preferences.where(:name => name).first_or_create 43 | p.update_attribute(:value, value) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/support/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20130103134512441314) do 14 | 15 | create_table "preferences", :force => true do |t| 16 | t.string "name" 17 | t.boolean "value" 18 | t.integer "prefered_id" 19 | t.string "prefered_type" 20 | t.datetime "created_at", :null => false 21 | t.datetime "updated_at", :null => false 22 | end 23 | 24 | add_index "preferences", ["name"], :name => "index_preferences_on_name" 25 | add_index "preferences", ["prefered_id"], :name => "index_preferences_on_prefered_id" 26 | add_index "preferences", ["prefered_type"], :name => "index_preferences_on_prefered_type" 27 | 28 | create_table "users", :force => true do |t| 29 | t.string "email" 30 | t.string "username" 31 | t.datetime "created_at", :null => false 32 | t.datetime "updated_at", :null => false 33 | end 34 | 35 | create_table "posts", :force => true do |t| 36 | t.string "title" 37 | t.text "body" 38 | t.datetime "created_at", :null => false 39 | t.datetime "updated_at", :null => false 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /spec/preferences_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'flavors' 3 | 4 | describe Flavors::Preferences do 5 | before do 6 | User.class_eval do 7 | preference :notification, :default => true 8 | end 9 | 10 | Post.class_eval do 11 | preference :sticky, :default => false 12 | end 13 | end 14 | 15 | subject { User.create } 16 | 17 | it "returns preferences array" do 18 | expect(User.reflections_of_preferences).to eq [:notification] 19 | end 20 | 21 | it "scopes preferences for different classes" do 22 | expect(Post.reflections_of_preferences).to eq [:sticky] 23 | end 24 | 25 | it "has a default value" do 26 | expect(subject.notification).to be_truthy 27 | end 28 | 29 | it "updates preference" do 30 | subject.notification = false 31 | expect(subject.notification).to be_falsy 32 | end 33 | 34 | it "scopes preferences for different classes" do 35 | expect { 36 | subject.sticky 37 | }.to raise_error NoMethodError 38 | end 39 | 40 | it "scopes default value for different classes" do 41 | Post.class_eval do 42 | preference :notification, :default => false 43 | end 44 | 45 | expect(subject.notification).to be_truthy 46 | end 47 | 48 | it "returns if nil if no default value defined" do 49 | User.class_eval do 50 | preference :foo 51 | end 52 | 53 | expect(subject.foo).to be_nil 54 | end 55 | 56 | it "invokes callback block" do 57 | User.class_eval do 58 | def buz; end 59 | 60 | preference :bar do |object, value| 61 | object.buz 62 | end 63 | end 64 | 65 | expect(subject).to receive(:buz) 66 | subject.bar = true 67 | end 68 | 69 | it "responds to the preference name with question mark" do 70 | expect(subject).to respond_to(:notification?) 71 | expect(subject).to be_notification 72 | subject.notification = false 73 | expect(subject).not_to be_notification 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flavors 2 | 3 | Simple and flexible preferences integration for ActiveRecord models. 4 | 5 | [![Gem Version](https://badge.fury.io/rb/flavors.svg)](http://badge.fury.io/rb/flavors) 6 | [![Build Status](https://travis-ci.org/polydice/flavors.svg?branch=master)](https://travis-ci.org/polydice/flavors) 7 | [![Code Climate](https://codeclimate.com/github/polydice/flavors.png)](https://codeclimate.com/github/polydice/flavors) 8 | [![Coverage Status](https://img.shields.io/coveralls/polydice/flavors.svg)](https://coveralls.io/r/polydice/flavors) 9 | 10 | ## Installation 11 | 12 | Add this line to your application's Gemfile: 13 | 14 | gem 'flavors' 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install flavors 23 | 24 | Generate the migration for the preferences table and migrate the database: 25 | 26 | $ rails generate flavors:migration 27 | $ rake db:migrate 28 | 29 | ## Usage 30 | 31 | To add preferences to your ActiveRecord model, in your model file: 32 | 33 | ```ruby 34 | class User < ActiveRecord::Base 35 | preference :receive_email, default: true 36 | end 37 | ``` 38 | 39 | Then you can then use the methods provided by flavors to read / write preferences. 40 | 41 | ```ruby 42 | irb(main):001:0> u = User.create(email: "foo@bar.com") 43 | irb(main):002:0> u.notification 44 | => true 45 | irb(main):003:0> u.notification = false 46 | => false 47 | irb(main):004:0> u.notification 48 | => false 49 | ``` 50 | 51 | From 0.2.0, Flavors also supports callback block for preference setter. 52 | 53 | ```ruby 54 | class User < ActiveRecord::Base 55 | preference :receive_email, default: true do |object, value| 56 | puts "#{object.name} sets preference to #{value}" 57 | end 58 | end 59 | ``` 60 | 61 | When you set preference of instances, the callback block will be invoked. 62 | 63 | ```ruby 64 | irb(main):001:0> u = User.create(name: "foo", email: "foo@bar.com") 65 | irb(main):002:0> u.notification = true 66 | foo sets preference to true 67 | => true 68 | ``` 69 | 70 | ## Contributing 71 | 72 | 1. Fork it 73 | 2. Create your feature branch (`git checkout -b my-new-feature`) 74 | 3. Commit your changes (`git commit -am 'Add some feature'`) 75 | 4. Push to the branch (`git push origin my-new-feature`) 76 | 5. Create new Pull Request 77 | --------------------------------------------------------------------------------