• Stars
    star
    99
  • Rank 343,315 (Top 7 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple and lightweight Virtus analog.

ShallowAttributes

Build Status Code Climate Coverage Status Inline docs

Simple and lightweight Virtus analog without any dependencies. Documentation.

Motivation

There are already a lot of good and flexible gems which solve a similar problem, allowing attributes to be defined with their types, for example: virtus, fast_attributes or attrio. However, the disadvantage of these gems is performance or API. So, the goal of ShallowAttributes is to provide a simple solution which is similar to the Virtus API, simple, fast, understandable and extendable.

Installation

Add this line to your application's Gemfile:

gem 'shallow_attributes'

And then execute:

$ bundle

Or install it yourself as:

$ gem install shallow_attributes

Examples

Table of contents

Using ShallowAttributes with Classes

You can create classes extended with Virtus and define attributes:

class User
  include ShallowAttributes

  attribute :name, String
  attribute :age, Integer
  attribute :birthday, DateTime
end

class SuperUser < User
  include ShallowAttributes

  attribute :name, String
  attribute :age, Integer, allow_nil: true
  attribute :birthday, DateTime
end

user = User.new(name: 'Anton', age: 31)
user.name       # => "Anton"

user.age = '31' # => 31
user.age = nil  # => nil
user.age.class  # => Fixnum

user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>

user.attributes # => { name: "Anton", age: 31, birthday: nil }

# mass-assignment
user.attributes = { name: 'Jane', age: 21 }
user.name       # => "Jane"
user.age        # => 21

super_user = SuperUser.new
user.age = nil  # => 0

ShallowAttributes doesn't make any assumptions about base classes. There is no need to define default attributes, or even mix ShallowAttributes into the base class:

require 'active_model'

class Form
  extend ActiveModel::Naming
  extend ActiveModel::Translation
  include ActiveModel::Conversion
  include ShallowAttributes

  def persisted?
    false
  end
end

class SearchForm < Form
  attribute :name, String
end

form = SearchForm.new(name: 'Anton')
form.name # => "Anton"

Default Values

class Page
  include ShallowAttributes

  attribute :title, String

  # default from a singleton value (integer in this case)
  attribute :views, Integer, default: 0

  # default from a singleton value (boolean in this case)
  attribute :published, 'Boolean', default: false

  # default from a callable object (proc in this case)
  attribute :slug, String, default: lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }

  # default from a method name as symbol
  attribute :editor_title, String,  default: :default_editor_title

  private

  def default_editor_title
    published ? title : "UNPUBLISHED: #{title}"
  end
end

page = Page.new(title: 'Virtus README')
page.slug         # => 'virtus-readme'
page.views        # => 0
page.published    # => false
page.editor_title # => "UNPUBLISHED: Virtus README"

page.views = 10
page.views                    # => 10
page.reset_attribute(:views)  # => 0
page.views                    # => 0

Mandatory attributes

You can provide present: true option for any attribute that will prevent class from initialization if this attribute was not provided:

class CreditCard
  include ShallowAttributes
  attribute :number, Integer, present: true
  attribute :owner, String, present: true
end

card = CreditCard.new(number: 1239342)
# => ShallowAttributes::MissingAttributeError: Mandatory attribute "owner" was not provided

Embedded Value

class City
  include ShallowAttributes

  attribute :name, String
  attribute :size, Integer, default: 9000
end

class Address
  include ShallowAttributes

  attribute :street,  String
  attribute :zipcode, String, default: '111111'
  attribute :city,    City
end

class User
  include ShallowAttributes

  attribute :name,    String
  attribute :address, Address
end

user = User.new(address: {
  street: 'Street 1/2',
  zipcode: '12345',
  city: {
    name: 'NYC'
  }
})

user.address.street    # => "Street 1/2"
user.address.city.name # => "NYC"

Custom Coercions

require 'json'

class Json
  def coerce(value, options = {})
    value.is_a?(::Hash) ? value : JSON.parse(value)
  end
end

class User
  include ShallowAttributes

  attribute :info, Json, default: {}
end

user = User.new
user.info = '{"email":"[email protected]"}' # => {"email"=>"[email protected]"}
user.info.class                           # => Hash

# With a custom attribute encapsulating coercion-specific configuration
class NoisyString
  def coerce(value, options = {})
    value.to_s.upcase
  end
end

class User
  include ShallowAttributes

  attribute :scream, NoisyString
end

user = User.new(scream: 'hello world!')
user.scream # => "HELLO WORLD!"

Collection Member Coercions

# Support "primitive" classes
class Book
  include ShallowAttributes

  attribute :page_numbers, Array, of: Integer
end

book = Book.new(:page_numbers => %w[1 2 3])
book.page_numbers # => [1, 2, 3]

# Support EmbeddedValues, too!
class Address
  include ShallowAttributes

  attribute :address,     String
  attribute :locality,    String
  attribute :region,      String
  attribute :postal_code, String
end

class PhoneNumber
  include ShallowAttributes

  attribute :number, String
end

class User
  include ShallowAttributes

  attribute :phone_numbers, Array, of: PhoneNumber
  attribute :addresses,     Array, of: Address
end

user = User.new(
  :phone_numbers => [
    { :number => '212-555-1212' },
    { :number => '919-444-3265' } ],
  :addresses => [
    { :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ])

user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">, #<PhoneNumber:0x007fdb2d3beb00 @number="919-444-3265">]
user.addresses     # => [#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">]

user.attributes
# => {
# =>   :phone_numbers => [
# =>     { :number => '212-555-1212' },
# =>     { :number => '919-444-3265' }
# =>   ],
# =>   :addresses => [
# =>     {
# =>       :address => '1234 Any St.',
# =>       :locality => 'Anytown',
# =>       :region => "DC",
# =>       :postal_code => "21234"
# =>     }
# =>   ]
# => }

IMPORTANT note about member coercions

ShallowAttributes performs coercions only when a value is being assigned. If you mutate the value later on using its own interfaces then coercion won't be triggered.

Here's an example:

class Book
  include ShallowAttributes
  attribute :title, String
end

class Library
  include ShallowAttributes
  attribute :books, Array, of: Book
end

library = Library.new

# This will coerce Hash to a Book instance
library.books = [ { :title => 'Introduction' } ]

# This WILL NOT COERCE the value because you mutate the books array with Array#<<
library.books << { :title => 'Another Introduction' }

Overriding setters

class User
  include ShallowAttributes

  attribute :name, String

  alias_method :_name=, :name=
  def name=(new_name)
    custom_name = nil
    if new_name == "Godzilla"
      custom_name = "Can't tell"
    end

    self._name = custom_name || new_name
  end
end

user = User.new(name: "Frank")
user.name # => 'Frank'

user = User.new(name: "Godzilla")
user.name # => 'Can't tell'

ActiveModel compatibility

ShallowAttributes is fully compatible with ActiveModel.

Form object

require 'active_model'

class SearchForm
  extend ActiveModel::Naming
  extend ActiveModel::Translation
  include ActiveModel::Conversion
  include ShallowAttributes

  attribute :name, String
  attribute :service_ids, Array, of: Integer
  attribute :archived, 'Boolean', default: false

  def persisted?
    false
  end

  def results
    # ...
  end
end

class SearchesController < ApplicationController
  def index
    search_params = params.require(:search_form).permit(...)
    @search_form = SearchForm.new(search_params)
  end
end
<h1>Search</h1>
<%= form_for @search_form do |f| %>
  <%= f.text_field :name %>
  <%= f.collection_check_boxes :service_ids, Service.all, :id, :name %>
  <%= f.select :archived, [['Archived', true], ['Not Archived', false]] %>
<% end %>

Validations

require 'active_model'

class Children
  include ShallowAttributes
  include ActiveModel::Validations

  attribute :scream, String
  validates :scream, presence: true
end

user = User.new(scream: '')
user.valid? # => false
user.scream = 'hello world!'
user.valid? # => true

Dry-types

You can use dry-types objects as a type for your attribute:

module Types
  include Dry::Types.module
end

class User
  include ShallowAttributes

  attribute :name, Types::Coercible::String
  attribute :age, Types::Coercible::Int
  attribute :birthday, DateTime
end

user = User.new(name: nil, age: 0)
user.name # => ''
user.age # => 0

Ruby version support

ShallowAttributes is known to work correctly with the following rubies:

  • 2.0
  • 2.1
  • 2.2
  • 2.3
  • 2.4
  • jruby-head

Also we run rbx-2 buld too.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/davydovanton/shallow_attributes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

More Repositories

1

sidekiq-statistic

See statistic about your workers
Ruby
797
star
2

awesome-hanami

A collection of awesome Hanami Gems and projects
Ruby
410
star
3

kan

Simple, functional authorization library and role management for ruby
Ruby
234
star
4

stop_active_support_anywhere

Stop use Active Support in not rails related projects
Ruby
146
star
5

rubyjobs.dev

Job boards for Russian-speaking Ruby developers
Ruby
80
star
6

novel

Orchestration SAGA builder for ruby
Ruby
47
star
7

hanami-architecture

Ideas and suggestions about architecture for hanami projects
47
star
8

rspec-hanami

RSpec Matchers for Hanami
Ruby
46
star
9

cqrs-ruby-example

Example of using CQRS with ruby, hanami-api, rom, dry-system and kafka
Ruby
42
star
10

AnyBar_rb

Ruby wrapper for AnyBar.app
Ruby
37
star
11

ruby-service-template

Simple (Micro)Service template based on dry-system, hanami-api, hanami 2.0, rom, rabbitmq, kafka, psql, and other microlibraries.
Ruby
31
star
12

relative_time

Micro lib without any dependency for getting relative time
Ruby
22
star
13

hanami-serializer

Serializer library for hanami applications
Ruby
22
star
14

dotfiles

My dotfiles
Vim Script
21
star
15

rodauth_hanami

Example app for integrate rodauth server to hanami app
Ruby
16
star
16

web_bouncer

Simple and module auth lib for any rack projects
Ruby
16
star
17

popug-inventory

HTML
16
star
18

layerd-arch-style-with-dry-example

Example of layered architecture style builded with dry-rb libs
Ruby
16
star
19

hanami_event_example

Simple hanami app with hanami events
Ruby
15
star
20

hanami-pagination

Ruby
14
star
21

hanami-bootstrap

Bootstrap wrapper for hanami framework.
Ruby
14
star
22

cookie_box

Follow and controll issues from several repositories from one place
Ruby
12
star
23

hanami-scaffold

Make hanami scaffolds faster
Ruby
12
star
24

octostar

Improved search for your github stars
Ruby
11
star
25

event_schema_registry

Simple implantation of schema registry for JSON schema events
Ruby
11
star
26

rom_sql_graph

Display your DB (sql) association graph
Ruby
9
star
27

soa-readiness-checklist

Check your system for SOA readiness
9
star
28

state_changer

The state machine for change your data between states.
Ruby
9
star
29

dry-system-hanami

Folder resolver for dry-system in hanami projects
Ruby
8
star
30

hanami-zsh

Zsh plugin for hanami.
7
star
31

hanami-rodauth

This repository is no longer maintain: Roudauth wrapper for hanami apps
Ruby
7
star
32

ivento

Simple event sourcing framework in functional style
Ruby
7
star
33

rlisp

Simple scheme interpreter written on ruby
Ruby
7
star
34

hanami-bench

Benchmarks for hanami
Ruby
6
star
35

link-shortener

Simple hanami link shortener application
Ruby
6
star
36

ruby-job-task

Тестовое задание на позицию ruby разработчика
Ruby
6
star
37

vim-html2slim

Vim plugin for convert HTML(erb) to Slim
Vim Script
6
star
38

hanami-project-template

Template repository for all new project
Ruby
5
star
39

AnyBar_cr

Simple crystal wrapper for AnyBar
Crystal
5
star
40

igoods-service-template

Igoods service template
Ruby
4
star
41

excess.zsh-theme

Simple zsh color theme
4
star
42

davydovanton.github.io

HTML
4
star
43

grape-rodauth

Simple grape app with rodauth
Ruby
4
star
44

kaminari-hanami

This repository is no longer maintain: Kaminari integration for @hanami
Ruby
4
star
45

yeelight-lamp-client

Ruby client for yeelight lamps
Ruby
4
star
46

pepe

Pepe for you ruby console
Ruby
3
star
47

dry-http-client

This repository is no longer maintain: Fundctional HTTP client based on dry stack.
Ruby
3
star
48

rubyunderhood

Коллективный твиттер-аккаунт для ruby разработчиков с новым автором каждую неделю.
3
star
49

hanami-action-documentation

Automatically generate documentation for you actions
Ruby
2
star
50

system-analysis-example-system

2
star
51

event_sourcing_ruby

Simple repository for playing with event sourcing conceptions from F#
Ruby
2
star
52

hanami-workshop

Simple hanami app for workshop
Ruby
2
star
53

lotus-webpack-reactjs

Simple lotus app with webpack and react js
JavaScript
2
star
54

hanami-graphql-example

Simple example of GQL in hanami application
Ruby
2
star
55

arch-katas-solution

Personal repository for arch kata solutions (rus)
1
star
56

hanami-auth

This repository is no longer maintain. Prototype for hanami auth (bad way)
Ruby
1
star
57

hanami-interactor-matcher

This repository is no longer maintain: Simple dry-matcher for hanami interactor
Ruby
1
star
58

service-separation-checklist

Simple checklist for service separation
1
star
59

bruevich

Ruby
1
star
60

momiji

Post framework
Ruby
1
star
61

hanami-operation-generator

Simple library for generating operations for hanami-dry-system project
Ruby
1
star
62

data_matrix

simple way for creating grids for any data objects and for any frameworks
Ruby
1
star