├── .ruby-version ├── .yardopts ├── .tool-versions ├── .rspec ├── lib ├── statics │ ├── version.rb │ ├── errors.rb │ ├── types.rb │ ├── translatable.rb │ ├── model.rb │ └── collection.rb └── statics.rb ├── spec ├── support │ ├── initializer.rb │ └── post.rb ├── statics_spec.rb ├── files │ └── posts.yml ├── spec_helper.rb └── statics │ └── model_spec.rb ├── .gitignore ├── bin ├── setup └── console ├── .editorconfig ├── Gemfile ├── Rakefile ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish-gem.yml ├── CONTRIBUTING.md ├── LICENSE ├── statics.gemspec ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.0 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --private 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.2.0 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /lib/statics/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Statics 4 | VERSION = "3.0.0" 5 | end 6 | -------------------------------------------------------------------------------- /lib/statics/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Statics 4 | class KeyNotFoundError < StandardError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/support/initializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Statics.configure do |config| 4 | config.data_path = "spec/files/" 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | *.gem 11 | .rspec_status 12 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /spec/statics_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Statics do 4 | it "has a version number" do 5 | expect(Statics::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 6 | 7 | # Specify your gem's dependencies in statics.gemspec 8 | gemspec 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | require "standard/rake" 9 | 10 | task default: %i[spec standard] 11 | -------------------------------------------------------------------------------- /lib/statics/types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Statics 4 | module Types 5 | include Dry.Types 6 | 7 | Translations = Types::Map(Types::Strict::Symbol.constructor(&:to_sym), Types::Strict::String) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "statics" 6 | require "pry" 7 | require "pry-byebug" 8 | 9 | require "./spec/support/initializer" 10 | require "./spec/support/post" 11 | 12 | Pry.start 13 | -------------------------------------------------------------------------------- /spec/support/post.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Post < Statics::Model 4 | include Statics::Translatable 5 | 6 | filename "posts" 7 | 8 | attribute :title, Types::Strict::String 9 | attribute? :author, Types::Strict::String.default("Unknown") 10 | 11 | translatable_attribute :body 12 | translatable_attribute :footer, optional: true 13 | end 14 | -------------------------------------------------------------------------------- /spec/files/posts.yml: -------------------------------------------------------------------------------- 1 | post1: 2 | title: "Post 1" 3 | body: 4 | en: "Hello!" 5 | nl: "Hallo!" 6 | 7 | post2: 8 | title: "Post 2" 9 | body: 10 | en: "Bye!" 11 | nl: "Doei!" 12 | 13 | post3: 14 | title: "Post 3" 15 | author: "Rick Sanchez" 16 | body: 17 | en: "Good morning" 18 | nl: "Goedemorgen" 19 | footer: 20 | en: "Good evening" 21 | nl: "Goedenavond" 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: daily 8 | time: "10:00" 9 | timezone: Europe/Amsterdam 10 | - package-ecosystem: bundler 11 | directory: / 12 | registries: "*" 13 | schedule: 14 | interval: daily 15 | time: "09:00" 16 | timezone: Europe/Amsterdam 17 | -------------------------------------------------------------------------------- /lib/statics.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "dry/configurable" 4 | require "dry/struct" 5 | require "dry/core" 6 | require "i18n" 7 | require "yaml" 8 | require "forwardable" 9 | 10 | require "statics/errors" 11 | require "statics/types" 12 | require "statics/collection" 13 | require "statics/model" 14 | require "statics/translatable" 15 | require "statics/version" 16 | 17 | module Statics 18 | extend Dry::Configurable 19 | 20 | setting :data_path, reader: true 21 | end 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Set up Ruby 14 | uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: '3.2' 17 | 18 | - name: Run tests 19 | run: | 20 | gem install bundler 21 | bundle install --jobs 4 --retry 3 22 | bundle exec rspec spec --format RspecJunitFormatter --out coverage/rspec.xml --format progress --profile 10 --color --order random 23 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "simplecov" 4 | 5 | SimpleCov.start 6 | 7 | require "bundler/setup" 8 | require "statics" 9 | require "support/initializer" 10 | require "support/post" 11 | 12 | RSpec.configure do |config| 13 | # Enable flags like --only-failures and --next-failure 14 | config.example_status_persistence_file_path = ".rspec_status" 15 | 16 | # Disable RSpec exposing methods globally on `Module` and `main` 17 | config.disable_monkey_patching! 18 | 19 | config.expect_with :rspec do |c| 20 | c.syntax = :expect 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/statics/translatable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Statics 4 | module Translatable 5 | def self.included(base) 6 | base.extend(ClassMethods) 7 | end 8 | 9 | module ClassMethods 10 | def translatable_attributes(*names, **options) 11 | names.each { |name| translatable_attribute(name, options) } 12 | end 13 | 14 | def translatable_attribute(name, options = {}) 15 | attribute(name, Types::Translations.meta(omittable: options.fetch(:optional, false))) 16 | override_translatable_attribute_getter(name) 17 | end 18 | 19 | def override_translatable_attribute_getter(name) 20 | define_method(name) do |locale: I18n.locale| 21 | attributes.dig(name, locale.to_sym) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /.github/workflows/publish-gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build: 10 | name: Build and Publish 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | ref: "${{ github.event.release.tag_name }}" 17 | 18 | - name: Set up Ruby 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: '3.2' 22 | 23 | - name: Publish to RubyGems 24 | run: | 25 | mkdir -p $HOME/.gem 26 | touch $HOME/.gem/credentials 27 | chmod 0600 $HOME/.gem/credentials 28 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 29 | gem build *.gemspec 30 | gem push *.gem 31 | env: 32 | GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}} 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for taking an interest in this open source project. Your support and involvement is greatly 4 | appreciated. The following sections detail what you need to know in order to contribute. 5 | 6 | ## Code 7 | 8 | 0. Read the project README before starting. 9 | 0. Fork the `master` branch of this repository and clone the fork locally. 10 | 0. Ensure there are no setup, usage, and/or test issues. 11 | 0. Add tests for new functionality and ensure they pass. 12 | 0. Submit a pull request, follow the instructions it provides, and ensure the build passes. 13 | 14 | ## Issues 15 | 16 | 0. Submit an issue via the GitHub Issues tab (assuming one does not 17 | already exist) and follow the instructions it provides. 18 | 19 | ## Feedback 20 | 21 | - Expect a response within one to three business days. 22 | - Changes, alternatives, and/or improvements might be suggested upon review. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pablo Crivella. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/statics/model.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Statics 4 | class Model < Dry::Struct 5 | module Types 6 | include Statics::Types 7 | end 8 | 9 | transform_keys(&:to_sym) 10 | 11 | defines :filename 12 | 13 | attribute :key, Types::Strict::Symbol.constructor(&:to_sym) 14 | 15 | class << self 16 | # @param key [Symbol] 17 | # @raise [Statics::KeyNotFoundError] if key is not found. 18 | # @return [Statics::Model] 19 | def [](key) 20 | new(file_contents.fetch(key.to_s) { raise KeyNotFoundError }.merge(key: key.to_sym)) 21 | end 22 | 23 | # @return [Statics::Collection] 24 | def all 25 | @all ||= Collection.new(records) 26 | end 27 | 28 | def method_missing(method, *, &block) 29 | if Collection.instance_methods.include?(method) 30 | all.public_send(method, *, &block) 31 | else 32 | super 33 | end 34 | end 35 | 36 | def respond_to_missing?(method, include_private = false) 37 | Collection.instance_methods(false).include?(method) || super 38 | end 39 | 40 | private 41 | 42 | # @return [Hash] 43 | def file_contents 44 | @file_contents ||= YAML.load_file(path) 45 | end 46 | 47 | # @return [Array] 48 | def records 49 | file_contents.map do |key, attributes| 50 | new(attributes.merge(key: key.to_sym)) 51 | end 52 | end 53 | 54 | # @return [String] 55 | def path 56 | File.join(Statics.data_path, "#{filename}.yml") 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /statics.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require "statics/version" 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "statics" 9 | spec.email = ["pablocrivella@gmail.com"] 10 | spec.license = "MIT" 11 | spec.version = Statics::VERSION 12 | spec.authors = ["Pablo Crivella"] 13 | spec.homepage = "https://github.com/pcriv/statics" 14 | spec.summary = "Base class and modules for static models." 15 | spec.metadata = { 16 | "bug_tracker_uri" => "https://github.com/pcriv/statics/issues", 17 | "changelog_uri" => "https://github.com/pcriv/statics/blob/master/CHANGELOG.md", 18 | "source_code_uri" => "https://github.com/pcriv/statics" 19 | } 20 | spec.files = Dir["lib/**/*"] 21 | spec.extra_rdoc_files = Dir["README*", "LICENSE*"] 22 | spec.require_paths = ["lib"] 23 | 24 | spec.add_runtime_dependency "dry-configurable", "~> 1.0" 25 | spec.add_runtime_dependency "dry-core", "~> 1.0" 26 | spec.add_runtime_dependency "dry-struct", "~> 1.0" 27 | spec.add_runtime_dependency "dry-types", "~> 1.0" 28 | spec.add_runtime_dependency "i18n", "~> 1.0" 29 | 30 | spec.add_development_dependency "bundler", "~> 2.0" 31 | spec.add_development_dependency "pry", "~> 0.12" 32 | spec.add_development_dependency "pry-byebug", "~> 3.7" 33 | spec.add_development_dependency "rake", "~> 13.0" 34 | spec.add_development_dependency "rspec", "~> 3.0" 35 | spec.add_development_dependency "rspec_junit_formatter", "~> 0.4" 36 | spec.add_development_dependency "rubocop-rspec", "~> 3.0" 37 | spec.add_development_dependency "standard", "~> 1.1" 38 | spec.add_development_dependency "simplecov", "~> 0.1" 39 | end 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## Changed 11 | 12 | - Update dev dependencies. 13 | 14 | ## [2.0.0] - 2019-04-23 15 | 16 | ## Changed 17 | 18 | - [BREAKING] Update dry-struct to version [1.0.0](https://github.com/dry-rb/dry-struct/blob/master/CHANGELOG.md#100-2019-04-23). 19 | - [BREAKING] Update dry-types to version [1.0.0](https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md#100-2019-04-23). 20 | 21 | ## [1.3.0] - 2019-02-06 22 | 23 | ## Added 24 | 25 | - Introduce `Translations` type. 26 | - Add `optional` flag for translatable attributes. 27 | 28 | ## [1.2.0] - 2018-11-05 29 | 30 | ## Changed 31 | 32 | - Update to dry-struct 0.6.0 33 | - Add documentation for omittable attributes to README. 34 | 35 | ## [1.1.1] - 2018-09-11 36 | 37 | ### Added 38 | 39 | - Link to dry-types gem on README. 40 | 41 | ### Changed 42 | 43 | - Isolate Types namespace. 44 | 45 | ## 1.0.1 - 2018-09-08 46 | 47 | ### Added 48 | 49 | - Depfu badge to README. 50 | - Docs badge to README. 51 | 52 | ### Changed 53 | 54 | - Loosen dependency on i18n. 55 | - Update CODE_OF_CONDUCT. 56 | - Update README. 57 | - Fix link to LICENSE on README. 58 | 59 | ## 1.0.0 - 2018-08-31 60 | 61 | ### Added 62 | 63 | - First release. 64 | 65 | [Unreleased]: https://github.com/pcriv/statics/compare/v2.0.0...HEAD 66 | [2.0.0]: https://github.com/pcriv/statics/compare/v1.3.0...v2.0.0 67 | [1.3.0]: https://github.com/pcriv/statics/releases/tag/v1.2.1...v1.3.0 68 | [1.2.0]: https://github.com/pcriv/statics/releases/tag/v1.1.1...v1.2.0 69 | [1.1.1]: https://github.com/pcriv/statics/releases/tag/v1.1.1 70 | -------------------------------------------------------------------------------- /lib/statics/collection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Statics 4 | class Collection 5 | extend Forwardable 6 | include Enumerable 7 | include Dry::Core::Equalizer(:records) 8 | 9 | def_delegators :@records, :last, :size 10 | 11 | # @param records [Array] 12 | def initialize(records) 13 | @records = records 14 | end 15 | 16 | # @return [Statics::Collection] 17 | def each(&block) 18 | self.class.new(records.each(&block)) 19 | end 20 | 21 | # @return [Statics::Collection] 22 | def select(&block) 23 | self.class.new(records.select(&block)) 24 | end 25 | 26 | # @return [Statics::Collection] 27 | def reject(&block) 28 | self.class.new(records.reject(&block)) 29 | end 30 | 31 | # @param conditions [Hash] 32 | # @return [Statics::Collection] 33 | def where(conditions) 34 | select { |record| filter?(record, conditions) } 35 | end 36 | 37 | # @param conditions [Hash] 38 | # @return [Statics::Collection] 39 | def where_not(conditions) 40 | reject { |record| filter?(record, conditions) } 41 | end 42 | 43 | # @param conditions [Hash] 44 | # @return [Statics::Model] 45 | def find_by(conditions) 46 | find { |record| filter?(record, conditions) } 47 | end 48 | 49 | # @return [Array] 50 | def keys 51 | pluck(:key) 52 | end 53 | 54 | # @param attributes [Array] 55 | # @return [Array] 56 | def pluck(*attributes) 57 | map { |record| record.attributes.slice(*attributes).values } 58 | .tap { |result| result.flatten! if attributes.size == 1 } 59 | end 60 | 61 | private 62 | 63 | attr_reader :records 64 | 65 | # @param record [Static::Model] 66 | # @param conditions [Hash] 67 | # @return [true, false] 68 | def filter?(record, conditions) 69 | conditions.all? do |attribute, value| 70 | case value 71 | when Array 72 | value.include?(record.send(attribute)) 73 | else 74 | record.send(attribute) == value 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/statics/model_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe Statics::Model do 6 | let(:post) { Post.new(key: "post3", title: "Post", body: {:en => "Hi!", "nl" => "Hoi!"}) } 7 | 8 | describe ".all" do 9 | subject { Post.all } 10 | 11 | it { is_expected.to be_a Statics::Collection } 12 | end 13 | 14 | describe ".where" do 15 | subject { Post.where(key: :post1) } 16 | 17 | it { is_expected.to be_a Statics::Collection } 18 | it { is_expected.to include(Post[:post1]) } 19 | end 20 | 21 | describe ".where_not" do 22 | subject { Post.where_not(key: :post1) } 23 | 24 | it { is_expected.to be_a Statics::Collection } 25 | it { is_expected.not_to include(Post[:post1]) } 26 | end 27 | 28 | describe ".keys" do 29 | subject { Post.keys } 30 | 31 | it { is_expected.to be_an Array } 32 | it { is_expected.to include(:post1, :post2) } 33 | end 34 | 35 | describe ".pluck" do 36 | subject { Post.pluck(:title) } 37 | 38 | it { is_expected.to be_an Array } 39 | it { is_expected.to include("Post 1", "Post 2") } 40 | end 41 | 42 | describe ".find_by" do 43 | subject { Post.find_by(title: "Post 1") } 44 | 45 | it { is_expected.to eq Post[:post1] } 46 | end 47 | 48 | describe "[]" do 49 | subject { Post[:post1] } 50 | 51 | context "when key is found" do 52 | it { is_expected.to be_a described_class } 53 | end 54 | 55 | context "when key is not found" do 56 | it { expect { Post[:not_a_key] }.to raise_error(Statics::KeyNotFoundError) } 57 | end 58 | end 59 | 60 | describe "#key" do 61 | subject { post.key } 62 | 63 | it { is_expected.to be_a Symbol } 64 | end 65 | 66 | describe "transtable attribute" do 67 | context "when no locale param is given" do 68 | subject { post.body } 69 | 70 | it { is_expected.to eq "Hi!" } 71 | end 72 | 73 | context "when locale param is given" do 74 | subject { post.body(locale: :nl) } 75 | 76 | it { is_expected.to eq "Hoi!" } 77 | end 78 | end 79 | 80 | describe "optional transtable attribute" do 81 | context "when attribute key is not present and no locale param is given" do 82 | subject { post.footer } 83 | 84 | it { is_expected.to eq(nil) } 85 | end 86 | 87 | context "when attribute key is not present and locale param is given" do 88 | subject { post.footer(locale: :nl) } 89 | 90 | it { is_expected.to eq(nil) } 91 | end 92 | 93 | context "when attribute key is present" do 94 | subject { post.footer } 95 | 96 | let(:post) { Post[:post3] } 97 | 98 | it { is_expected.to eq("Good evening") } 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making 6 | participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, 7 | disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, 8 | religion, or sexual identity and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to creating a positive environment include: 13 | 14 | * Using welcoming and inclusive language 15 | * Being respectful of differing viewpoints and experiences 16 | * Gracefully accepting constructive criticism 17 | * Focusing on what is best for the community 18 | * Showing empathy towards other community members 19 | 20 | Examples of unacceptable behavior by participants include: 21 | 22 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 23 | * Trolling, insulting/derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 26 | * Other conduct which could reasonably be considered inappropriate in a professional setting 27 | 28 | ## Our Responsibilities 29 | 30 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take 31 | appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, 34 | issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any 35 | contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the 40 | project or its community. Examples of representing a project or community include using an official project e-mail 41 | address, posting via an official social media account, or acting as an appointed representative at an online or offline 42 | event. Representation of a project may be further defined and clarified by project maintainers. 43 | 44 | ## Enforcement 45 | 46 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 47 | [pablocrivella@gmail.com](mailto:pablocrivella@gmail.com). All complaints will be reviewed and 48 | investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project 49 | team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific 50 | enforcement policies may be posted separately. 51 | 52 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent 53 | repercussions as determined by other members of the project's leadership. 54 | 55 | ## Attribution 56 | 57 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 58 | available at [http://contributor-covenant.org/version/1/4][version] 59 | 60 | [homepage]: http://contributor-covenant.org 61 | [version]: http://contributor-covenant.org/version/1/4 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Statics 2 | 3 | [![Gem](https://img.shields.io/gem/v/statics.svg?style=flat)](http://rubygems.org/gems/statics) 4 | [![Depfu](https://badges.depfu.com/badges/6f2f73672eae4d603d6ae923164435e2/overview.svg)](https://depfu.com/github/pcriv/statics?project=Bundler) 5 | [![Inline docs](http://inch-ci.org/github/pcriv/statics.svg?branch=master&style=shields)](http://inch-ci.org/github/pcriv/statics) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/935822c7c481aa464186/maintainability)](https://codeclimate.com/github/pcriv/statics/maintainability) 7 | [![Test Coverage](https://api.codeclimate.com/v1/badges/935822c7c481aa464186/test_coverage)](https://codeclimate.com/github/pcriv/statics/test_coverage) 8 | 9 | Base class and modules for static models. 10 | 11 | Links: 12 | 13 | - [API Docs](https://www.rubydoc.info/gems/statics) 14 | - [Contributing](https://github.com/pcriv/statics/blob/master/CONTRIBUTING.md) 15 | - [Code of Conduct](https://github.com/pcriv/statics/blob/master/CODE_OF_CONDUCT.md) 16 | 17 | ## Requirements 18 | 19 | 1. [Ruby 3.0.0](https://www.ruby-lang.org) 20 | 21 | ## Installation 22 | 23 | To install, run: 24 | 25 | ```sh 26 | gem install statics 27 | ``` 28 | 29 | Or add the following to your Gemfile: 30 | 31 | ```sh 32 | gem "statics" 33 | ``` 34 | 35 | ## Usage 36 | 37 | Setting the data path: 38 | 39 | ```ruby 40 | Statics.configure do |config| 41 | config.data_path = "data/" 42 | end 43 | ``` 44 | 45 | Defining a static model: 46 | 47 | ```ruby 48 | class Post < Statics::Model 49 | filename "posts" 50 | 51 | attribute :title, Types::Strict::String 52 | end 53 | ``` 54 | 55 | ```yml 56 | # data/posts.yml 57 | --- 58 | post1: 59 | title: "Post 1" 60 | 61 | post2: 62 | title: "Post 2" 63 | ``` 64 | 65 | ```ruby 66 | Post.all 67 | #=> #, #]> 68 | Post.where(title: "Post 1") 69 | #=> #]> 70 | Post.where_not(title: "Post 1") 71 | #=> #]> 72 | Post.find_by(key: :post1) 73 | #=> # 74 | Post[:post1] 75 | #=> # 76 | Post.pluck(:title) 77 | #=> ["Post 1", "Post 2"] 78 | post = Post.first 79 | #=> # 80 | post.key 81 | #=> :post1 82 | post.title 83 | #=> "Post 1" 84 | post.attributes 85 | #=> {:title=>"Post 1", :key=>:post1} 86 | ``` 87 | 88 | Defining translatable attributes: 89 | 90 | ```ruby 91 | class Post < Statics::Model 92 | include Statics::Translatable 93 | 94 | filename "posts" 95 | 96 | attribute :title, Types::Strict::String 97 | translatable_attribute :body 98 | end 99 | ``` 100 | 101 | ```yml 102 | # data/posts.yml 103 | --- 104 | post1: 105 | title: "Post 1" 106 | body: 107 | en: "Hello!" 108 | nl: "Hallo!" 109 | 110 | post2: 111 | title: "Post 2" 112 | body: 113 | en: "Bye!" 114 | nl: "Doei!" 115 | ``` 116 | 117 | ```ruby 118 | post = Post.first 119 | # when I18n.locale is :en 120 | post.body #=> "Hello!" 121 | post.body(locale: :nl) #=> "Hallo!" 122 | ``` 123 | 124 | Defining omittable attributes with defaults: 125 | 126 | ```ruby 127 | class Post < Statics::Model 128 | include Statics::Translatable 129 | 130 | filename "posts" 131 | 132 | attribute :title, Types::Strict::String 133 | # With default 134 | attribute? :author, Types::Strict::String.default("Unknown") 135 | # Without default 136 | # attribute? :author, Types::Strict::String 137 | end 138 | ``` 139 | 140 | ```yml 141 | # data/posts.yml 142 | --- 143 | post1: 144 | title: "Post 1" 145 | author: "Rick Sanchez" 146 | 147 | post2: 148 | title: "Post 2" 149 | ``` 150 | 151 | ```ruby 152 | post1 = Post.first 153 | post1.author #=> "Rick Sanchez" 154 | post2 = Post.last 155 | post2.author #=> "Unknown" 156 | ``` 157 | 158 | Check [dry-types](https://dry-rb.org/gems/dry-types) for documentation about the [built-in types](https://dry-rb.org/gems/dry-types/built-in-types/). 159 | 160 | ## Caveats 161 | 162 | If you have dates in your yaml-files, use the following format for them to be handled properly: `YYYY-MM-DD` 163 | 164 | ## Tests 165 | 166 | To test, run: 167 | 168 | ``` 169 | bundle exec rspec spec/ 170 | ``` 171 | 172 | ## Versioning 173 | 174 | Read [Semantic Versioning](https://semver.org) for details. Briefly, it means: 175 | 176 | - Major (X.y.z) - Incremented for any backwards incompatible public API changes. 177 | - Minor (x.Y.z) - Incremented for new, backwards compatible, public API enhancements/fixes. 178 | - Patch (x.y.Z) - Incremented for small, backwards compatible, bug fixes. 179 | 180 | ## License 181 | 182 | Copyright 2018 [Pablo Crivella](https://pcriv.com). 183 | Read [LICENSE](LICENSE) for details. 184 | --------------------------------------------------------------------------------