2

I'm trying to call a method in a class during its initialize method. Is this not allowed? I originally had the method outside of the class to try and use it as a global method. The current method is trying to return the created matrix and then the initialize method saves the returned matrix into an instance variable.

class Member
  def setMatrix(a, i, l)
    puts "here"
    m = Matrix.zero(6)
    m[0,0] = a*l**2/i
    m[0,3] = -a*l**2/i
    m[1,1] = 12
    m[1,2] = 6*l
    m[1,4] = -12
    m[1,5] = 6*l
    m[2,1] = 6*l
    m[2,2] = 4*l**2
    m[2,4] = -6*l
    m[2,5] = 2*l**2
    m[3,0] = -a*l**2/i
    m[3,3] = a*l**2/i
    m[4,1] = -12
    m[4,2] = -6*l
    m[4,4] = 12
    m[4,5] = -6*l
    m[5,1] = 6*l
    m[5,2] = 2*l**2
    m[5,4] = -6*l
    m[5,5] = 4*l**2
    return m
    #@k = m
   end

   def initialize(a, i, l)
     @area = a
     @i = i
     @length = l
     @k = setMatrix(a, i, l)
    end
end

Doing this returns this error

`'setMatrix': private method '[]=' called for #<Matrix:0x00000001186e00> (NoMethodError)
from truss_solver.rb:71:in 'initialize'
from truss_solver.rb:86:in 'new'
from truss_solver.rb:86:in 'block in <main>'
from truss_solver.rb:85:in 'each'
from truss_solver.rb:85:in '<main>'`

I would like it to make an instance variable of a matrix when the class is instantiated. I've also tried to have the setMatrix method save the matrix to @k directly instead of returning the matrix and that gave a similar error. How else can I do to achieve what I want?

minnymauer
  • 374
  • 1
  • 4
  • 17
  • "You said "doing this" returns this error..." Presumably, "this" includes the creation of an instance of `Member` (e.g., `Member.new(1,2,3)`), which is not shown. – Cary Swoveland Apr 21 '16 at 02:42

2 Answers2

6

There's nothing to prevent you from calling methods within initialize, there's no special behaviour in there, but what you're calling here is a private method in another class.

I don't know why it's set private, and some people have observed that seems to be a problem, so you can always just brute-force it:

matrix.send(:[]=, 1, 2, 3)

That seems messy, and patching it as recommended in that post might help simplify things:

class Matrix
  def []=(row, column, value)
    @rows[row][column] = value
  end
end

You could also subclass Matrix to MutableMatrix and include that method.

As a note, Ruby's naming conventions for methods is underscore_style and including an explicit return at the end isn't necessary, it's implied. m alone would do the job.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • 1
    In addition, you should `raise ArgumentError if row >= row_count || column >= column_count` to ensure that `row` and `column` are within the matrix. – Stefan Apr 21 '16 at 07:28
1

Let's get more information:

require 'matrix'
Matrix.instance_methods.include?(:[]=)
  #=> false
Matrix.private_instance_methods.include?(:[]=)
  #=> true

The latter was a surprise to me. Considering that Matrix objects are immutable, there should be no :[]= method. (It's akin to 2=4, which of course raises an exception.) Perhaps it's used by Ruby for implementing Matrix methods. Can a reader explain why this undocumented private method exists?

If you wish to use that private instance method you can do as @tadman suggests.

My advice is to use Matrix#build. (I've renamed setMatrix to set_matrix to conform with Ruby's convention for naming methods and variables.)

class Member
  def set_matrix(a, i, l)
    Matrix.build(6) do |r,c|
      case [r,c].sort
      when [0,0], [3,3] then a*l**2/i
      when [0,3]        then -a*l**2/i
      when [1,1], [4,4] then 12
      when [1,4]        then -12
      when [1,2], [1,5] then 6*l
      when [2,4], [4,5] then -6*l
      when [2,2], [5,5] then 4*l**2
      when [2,5]        then 2*l**2
      else              0
      end
    end
  end

  def initialize(a, i, l)
    @area = a
    @i = i
    @length = l
    @k = set_matrix(a, i, l)
  end
end

m = Member.new(1,2,3).instance_variable_get(:@k)
  #=> Matrix[[ 4,   0,   0, -5,   0,   0], 
  #          [ 0,  12,  18,  0, -12,  18],
  #          [ 0,  18,  36,  0, -18,  18],
  #          [-5,   0,   0,  4,   0,   0],
  #          [ 0, -12, -18,  0,  12, -18],
  #          [ 0,  18,  18,  0, -18,  36]]
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Surprising indeed. Neither `[]=`, not its aliases `set_element` and `set_component` are used within the class. – Stefan Apr 21 '16 at 07:32
  • The matrix is symmetric, so you could remove some redundant r,c/c,r-pairs from your `when`s by using `case [r,c].sort`. I'd also use the more compact `when ... then` form in this case. – Stefan Apr 21 '16 at 08:01
  • @Stefan, I hadn't noticed it was symmetric. I incorporated your (clever) suggestion. Thanks. Can you explain "Unbound" in `Matrix.instance_method(:[]=) #=> #`, considering that it is not necessary to bind the method to the matrix object? – Cary Swoveland Apr 21 '16 at 16:13
  • 1
    It's "Unbound" just because you don't have an instance. `String.instance_method(:upcase)` returns an `UnboundMethod`, whereas `'foo'.method(:upcase)` returns a (bound) `Method`. – Stefan Apr 22 '16 at 07:33