bdurand / json_record

Add ability to serialize arbitrarily complex schemas into an ActiveRecord field. You get the benefits of a schemaless database but with all the features of ActiveRecord.
http://rdoc.info/projects/bdurand/json_record
MIT License
24 stars 4 forks source link

Not compatible with Rails 4.2 beta2 #7

Open MichaelSp opened 10 years ago

MichaelSp commented 10 years ago

Here is a proof of the bug: https://github.com/MichaelSp/json_record_bug

You can switch in the Gemfile between the working and the non working version and than run: rm Gemfile.lock && bundle install && rake test

This is the error

  1) Error:
PostTest#test_create_posts:
ActiveRecord::StatementInvalid: SQLite3::SQLException: table posts has no column named email: INSERT INTO "posts" ("author", "body", "created_at", "email", "first_name", "last_name", "title", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    sqlite3 (1.3.9) lib/sqlite3/database.rb:91:in `initialize'
    sqlite3 (1.3.9) lib/sqlite3/database.rb:91:in `new'
    sqlite3 (1.3.9) lib/sqlite3/database.rb:91:in `prepare'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/sqlite3_adapter.rb:312:in `block in exec_query'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract_adapter.rb:464:in `block in log'
    activesupport (4.2.0.beta2) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract_adapter.rb:458:in `log'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/sqlite3_adapter.rb:299:in `exec_query'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract/database_statements.rb:76:in `exec_insert'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract/database_statements.rb:108:in `insert'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `insert'
    activerecord (4.2.0.beta2) lib/active_record/relation.rb:66:in `insert'
    activerecord (4.2.0.beta2) lib/active_record/persistence.rb:521:in `_create_record'
    activerecord (4.2.0.beta2) lib/active_record/counter_cache.rb:139:in `_create_record'
    activerecord (4.2.0.beta2) lib/active_record/attribute_methods/dirty.rb:122:in `_create_record'
    activerecord (4.2.0.beta2) lib/active_record/callbacks.rb:306:in `block in _create_record'
    activesupport (4.2.0.beta2) lib/active_support/callbacks.rb:83:in `run_callbacks'
    activerecord (4.2.0.beta2) lib/active_record/callbacks.rb:306:in `_create_record'
    activerecord (4.2.0.beta2) lib/active_record/timestamp.rb:57:in `_create_record'
    activerecord (4.2.0.beta2) lib/active_record/persistence.rb:501:in `create_or_update'
    activerecord (4.2.0.beta2) lib/active_record/callbacks.rb:302:in `block in create_or_update'
    activesupport (4.2.0.beta2) lib/active_support/callbacks.rb:114:in `call'
    activesupport (4.2.0.beta2) lib/active_support/callbacks.rb:114:in `call'
    activesupport (4.2.0.beta2) lib/active_support/callbacks.rb:166:in `block in halting'
    activesupport (4.2.0.beta2) lib/active_support/callbacks.rb:87:in `call'
    activesupport (4.2.0.beta2) lib/active_support/callbacks.rb:87:in `run_callbacks'
    activerecord (4.2.0.beta2) lib/active_record/callbacks.rb:302:in `create_or_update'
    activerecord (4.2.0.beta2) lib/active_record/persistence.rb:120:in `save'
    activerecord (4.2.0.beta2) lib/active_record/validations.rb:36:in `save'
    activerecord (4.2.0.beta2) lib/active_record/attribute_methods/dirty.rb:21:in `save'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:284:in `block (2 levels) in save'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:345:in `block in with_transaction_returning_status'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `block in transaction'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract/transaction.rb:188:in `within_new_transaction'
    activerecord (4.2.0.beta2) lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `transaction'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:218:in `transaction'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:342:in `with_transaction_returning_status'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:284:in `block in save'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:299:in `rollback_active_record_state!'
    activerecord (4.2.0.beta2) lib/active_record/transactions.rb:283:in `save'
    activerecord (4.2.0.beta2) lib/active_record/persistence.rb:34:in `create'
    test/models/post_test.rb:5:in `block in <class:PostTest>'
    minitest (5.4.2) lib/minitest/test.rb:108:in `block (3 levels) in run'
    minitest (5.4.2) lib/minitest/test.rb:206:in `capture_exceptions'
    minitest (5.4.2) lib/minitest/test.rb:105:in `block (2 levels) in run'
    minitest (5.4.2) lib/minitest/test.rb:258:in `time_it'
    minitest (5.4.2) lib/minitest/test.rb:104:in `block in run'
    minitest (5.4.2) lib/minitest.rb:319:in `on_signal'
    minitest (5.4.2) lib/minitest/test.rb:278:in `with_info_handler'
    minitest (5.4.2) lib/minitest/test.rb:103:in `run'
    minitest (5.4.2) lib/minitest.rb:761:in `run_one_method'
    minitest (5.4.2) lib/minitest.rb:293:in `run_one_method'
    minitest (5.4.2) lib/minitest.rb:287:in `block (2 levels) in run'
    minitest (5.4.2) lib/minitest.rb:286:in `each'
    minitest (5.4.2) lib/minitest.rb:286:in `block in run'
    minitest (5.4.2) lib/minitest.rb:319:in `on_signal'
    minitest (5.4.2) lib/minitest.rb:306:in `with_info_handler'
    minitest (5.4.2) lib/minitest.rb:285:in `run'
    minitest (5.4.2) lib/minitest.rb:149:in `block in __run'
    minitest (5.4.2) lib/minitest.rb:149:in `map'
    minitest (5.4.2) lib/minitest.rb:149:in `__run'
    minitest (5.4.2) lib/minitest.rb:126:in `run'
    minitest (5.4.2) lib/minitest.rb:55:in `block in autorun'

8 runs, 13 assertions, 0 failures, 1 errors, 0 skips
MichaelSp commented 10 years ago

I came up with an work around for that, called JsonStore: put this into your app/models/concerns directory

module JsonStore

  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def in_json(store: :json_data, attributes:)
      attributes.each do |attr_name|
        define_method "#{attr_name}=".to_sym do |value|
          send(store)[attr_name] = value
          value
        end

        define_method attr_name do
          send(store)[attr_name]
        end

        define_method store do
          ivar = instance_variable_get "@#{store}".to_sym
          ivar ||= (JSON.parse(self[store]) rescue {}).with_indifferent_access
          instance_variable_set "@#{store}".to_sym, ivar
          ivar
        end

        class_eval <<-EOF
          before_save do
            self.#{store} = @#{store}.to_json
          end
        EOF
      end
    end
  end
end

And use it like this:

class Company < ActiveRecord::Base
  include JsonStore

  in_json store: :contact_person, attributes: [:contact_name, :email, :phone]

  validates :phone, format: { with: /\A[+]*[0-9\.\\ -]+\z/, message: 'Wrong phone number'}
  validates :email, uniqueness: false, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: 'Illegal email address'}
  validates_presence_of :contact_name, :email, :phone
  validates_uniqueness_of :name
end