Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ spec/reports
test/tmp
test/version_tmp
tmp
tags
.idea/.name
.idea/.rakeTasks
.idea/dictionaries/bsundarraj.xml
Expand All @@ -24,4 +25,4 @@ tmp
.idea/plain_old_model.iml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
.idea/workspace.xml
.idea/workspace.xml
11 changes: 11 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,14 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in plain_old_model.gemspec
gemspec

group :test do
gem 'simplecov', :require => false
end

group :development do
# gem 'pry', :require => false
gem 'guard', require: false
gem 'guard-rspec', require: false
end

25 changes: 25 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard :rspec do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^spec/(.+)/.+_spec\.rb$})
watch(%r{^lib/plain_old_model/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }

# # Rails example
# watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
# watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
# watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
# watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
# watch('config/routes.rb') { "spec/routing" }
# watch('app/controllers/application_controller.rb') { "spec/controllers" }
#
# # Capybara features specs
# watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
#
# # Turnip features and steps
# watch(%r{^spec/acceptance/(.+)\.feature$})
# watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
end

98 changes: 57 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,84 @@
# PlainOldModel
# PlainOldModel #

TODO: Write a gem description
Implements nested attribute mass-assignment and has_many and has_one associations, and also pulls in:

## Installation
* ActiveModel::Naming
* ActiveModel::Translation
* ActiveModel::Validations
* ActiveModel::Conversion

Add this line to your application's Gemfile:

gem 'plain_old_model'

And then execute:
### Installation ###

$ bundle
Add this line to your application's Gemfile:

Or install it yourself as:
```ruby
gem install plain_old_model
```

$ gem install plain_old_model
or run

## Usage
Example
=======
```ruby
gem 'plain_old_model'
```

class Person < PlainOldModel::Base
attr_accessor :name, :age, :book

attr_reader :account_number
attr_writer :address
### Usage ###

validates_presence_of :book
end
```ruby
class Person < PlainOldModel::Base
attr_accessor :name
validates_presence_of :book
end

params = {"name" =>"testmeparams", "age" => "25", "book" =>["wewrwrwr", "werwrwrr"]}
params = {"name" =>"Anna", :book => {:author =>"Tolstoy", :category => "fiction"}}

params1 = {:name =>"testmeparams", :age => "25", :book => {:author =>"my name", :category => "fiction"}}

p = Person.new(params)

p.book # ["wewrwrwr", "werwrwrr"]

p.book
p.valid? #true
```


### Mass Assignment ###

```ruby
p.assign_attributes({name: "Anna", book: {author: "Tolstoy", category: "fiction"}})
```

or

OR
p = Person.new()
```ruby
p.attributes = {:name =>"Anna", :book => {:author =>"Tolstoy", :category => "fiction"}}
```

p.assign_attributes(params11)

=====================================================================
p1 = Person.new(params1)
### Associations ###

p1.book # {:author =>"my name", :category => "fiction"}
* has_one
* has_many

p.attributes #[:name, :age, :book, :account_number, :address]
```ruby
class Person < PlainOldModel::Base
has_many :phones
end
```
or, with optional factory_method and class_name:

```ruby
class Person < PlainOldModel::Base
has_many :phones, factory_method: :create, class_name: :telephone
end

TODO:
class Telephone < PlainOldModel::Base
attr_accessor :number, :extension

* Association(s)
* mass assignments
*
def self.create(attributes)
attributes[:extension] = '000'
new(attributes)
end
end
```

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
3 changes: 2 additions & 1 deletion examples/book.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require_relative '../lib/plain_old_model/base'

class Book < PlainOldModel::Base

attr_accessor :author, :category

end
end
156 changes: 87 additions & 69 deletions lib/plain_old_model/attribute_assignment.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
require 'active_support/all'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/indifferent_access'

module PlainOldModel
module AttributeAssignment

def assign_attributes(new_attributes, options = {})
return unless new_attributes
@new_attributes = new_attributes.dup
assign_attributes_from_associations
assign_simple_attributes
end

alias attributes= assign_attributes

def self.included(klass)
klass.extend ClassMethods
end

def associations
self.class.associations
end


module ClassMethods

def has_one(attr_name, options={})
associations << HasOneAssociation.new(attr_name, options)
attr_accessor attr_name
Expand All @@ -19,29 +40,6 @@ def associations

end

def self.included(klass)
klass.extend ClassMethods
end

def assign_attributes(new_attributes, options = {})
return unless new_attributes
attributes = new_attributes.dup

associations.each do |association|
attr_name = association.attr_name
if attributes.include?(attr_name)
merged_hash = merge_association_instance_variables_with_attributes(association, attr_name, attributes)
value = association.create_value_from_attributes(merged_hash)
set_attribute(attr_name, value)
attributes = attributes.delete_if { |key, value| key.to_s == attr_name.to_s }
end
end
assign_simple_attributes(attributes, options)
end

def associations
self.class.associations
end

class Association
attr_reader :attr_name
Expand Down Expand Up @@ -72,10 +70,11 @@ def klass_from_attr_name
end
end

class HasOneAssociation < Association

class HasOneAssociation < Association
end


class HasManyAssociation < Association
def create_value_from_attributes(items)
items.map{|item| super(item)}
Expand All @@ -86,65 +85,84 @@ def klass_from_attr_name
end
end


private

def sanitize_attributes(attributes)
sanitized_attributes = {}
attributes.each do |k, v|
if respond_to?("#{k}=") || respond_to?("#{k}")
sanitized_attributes[k]=v
def assign_attributes_from_associations
associations.each do |association|
assign_attributes_from_association(association)
end
end
sanitized_attributes
end

def assign_simple_attributes(attributes, options)
attributes = sanitize_attributes(attributes).stringify_keys

attributes.each do |k, v|
set_attribute(k, v)
def assign_attributes_from_association(association)
attr_name = association.attr_name
if @new_attributes.include?(attr_name)
merged_hash = merged_association_hash(association, attr_name)
value = association.create_value_from_attributes(merged_hash)
set_attribute(attr_name, value)
@new_attributes.delete_if { |key, value| key.to_s == attr_name.to_s }
end
end
end

def set_attribute(attr_name, value)
if respond_to?("#{attr_name}=")
send("#{attr_name}=", value)
else
instance_variable_set("@#{attr_name}".to_sym, value)
# deep merge of instance variables from one association
# with the rest of the 'new_attributes'
def merged_association_hash(association, attr_name)
association_instance = send(attr_name)

if association.class == HasOneAssociation
return instance_variables_hash(association_instance).deep_merge(@new_attributes[attr_name])
elsif association.class == HasManyAssociation
if association_instance.nil?
return @new_attributes[attr_name]
else
association_instance_array = []
association_instance.each_with_index do |instance, i|
association_instance_array << instance_variables_hash(instance).deep_merge(@new_attributes[attr_name][i])
end
return association_instance_array
end
end
end
end

def merge_association_instance_variables_with_attributes(association, attr_name, attributes)
association_instance = send(attr_name)
if association.class == HasOneAssociation
instance_hash = create_association_hash(association_instance,HashWithIndifferentAccess.new)
merged_result = instance_hash.deep_merge(attributes[attr_name])
elsif association.class == HasManyAssociation
association_instance_array = []
if association_instance.nil?
merged_result = attributes[attr_name]
else
for i in 0..association_instance.length-1
instance_hash = create_association_hash(association_instance[i],HashWithIndifferentAccess.new)
association_instance_array << instance_hash.deep_merge(attributes[attr_name][i])
def instance_variables_hash(association_instance)
hash = HashWithIndifferentAccess.new

unless association_instance.nil?
association_instance.instance_variables.each do |var|
if association_instance.instance_variable_get(var).instance_variables.length > 0
hash[var.to_s.delete("@")] = instance_variables_hash(association_instance.instance_variable_get(var))
else
hash[var.to_s.delete("@")] = association_instance.instance_variable_get(var)
end
end
merged_result = association_instance_array
end
hash
end
merged_result
end

def create_association_hash(association_instance,association_instance_hash)
unless association_instance.nil?
association_instance.instance_variables.each do |var|
if association_instance.instance_variable_get(var).instance_variables.length > 0
association_instance_hash[var.to_s.delete("@")] = create_association_hash(association_instance.instance_variable_get(var),HashWithIndifferentAccess.new)
else
association_instance_hash[var.to_s.delete("@")] = association_instance.instance_variable_get(var)
def assign_simple_attributes
sanitized_attributes.stringify_keys.each do |k, v|
set_attribute(k, v)
end
end

# attributes for which we have an attr_reader and/or an attr_writer
def sanitized_attributes
sanitized = {}
@new_attributes.each do |k, v|
if respond_to?("#{k}=") || respond_to?("#{k}")
sanitized[k] = v
end
end
sanitized
end
association_instance_hash
end

def set_attribute(attr_name, value)
if respond_to?("#{attr_name}=")
send("#{attr_name}=", value)
else
instance_variable_set("@#{attr_name}".to_sym, value)
end
end

end
end
Loading