Skip to content
This repository has been archived by the owner on Apr 17, 2018. It is now read-only.

Error when Model.new with attributes #152

Closed
yayugu opened this issue Oct 12, 2011 · 9 comments
Closed

Error when Model.new with attributes #152

yayugu opened this issue Oct 12, 2011 · 9 comments

Comments

@yayugu
Copy link

yayugu commented Oct 12, 2011

Error when Model.new with attributes
because of commit: afbd387

def attributes=(attributes)
  model = self.model
  attributes.each do |name, value|
    case name
      when String, Symbol
        if model.allowed_writer_methods.include(setter = "#{name}=")  # <- HERE!
          __send__(setter, value)
        else
          raise ArgumentError, "The attribute '#{name}' is not accessible in #{model}"
        end
      when Associations::Relationship, Property
        self.persistence_state = persistence_state.set(name, value)
    end
  end
end

model.allowed_writer_methods is nil

stack trace:
NoMethodError:
undefined method include?' for nil:NilClass # /Users/yayugu/.rvm/gems/ruby-1.9.2-p290/bundler/gems/dm-core-6031e6ef3ee8/lib/dm-core/resource.rb:332:inblock in attributes='
# /Users/yayugu/.rvm/gems/ruby-1.9.2-p290/bundler/gems/dm-core-6031e6ef3ee8/lib/dm-core/resource.rb:329:in each' # /Users/yayugu/.rvm/gems/ruby-1.9.2-p290/bundler/gems/dm-core-6031e6ef3ee8/lib/dm-core/resource.rb:329:inattributes='
# /Users/yayugu/.rvm/gems/ruby-1.9.2-p290/bundler/gems/dm-core-6031e6ef3ee8/lib/dm-core/resource.rb:748:in initialize' # ./spec/user_spec.rb:7:innew'
# ./spec/user_spec.rb:7:in `block (3 levels) in <top (required)>'

code:
user.rb
# encoding: utf-8
class User
include DataMapper::Resource

    property :id, Serial
    property :name, String, :length => (3..40), :unique => true
    property :email, String, :length => (5..40), :unique => true, :format => :email_address
    property :hashed_password, String
    property :salt, String
    property :created_at, DateTime
    property :updated_at, DateTime
    property :permission_level, Integer, :default => 1

    has n, :entries

    attr_accessor :password, :password_confirmation

    validates_uniqueness_of :name
    validates_uniqueness_of :email
    validates_length_of :password, :minimum => 4, :if => :password_require?
    validates_presence_of :password_confirmation, :if => :password_require?
    validates_confirmation_of :password

    before :valid? do
      self.name = name.strip
    end

    def password=(pass)
      @password = pass
      self.salt = User.random_string(10) if !self.salt
      self.hashed_password = User.encrypt(@password, self.salt) if !@password.blank?
    end

    def self.authenticate(name, pass)
      current_user = first(:name => name)
      return nil if current_user.nil?
      return current_user if User.encrypt(pass, current_user.salt) == current_user.hashed_password
      nil
    end

    def admin?
      permission_level == 1
    end

    def password_require?
      self.new? || (!self.new? && !self.password.blank?)
    end

    protected

    def self.encrypt(pass, salt)
      Digest::SHA1.hexdigest(pass+salt)
    end

    def self.random_string(len)
      Array.new(len) { ['a'..'z','A'..'Z','0'..'9'].map(&:to_a).flatten[rand(61)] }.join
    end
  end

  class Hash
    def stringify
      inject({}) do |options, (key, value)|
        options[key.to_s] = value.to_s
        options
      end
    end

    def stringify!
      each do |key, value|
        delete(key)
        store(key.to_s, value.to_s)
      end
    end
  end

  class GuestUser
    def admin?
      false
    end

    def permission_level
      0
    end
  end

user_spec.rb

        # encoding: utf-8
        describe "User" do
          context "register" do
            before do
              @user = User.new(
                :name => 'Johnny',
                :email => 'johnny@example.com',
                :password => 'password',
                :password_confirmation => 'password'
              )
            end
            after :each do
              @user.destroy
            end
            it "should be able to register a user" do
              @user.save.should be_true
              @user.name.should eq('Johnny')
            end
            it "should strip spaces" do
              @user.name = ' Johnny '
              @user.save.should be_true
              @user.name.should eq('Johnny')
            end
          end
        end

when rollback to
if model.public_method_defined?(setter = "#{name}=")
it works fine.

@namelessjon
Copy link
Member

This is because you are failing to call DataMapper.finalize after requiring all your models. This is an important part of setting up the DataMapper models in memory, including initialization of relationships and some basic sanity checking.

If the problem persists after calling DataMapper.finalize, you should reopen.

@yayugu
Copy link
Author

yayugu commented Oct 12, 2011

it works! thanks!!

@disbelief
Copy link

Could you please advise on where exactly within the Rails app DataMapper.finalize should be called? Can it be called multiple times (ie. at the bottom of every model file), once from inside an initializer, application.rb, etc.?

@namelessjon
Copy link
Member

Hi disbelief

DataMapper.finalize is safe to call multiple times at the bottom of each file*, BUT it should really only be called once, after all models have loaded. So in an initializer (the first one?) is ideal, assuming I correctly understand the rails load sequence. I'm not sure where application.rb gets loaded, so I can't advise on that one.

* if the model has a relationship to another model that hasn't been required yet, it will blow up**
** Probably, anyway. Unless rails does some funky autoloading. And even then, I'm not 100% of the implications of running finalize in the middle of an existing finalize call. I think it should be safe, though.

@mbj
Copy link
Member

mbj commented Nov 24, 2011

@disbelief

As far as I know :

  • the dm-rails railtie calls DataMapper.finalize after rails loaded the models.
  • you can call DataMapper.finalize as often as you want without any side effect.

@disbelief
Copy link

@namelessjon I've got something like what you've described working now. I created a DataMapper initializer that requires all of my Model files first, then calls DataMapper.finalize. I heed your warning about things blowing up if the Models require files that haven't been included yet.

@mbj thanks for the info. I'm actually not using the dm-rails gem at the moment. I'm going to check it out now.

@pdf
Copy link

pdf commented Mar 28, 2012

This will also happen when cache_classes = false in Rails. Which obviously makes testing and development rather tiresome.

@jaischeema
Copy link

@pdf Did you find a way around that in development environment?

@pdf
Copy link

pdf commented Aug 15, 2012

@jaischeema Only by explicitly calling .finalize in some codepaths, not very satisfactory.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants