diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..cf27a37 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +service_name: travis-pro diff --git a/.gitignore b/.gitignore index 4c6e75c..5ae9730 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .vscode .idea .DS_Store +coverage \ No newline at end of file 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/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ab33aaf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: ruby +before_install: + - gem install bundler +rvm: + - 2.6.3 +script: + - bundle exec rspec \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..dd100db --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +source 'https://gems.ruby-china.com/' + +group :development, :test do + gem 'rspec' + gem 'simplecov' + gem 'coveralls' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..846af90 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,51 @@ +GEM + remote: https://gems.ruby-china.com/ + specs: + coveralls (0.7.2) + multi_json (~> 1.3) + rest-client (= 1.6.7) + simplecov (>= 0.7) + term-ansicolor (= 1.2.2) + thor (= 0.18.1) + diff-lcs (1.3) + docile (1.3.2) + json (2.2.0) + mime-types (3.3) + mime-types-data (~> 3.2015) + mime-types-data (3.2019.1009) + multi_json (1.14.1) + rest-client (1.6.7) + mime-types (>= 1.16) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.0) + rspec-support (~> 3.9.0) + rspec-expectations (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.0) + simplecov (0.17.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + term-ansicolor (1.2.2) + tins (~> 0.8) + thor (0.18.1) + tins (0.13.2) + +PLATFORMS + ruby + +DEPENDENCIES + coveralls + rspec + simplecov + +BUNDLED WITH + 2.0.2 diff --git a/README.md b/README.md index 5c5fdd6..daa7dd8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # client-data-adapter -## TODO \ No newline at end of file +[![Build Status](https://travis-ci.org/jinghua000/client-data-adapter.svg?branch=master)](https://travis-ci.org/jinghua000/client-data-adapter) +[![Gem Version](https://badge.fury.io/rb/client-data-adapter.svg)](https://rubygems.org/gems/client-data-adapter) +[![Coverage Status](https://coveralls.io/repos/github/jinghua000/client-data-adapter/badge.svg?branch=master)](https://coveralls.io/github/jinghua000/client-data-adapter?branch=master) + +## Introduction + +TODO diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..6d349f9 --- /dev/null +++ b/Rakefile @@ -0,0 +1,3 @@ +task :test do + p 123 +end \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..43bcfef --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +gem build client-data-adapter.gemspec +gem install client-data-adapter-0.0.1.gem \ No newline at end of file diff --git a/client-data-adapter.gemspec b/client-data-adapter.gemspec index 02959d4..ba16e77 100644 --- a/client-data-adapter.gemspec +++ b/client-data-adapter.gemspec @@ -1,3 +1,6 @@ +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + Gem::Specification.new do |s| s.name = 'client-data-adapter' diff --git a/demo/book.rb b/demo/book.rb new file mode 100644 index 0000000..a18fd69 --- /dev/null +++ b/demo/book.rb @@ -0,0 +1,84 @@ +require_relative '../lib/client-data-adapter' +require_relative 'book_shelf' +require_relative 'category' + +class Book + + include ClientDataAdapter + + define_adapter do + + link_one :book_shelf + link_many :categories + + adapter do + { + id: id, + title: title, + } + end + + with :full_title do + "<#{title}>" + end + + with :my_awesome_title do + "my_awesome_title" + end + + with :pass1 do |*args| + args << 'pass1' + end + + with :pass2 do |*args| + args << 'pass2' + end + + with :id do + return id if true + # :nocov: + raise 'CODE WILL NOT REACH HERE.' + # :nocov: + end + + end + + attr_accessor :id, :title, :book_shelf_id + + def initialize(**opt) + + self.id = opt[:id] + self.title = opt[:title] + self.book_shelf_id = opt[:book_shelf_id] + + end + + def self.demo + new( + id: 1, + title: 'My Book', + book_shelf_id: 1, + ) + end + + def my_title + 'my title' + end + + def my_awesome_title + 'no no no' + end + + def sub_title(sub) + "#{title}-#{sub}" + end + + def book_shelf + BookShelf.demo + end + + def categories + [Category.new(id: 1, cat: 'one'), Category.new(id: 2, cat: 'two')] + end + +end \ No newline at end of file diff --git a/demo/book_shelf.rb b/demo/book_shelf.rb new file mode 100644 index 0000000..192dc21 --- /dev/null +++ b/demo/book_shelf.rb @@ -0,0 +1,46 @@ +require_relative 'library' + +class BookShelf + + include ClientDataAdapter + + define_adapter do + + link_one :library + + adapter do + { + id: id, + desc: desc, + } + end + + with :other_desc do + 'other desc' + end + + end + + attr_accessor :id, :desc, :library_id + + def initialize(**opt) + + self.id = opt[:id] + self.desc = opt[:desc] + self.library_id = opt[:library_id] + + end + + def self.demo + new( + id: 1, + desc: 'my book shelf', + library_id: 1, + ) + end + + def library + Library.demo + end + +end \ No newline at end of file diff --git a/demo/category.rb b/demo/category.rb new file mode 100644 index 0000000..108aae6 --- /dev/null +++ b/demo/category.rb @@ -0,0 +1,29 @@ +class Category + + include ClientDataAdapter + + define_adapter do + + adapter do + { + id: id, + cat: cat, + } + end + + end + + attr_accessor :id, :cat + + def initialize(**opt) + + self.id = opt[:id] + self.cat = opt[:cat] + + end + + def summary + 'summary' + end + +end \ No newline at end of file diff --git a/demo/library.rb b/demo/library.rb new file mode 100644 index 0000000..6ec571e --- /dev/null +++ b/demo/library.rb @@ -0,0 +1,36 @@ +class Library + + include ClientDataAdapter + + define_adapter do + + adapter do + { + id: id, + size: size, + } + end + + with :welcome do + 'welcome' + end + + end + + attr_accessor :id, :size, :city_id + + def initialize(**opt) + + self.id = opt[:id] + self.size = opt[:size] + + end + + def self.demo + new( + id: 1, + size: 'small' + ) + end + +end \ No newline at end of file diff --git a/lib/client-data-adapter.rb b/lib/client-data-adapter.rb index 568d2bd..eedd948 100644 --- a/lib/client-data-adapter.rb +++ b/lib/client-data-adapter.rb @@ -1,7 +1,11 @@ +require_relative 'client-data-adapter/class_methods' +require_relative 'client-data-adapter/instance_methods' + module ClientDataAdapter - def self.demo - 'hello' + def self.included(base) + base.include(ClientDataAdapter::InstanceMethods) + base.extend(ClientDataAdapter::ClassMethods) end end diff --git a/lib/client-data-adapter/class_methods.rb b/lib/client-data-adapter/class_methods.rb new file mode 100644 index 0000000..317185c --- /dev/null +++ b/lib/client-data-adapter/class_methods.rb @@ -0,0 +1,18 @@ +require_relative 'config' +require_relative 'wrapper' + +module ClientDataAdapter + module ClassMethods + + def define_adapter(&block) + + const_set(ADAPTER_WRAPPER, Class.new(Wrapper)) + + define_method :adapter_wrapper do + @__adapter_wrapper__ ||= self.class.const_get(ADAPTER_WRAPPER).new(self, &block) + end + + end + + end +end \ No newline at end of file diff --git a/lib/client-data-adapter/config.rb b/lib/client-data-adapter/config.rb new file mode 100644 index 0000000..b55e182 --- /dev/null +++ b/lib/client-data-adapter/config.rb @@ -0,0 +1,6 @@ +module ClientDataAdapter + + ADAPTER_WRAPPER = :AdapterWrapper + ADAPTER = :adapter + +end diff --git a/lib/client-data-adapter/instance_methods.rb b/lib/client-data-adapter/instance_methods.rb new file mode 100644 index 0000000..376ee53 --- /dev/null +++ b/lib/client-data-adapter/instance_methods.rb @@ -0,0 +1,41 @@ +require_relative 'util' + +module ClientDataAdapter + module InstanceMethods + + def adapter(*args) + + length = args.length + + if length == 0 + adapter_wrapper.__adapter__ + else + adapter_wrapper.__adapter__.merge( + + *args.map do |arg| + if [String, Symbol].include?(arg.class) + __merge_to_adapter__(arg.to_sym, nil) + elsif arg.is_a?(Hash) + arg.map(&method(:__merge_to_adapter__)) + else + raise '[ERROR] Not available arguments type.' + end + end.flatten, + + ) + end + end + + private + + def __merge_to_adapter__(key, params) + {}.tap do |hah| + hah["#{key}".to_sym] = + adapter_wrapper.respond_to?(key) ? + adapter_wrapper.public_send(key, *params) : + public_send(key, *params) + end + end + + end +end diff --git a/lib/client-data-adapter/util.rb b/lib/client-data-adapter/util.rb new file mode 100644 index 0000000..50a105a --- /dev/null +++ b/lib/client-data-adapter/util.rb @@ -0,0 +1,23 @@ +module ClientDataAdapter + module Util + + module_function + + # Convert a Proc to Lambda. + # + # @param [Proc] source_proc + # @return [Lambda] + def to_lambda(source_proc) + return source_proc if source_proc.lambda? + + unbound_method = Module.new.module_eval do + instance_method(define_method(:_, &source_proc)) + end + + lambda do |*args, &block| + unbound_method.bind(self).call(*args, &block) + end + end + + end +end diff --git a/lib/client-data-adapter/wrapper.rb b/lib/client-data-adapter/wrapper.rb new file mode 100644 index 0000000..f3dbdf0 --- /dev/null +++ b/lib/client-data-adapter/wrapper.rb @@ -0,0 +1,46 @@ +require_relative 'util' + +module ClientDataAdapter + class Wrapper + + attr_reader :target + + def initialize(target, &block) + @target = target + + self.class.module_eval(&block) + end + + def self.adapter(&block) + with('__adapter__', &block) + end + + def self.link_one(*associations) + associations.each do |assoc| + with(assoc) do |*args| + public_send(assoc.to_sym).adapter(*args) + end + end + end + + def self.link_many(*associations) + associations.each do |assoc| + with(assoc) do |*args| + public_send(assoc.to_sym).map do |elem| + elem.adapter(*args) + end + end + end + end + + def self.with(method_name, &block) + define_method(method_name.to_sym) do |*args| + target.instance_exec( + *args, + &Util.to_lambda(block) + ) + end + end + + end +end diff --git a/spec/adapter_spec.rb b/spec/adapter_spec.rb new file mode 100644 index 0000000..18f4c2b --- /dev/null +++ b/spec/adapter_spec.rb @@ -0,0 +1,81 @@ +require_relative '../demo/book' + +RSpec.describe Book do + + before(:each) do + @book = Book.demo + end + + it "adapter method return adapter wrapper's __adapter__" do + + expect(@book.adapter).to eq(id: @book.id, title: @book.title) + expect(@book.adapter).to eq(@book.adapter_wrapper.__adapter__) + + end + + it "adapter can contain `with` method's returns" do + + expect(@book.adapter_wrapper.respond_to? :full_title).to eq(true) + expect(@book.adapter(:full_title)) + .to eq( + id: @book.id, + title: @book.title, + full_title: @book.adapter_wrapper.full_title + ) + + end + + it "adapter can contain origin class instance method" do + + expect(@book.adapter_wrapper.respond_to? :my_title).to eq(false) + expect(@book.adapter(:my_title)) + .to eq( + id: @book.id, + title: @book.title, + my_title: @book.my_title + ) + + end + + it "methods inside adapter wrapper will override method outside" do + + expect(@book.my_awesome_title).not_to eq(@book.adapter_wrapper.my_awesome_title) + expect(@book.adapter(:my_awesome_title)) + .to eq( + id: @book.id, + title: @book.title, + my_awesome_title: @book.adapter_wrapper.my_awesome_title + ) + + end + + it "adapter can pass arguments in `with` method block" do + + expect(@book.adapter(pass1: [:a, :b, :c], pass2: [:x, :y, :z])) + .to eq( + id: @book.id, + title: @book.title, + pass1: @book.adapter_wrapper.pass1(*[:a, :b, :c]), + pass2: @book.adapter_wrapper.pass2(*[:x, :y, :z]), + ) + + end + + it "adapter can pass arguments with origin class instance methods" do + + expect(@book.adapter(sub_title: 'yes')) + .to eq( + id: @book.id, + title: @book.title, + sub_title: @book.sub_title('yes') + ) + + end + + it "not available arguments type will raise an error" do + + expect { @book.adapter(123) }.to raise_error(RuntimeError) + + end + +end \ No newline at end of file diff --git a/spec/adapter_wrapper_spec.rb b/spec/adapter_wrapper_spec.rb new file mode 100644 index 0000000..4ec3973 --- /dev/null +++ b/spec/adapter_wrapper_spec.rb @@ -0,0 +1,21 @@ +require_relative '../demo/book' + +RSpec.describe Book do + + before(:each) do + @book = Book.demo + end + + it "adapter wrapper should apply origin instance's self-point" do + + expect(@book.adapter_wrapper.target).to eq(@book) + + end + + it "adapter wrapper's methods can use return inside" do + + expect(@book.adapter_wrapper.id).to eq(@book.id) + + end + +end \ No newline at end of file diff --git a/spec/demo_spec.rb b/spec/demo_spec.rb new file mode 100644 index 0000000..4bef900 --- /dev/null +++ b/spec/demo_spec.rb @@ -0,0 +1,5 @@ +require_relative '../demo/book' + +RSpec.describe Book do + +end \ No newline at end of file diff --git a/spec/link_spec.rb b/spec/link_spec.rb new file mode 100644 index 0000000..cf17bb6 --- /dev/null +++ b/spec/link_spec.rb @@ -0,0 +1,104 @@ +require_relative '../demo/book' + +RSpec.describe Book do + + before(:each) do + @book = Book.demo + end + + it "link one should call linked target's adapter method" do + + expect(@book.adapter(:book_shelf)) + .to eq( + id: @book.id, + title: @book.title, + book_shelf: { + id: @book.book_shelf.id, + desc: @book.book_shelf.desc, + } + ) + + end + + it "link one can pass arguments" do + + expect(@book.adapter(book_shelf: :other_desc)) + .to eq( + id: @book.id, + title: @book.title, + book_shelf: { + id: @book.book_shelf.id, + desc: @book.book_shelf.desc, + other_desc: @book.book_shelf.adapter_wrapper.other_desc, + } + ) + + end + + it "link can be nested" do + + @book_shelf = @book.book_shelf + @library = @book_shelf.library + + expect(@book.adapter(book_shelf: [:other_desc, library: :welcome])) + .to eq( + id: @book.id, + title: @book.title, + book_shelf: { + id: @book_shelf.id, + desc: @book_shelf.desc, + other_desc: @book_shelf.adapter_wrapper.other_desc, + library: { + id: @library.id, + size: @library.size, + welcome: @library.adapter_wrapper.welcome, + }, + } + ) + end + + it "link many should call all linked targets's adapter method" do + + @categories = @book.categories + expect(@book.adapter(:categories)) + .to eq( + id: @book.id, + title: @book.title, + categories: [ + { + id: @categories[0].id, + cat: @categories[0].cat, + }, + { + id: @categories[1].id, + cat: @categories[1].cat, + }, + ] + ) + + end + + it "link many can pass arguments" do + + @categories = @book.categories + expect(@book.adapter(categories: :summary)) + .to eq( + id: @book.id, + title: @book.title, + categories: [ + { + id: @categories[0].id, + cat: @categories[0].cat, + summary: @categories[0].summary, + }, + { + id: @categories[1].id, + cat: @categories[1].cat, + summary: @categories[1].summary, + }, + ] + ) + + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..cb39e56 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,108 @@ +# 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 'simplecov' +SimpleCov.start +SimpleCov.minimum_coverage 100 + +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 +end + +require 'coveralls' +Coveralls.wear! \ No newline at end of file diff --git a/update.sh b/update.sh index 5b7cafd..3de9686 100644 --- a/update.sh +++ b/update.sh @@ -1,4 +1,4 @@ git add . -git commit -m 'update' git status +git commit -m 'update' git push origin dev \ No newline at end of file