From 878e206c3326da4639c3687ea9df3b2bfe7334b3 Mon Sep 17 00:00:00 2001 From: jing_hua <36701496+jinghua000@users.noreply.github.com> Date: Mon, 11 Nov 2019 21:21:54 +0800 Subject: [PATCH] main feature (#2) main feature (#2) --- .coveralls.yml | 1 + .gitignore | 1 + .rspec | 1 + .travis.yml | 7 ++ Gemfile | 7 ++ Gemfile.lock | 51 +++++++++ README.md | 8 +- Rakefile | 3 + build.sh | 2 + client-data-adapter.gemspec | 3 + demo/book.rb | 84 +++++++++++++++ demo/book_shelf.rb | 46 +++++++++ demo/category.rb | 29 ++++++ demo/library.rb | 36 +++++++ lib/client-data-adapter.rb | 8 +- lib/client-data-adapter/class_methods.rb | 18 ++++ lib/client-data-adapter/config.rb | 6 ++ lib/client-data-adapter/instance_methods.rb | 41 ++++++++ lib/client-data-adapter/util.rb | 23 +++++ lib/client-data-adapter/wrapper.rb | 46 +++++++++ spec/adapter_spec.rb | 81 +++++++++++++++ spec/adapter_wrapper_spec.rb | 21 ++++ spec/demo_spec.rb | 5 + spec/link_spec.rb | 104 +++++++++++++++++++ spec/spec_helper.rb | 108 ++++++++++++++++++++ update.sh | 2 +- 26 files changed, 738 insertions(+), 4 deletions(-) create mode 100644 .coveralls.yml create mode 100644 .rspec create mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Rakefile create mode 100644 build.sh create mode 100644 demo/book.rb create mode 100644 demo/book_shelf.rb create mode 100644 demo/category.rb create mode 100644 demo/library.rb create mode 100644 lib/client-data-adapter/class_methods.rb create mode 100644 lib/client-data-adapter/config.rb create mode 100644 lib/client-data-adapter/instance_methods.rb create mode 100644 lib/client-data-adapter/util.rb create mode 100644 lib/client-data-adapter/wrapper.rb create mode 100644 spec/adapter_spec.rb create mode 100644 spec/adapter_wrapper_spec.rb create mode 100644 spec/demo_spec.rb create mode 100644 spec/link_spec.rb create mode 100644 spec/spec_helper.rb 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