From 3b836114693b5788fcaeffa1f14082d6f313a5e5 Mon Sep 17 00:00:00 2001 From: Igor Grubisic Date: Mon, 17 Jan 2022 00:08:27 +0100 Subject: [PATCH] Automatically build missing values for valid fields. Allow update value for fields that do not have inserted record in appropriate value table. --- README.md | 102 ++++++++++++++++++++++++++++++++++-- lib/dynabute/dynabutable.rb | 80 ++++++++++++++++++++++++---- 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d71863b..0b852a8 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,29 @@ now set value ```ruby user = User.create -user.build_dynabute_value( name: 'age', value: 35 ).save -# => +user.set_dynabute_value( name: 'age', value: 40 ) +# => # +user.set_dynabute_value( name: 'skinny', value: true ) +# => # +user.save +# => true + +# or update single value +user.set_dynabute_value( name: 'age', value: 35 ).save +# => true ``` check the value ```ruby -user.dynabute_value( name: 'age' ).value +user.get_dynabute_value(name: 'age') +# => 35 +``` + +or check entire value object +```ruby +value_obj = user.dynabute_value( name: 'age' ) +# => # +value_obj.value # => 35 ``` @@ -39,6 +55,75 @@ user.dynabute_age_value.value # => 35 ``` +set values for fields which can contain more then one value +```ruby +user = User.create + +user.set_dynabute_value( name: 'personalities', value: 'good' ) +# => # +user.set_dynabute_value( name: 'personalities', value: 'bad' ) +# => # +user.set_dynabute_value( name: 'personalities', value: 'ugly' ) +# => # +user.save +# => true +``` + +get values for fields which can contain more then one value +```ruby +user = User.first + +user.get_dynabute_value( name: 'personalities' ) +# => ["good", "bad", "ugly"] + +user.dynabute_value( name: 'personalities' ) +# => [#, +# #, +# #] +``` + +update specific value for field with `has_many` option +```ruby +user = User.first + +values_obj = user.dynabute_value( name: 'personalities' ) +# => [#, +# #, +# #] +good_value_obj = values_obj.first +# => # +bad_value_obj = values_obj.last +# => # +user.set_dynabute_value( name: 'personalities', value: 'very ugly', value_id: good_value_obj.id ) +# => # +user.set_dynabute_value( name: 'personalities', value: 'very ugly', value_id: bad_value_obj.id ) +# => # +user.save +# => true + +# in case all changes are not required to be saved within db transaction, +# each value can be saved separately. +good_value_obj.value = 'very very good' +# => "very very good" +good_value_obj.save +``` + +remove value +```ruby +user = User.first + +user.remove_dynabute_value( name: 'age' ) +# => # + +value_obj = user.dynabute_value( name: 'personalities' ).first +user.remove_dynabute_value( name: 'personalities', value_id: value_obj.id ) +# => # + +user.remove_dynabute_value( name: 'personalities' ) +# => [#, +# #] +``` + nested attributes of glory ```ruby personality_field_id = User.dynabutes.find_by( name: 'personalities' ).id @@ -102,6 +187,17 @@ $ rake db:migrate ``` ## Contributing + +#### Rspec: set test environment +```bash +$ bundle install +$ cd spec/dummy/ +$ RAILS_ENV=test bundle exec rake db:create +$ RAILS_ENV=test bundle exec rake db:migrate +$ cd ../../ +$ bundle exec rspec +``` + yea? ## License diff --git a/lib/dynabute/dynabutable.rb b/lib/dynabute/dynabutable.rb index dc6f911..c2374d6 100644 --- a/lib/dynabute/dynabutable.rb +++ b/lib/dynabute/dynabutable.rb @@ -4,6 +4,7 @@ module Dynabute module Dynabutable + class ValueNotFound < StandardError; end extend ActiveSupport::Concern included do @@ -36,18 +37,72 @@ def dynabute_values end def dynabute_value(name: nil, field_id: nil, field: nil) - field = find_field(name, field_id, field) - - if field.has_many - send(Util.value_relation_name(field.value_type)).select{|v| v.field_id == field.id } + field_obj = find_field(name, field_id, field) + field_values = send(Util.value_relation_name(field_obj.value_type)) + field_value = if field_obj.has_many + field_values.select{ |v| v.field_id == field_obj.id } else - send(Util.value_relation_name(field.value_type)).detect{|v| v.field_id == field.id } + field_values.detect{ |v| v.field_id == field_obj.id } end + field_value end def build_dynabute_value(name: nil, field_id: nil, field: nil, **rest) - field = find_field(name, field_id, field) - send(Util.value_relation_name(field.value_type)).build(field_id: field.id, **rest) + field_obj = find_field(name, field_id, field) + send(Util.value_relation_name(field_obj.value_type)).build(field_id: field_obj.id, **rest) + end + + # Returns value attribute for specified field. + # If field can have multiple values for single target model object, + # an array of values is returned, unless specific value_id is provided. + def get_dynabute_value(name: nil, field_id: nil, field: nil, value_id: nil) + field_obj = find_field(name, field_id, field) + value_obj = dynabute_value(field: field_obj) + return unless value_obj + if field_obj.has_many && value_id + value_obj = value_obj.detect{|v| v.id == value_id} + end + value_obj.is_a?(Array) ? value_obj.map(&:value) : value_obj.value + end + + # Sets the value in the target model object nested attribute structure. + # If field can have multiple values for single target model object, + # a new value will be added, unless specific value_id os provided. + # + # This method does not store changes in the database. "save" method should + # be called on target model to store all changes, or individually on every + # value record returned by this method. + def set_dynabute_value(name: nil, field_id: nil, field: nil, value: nil, value_id: nil) + field_obj = find_field(name, field_id, field) + value_obj = dynabute_value(field: field_obj) + if field_obj.has_many + if value_id + value_obj = value_obj.detect{|v| v.id == value_id} if value_obj + fail ValueNotFound unless value_obj + else + value_obj = build_dynabute_value(field: field_obj) + end + else + value_obj ||= build_dynabute_value(field: field_obj) + end + value_obj.value = value + value_obj + end + + # Removes value from database and keeps dynabute relations up-to-date. + # If field can have multiple values for single target model object, + # all values will be removed, unless specific value_id os provided. + # + # This method stores changes in the database + def remove_dynabute_value(name: nil, field_id: nil, field: nil, value_id: nil) + field_obj = find_field(name, field_id, field) + value_obj = dynabute_value(field: field_obj) + if value_obj && field_obj.has_many && value_id + value_obj = value_obj.detect{|v| v.id == value_id} + end + if value_obj + send(Util.value_relation_name(field_obj.value_type)).destroy(value_obj) + end end def method_missing(*args) @@ -64,10 +119,13 @@ def method_missing(*args) private def find_field(name, id, field) - name_or_id = {name: name, id: id}.compact - return nil if name_or_id.blank? && field.blank? - field_obj = field || Dynabute::Field.find_by(name_or_id.merge(target_model: self.class.to_s)) - fail Dynabute::FieldNotFound.new(name_or_id, field) if field_obj.nil? + field_obj = field + if field_obj.blank? + name_or_id = {name: name, id: id}.compact + return nil if name_or_id.blank? + field_obj = Dynabute::Field.find_by(name_or_id.merge(target_model: self.class.to_s)) + fail Dynabute::FieldNotFound.new(name_or_id, field) if field_obj.nil? + end field_obj end