Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically build missing values for valid fields. #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 99 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,29 @@ now set value
```ruby
user = User.create

user.build_dynabute_value( name: 'age', value: 35 ).save
# => <Dynabute::Values::IntegerValue:0x007faba5279540 id: 1, field_id: 1, dynabutable_id: 1, dynabutable_type: "User", value: 35>
user.set_dynabute_value( name: 'age', value: 40 )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: 40>
user.set_dynabute_value( name: 'skinny', value: true )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", 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' )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: 35>
value_obj.value
# => 35
```

Expand All @@ -39,6 +55,75 @@ user.dynabute_age_value.value
# => 35
```

set values for fields which can contain more than one value
```ruby
user = User.create

user.set_dynabute_value( name: 'personalities', value: 'good' )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "good">
user.set_dynabute_value( name: 'personalities', value: 'bad' )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "bad">
user.set_dynabute_value( name: 'personalities', value: 'ugly' )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "ugly">
user.save
# => true
```

get values for fields which can contain more than one value
```ruby
user = User.first

user.get_dynabute_value( name: 'personalities' )
# => ["good", "bad", "ugly"]

user.dynabute_value( name: 'personalities' )
# => [#<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "good">,
# #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "bad">,
# #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "ugly">]
```

update specific value for field with `has_many` option
```ruby
user = User.first

values_obj = user.dynabute_value( name: 'personalities' )
# => [#<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "good">,
# #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "bad">,
# #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "ugly">]
good_value_obj = values_obj.first
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "good">
bad_value_obj = values_obj.last
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "ugly">
user.set_dynabute_value( name: 'personalities', value: 'very good', value_id: good_value_obj.id )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "very good">
user.set_dynabute_value( name: 'personalities', value: 'very ugly', value_id: bad_value_obj.id )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "very ugly">
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' )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: 35>

value_obj = user.dynabute_value( name: 'personalities' ).first
user.remove_dynabute_value( name: 'personalities', value_id: value_obj.id )
# => #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "good">

user.remove_dynabute_value( name: 'personalities' )
# => [#<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "bad">,
# #<Dynabute::Values::StringValue:0x0000 ... dynabutable_type: "User", value: "ugly">]
```

nested attributes of glory
```ruby
personality_field_id = User.dynabutes.find_by( name: 'personalities' ).id
Expand Down Expand Up @@ -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
```

CrAsH1101 marked this conversation as resolved.
Show resolved Hide resolved
yea?

## License
Expand Down
101 changes: 90 additions & 11 deletions lib/dynabute/dynabutable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

module Dynabute
module Dynabutable
class ValueNotFound < StandardError; end
extend ActiveSupport::Concern

included do
Expand Down Expand Up @@ -36,18 +37,73 @@ 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 is 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
Comment on lines +78 to +84
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_dynabute_attributes() for has_many field sounds more like it's replacing the whole values with the given array. having append_dynabute_value (which should throw error when called on non has_many field) seems more natural to me, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea was that you have single method for setting the values. It was intended to be like create_or_update concept.
Just like rails, when submitting a form for persisted object (id exists), it will trigger update action. In case the object is not persisted and there is no id value, form will submit create action and the new record will be added to the database.

So first time you call method for has_many field, it will add specified value, same as with strings and integers, but it will return an Array. Next set will just append to that Array as something already exists.

Let me know if you have something specific on your mind 😉

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 is 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
result_obj = send(Util.value_relation_name(field_obj.value_type)).destroy(value_obj)
value_obj.is_a?(Array) ? result_obj : result_obj.first
end
end

def method_missing(*args)
Expand All @@ -63,11 +119,34 @@ def method_missing(*args)
end

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?
# Validate field argument
if field
unless field.is_a?(Dynabute::Field)
fail ArgumentError, 'Argument field must be Dynabute::Field'
end
return field
end
name_or_id = {}
# Validate name argument
if name
unless name.is_a?(String) || name.is_a?(Symbol)
fail ArgumentError, 'Argument name must be String or Symbol'
end
name_or_id[:name] = name.to_s
end
# Validate id argument
if id
unless id.is_a?(Integer)
fail ArgumentError, 'Argument id must be Integer'
end
name_or_id[:id] = id
end
name_or_id.reject!{ |k, v| v.blank? }
fail ArgumentError, 'Invalid arguments' 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) if field_obj.nil?
field_obj
end

Expand Down
Loading