I would like to
1. Grab an object 1 and object 2 from the database (both have 2 has_many associations each)
2. Create object 3 which is a clone of object 1 and therefore has no associations yet
2. Duplicate object 2's associations and add them to object 3's associations
3. Use the new object 3 for operations in memory
4. Exit the process with no permanent change to the database - object 1 and object 2 still have their original associations when the process exits
What instead is happening
I grab an object 1 and object 2 from the database (both have 2 has_many associations each)
I create object 3 which is a clone of object 1 and therefore has no associations yet
I duplicate object 2's associations and add them to object 3's associations
I use the new object 3 for operations in memory
I exit the process HOWEVER there is now a permanent change to the database - object 2 no longer has its associations because their keys have been changed to the (Temporary) object 3 id.
Here is my code. Note that all contains an array with an arbitrary # of objects in it
object1 = all.last.clone #we take the most recently created object
all.each do |instance|
instance_association1 = (instance.association1).dup
object1.association1 += instance_association1 #BUG this moves the association
object1.association1.uniq!
instance_association2 = (instance.association2).dup
object1.association2 += instance_association2
object1.association2.uniq!
end
Note that now when I say:
all.last.association1, I get an empty array.
Help!!
MongoMapper's associations may be a little overzealous with saving. I want to overhaul it sometime, but it's not an easy problem.
The code that's going to tell you when saves happen is many_documents_proxy.rb. When you do my_association = [...], replace is the method that's called.
The only method that doesn't do any saving is build, so you may be able to build up your temporary object like so:
tmp = all.last.clone
all.each do |instance|
instance.association1.each { |doc| tmp.association1.build(doc.attributes) }
tmp.association1.uniq!
# ...
end
More generally, you can convert all your associated documents to arrays and not worry about saving...
associations1 = []
associations2 = []
all.each do |doc|
associations1 += doc.associations1.to_a
associations2 += doc.associations2.to_a
end
assocations1.uniq!
assocations2.uniq!
HOWEVER, watch out! Ruby's uniq method looks like it uses Ruby's #hash method to compute equality, which may not give you the results you want in this case. Do a few tests to make sure that my_obj.hash == my_obj2.hash if my_obj == my_obj2. See this discussion for strategies on implementing your own #hash method, if that's the route you need to go.
Related
Say you have a Factory in FactoryBot, and say that factory has several traits for associations, and that each trait will need to update the model being created by the factory. So, your main model is MainModel, and you have associations to A, B, C, D, etc...
FactoryBot.define do
factory :main_model
trait :with_a do
after(:create) do |main_model|
# Both sides refer to each other, so this must be done in after(:create).
main_model.update(a_id: create(:a, main_model_id: main_model.id).id)
end
end
trait :with_b do
after(:create) do |main_model|
# Both sides refer to each other, so this must be done in after(:create).
main_model.update(b_id: create(:b, main_model_id: main_model.id).id)
end
end
# More traits omitted...
end
Side note: We're not using ActiveRecord, so we cannot use the associations features built into FactoryBot. Associations must be explicitly registered using a call to update on both sides of the association. Even if I could, I'm not sure it would have any effect?
Now, if client code does the following:
create(:main_model, :with_a, :with_b, :with_c, :with_d)
This is going to result in the following writes to the db:
Create the main record
Create the A record that points to the main.
Update the main record to point to the new A.
Create the B record that points to the main.
Update the main record to point to the new B.
Create the C record that points to the main.
Update the main record to point to the new C.
Create the D record that points to the main.
Update the main record to point to the new D.
Of the above, Numbers 3, 5, 7, and 9 could all be done at the same time, at the very end, if I could simply build up a list of updates that are needed and find a "hook" that would allow me to do one more update after the final trait was applied, and factory bot was ready to return the final result to me. I cannot figure out a way to do that, but the ability to do this would save thousands (probably tens of thousands) of DB writes across my spec suite.
Anyone have any ideas?
You could define a transient variable (I used associations, but call it whatever) - and use this to store all of the associated record IDs for a single record update in an after(:create) block.
Something like this:
FactoryBot.define do
factory :main_model do
transient do
associations { {} }
end
trait :with_a do
associations { super.merge(a_id: create(:a, main_model_id: id).id) }
end
trait :with_a do
associations { super.merge(b_id: create(:b, main_model_id: id).id) }
end
after(:create) do |main_model|
main_model.update(associations)
end
end
end
Usage of the factory remains unchanged - e.g.
create(:main_model, :with_a, :with_b)
...So you shouldn't need to update any of the spec code.
In a previous question I asked how I could show the contents of a Dictionary in a GUI. I started from this idea to build a GUI with a slightly better look and feel. It mainly consists of RectangleMorphs glued together in columns and rows (cfr. the accepted answer in my previous question).
The problem now is that I would like my table to be updated when elements are added/removed/edited in my dictionary. I managed to write some Morph that consists of columns of CellMorphs, which inherit from RectangleMorph and have model and message as instance variables with the following update message:
update
" update the contents of this cell "
| value |
self removeAllMorphs.
(model = nil or: message = nil)
ifTrue: [ value := '' ]
ifFalse: [ value := model perform: message ].
self addMorph: value asMorph.
As can be seen, the CellMorph is a container for a Morph containing the actual content of the cell. This works great for displaying the size of the dictionary for instance:
d := Dictionary new.
d at: 'foo' put: 100.
d at: 'bar' put: 200.
cell := CellMorph new
model: d;
message: #size;
color: Color white.
cell openInWorld.
d at: 'boo' put: 300. " cell will be updated "
but I don't seem to get something similar working for the contents of the dictionary, because I can't find a way to access single keys or values with a message. The only solution I can think of is to create new columns with new cells every time, but this is so expensive and I can't imagine that this is a good idea...
Therefore my question:
Is there a way to update my Morph displaying the dictionary without creating billions of my CellMorphs or should I forget about my idea and rather work with rows of CellMorphs for instance in order to group the entries in the dictionary?
for completeness: the model: message in CellMorph looks like:
model: newModel
"change the model behind this cell"
model ifNotNil: [ model removeDependent: self ].
newModel ifNotNil: [newModel addDependent: self].
model := newModel.
self update.
update: aParameter does nothing more than call update. and I also added self changed. in all messages of Dictionary that I want the interface to be notified of (at: put:, removeKey:, etc.).
In the instance variable named 'message' you could have a Message object, instead of having only the selector.
An instance of Message has the receiver, selector and arguments. So, you could configure it with the dictionary keys sorted asArray in the receiver, the selector #at: and an index, to get a specific key. Accessing the value would be getting the value at: that key in the dictionary.
I think that a Message is not executed with object perform: message, you should check. message perform should work because it already has the receiver.
In any case, this complexity may show that having only (one) model and (one) message is not enough to get the model in th granularity you want, and you can possibly specialize a bit more, using the knowledge that the model is a dictionary. For instance, having an instance variable for key or for keyIndex.
Some side notes about the code:
(model = nil or: message = nil)
has comparisons with nil, that can be replaced by #isNil message or, if you want to stick with equality, use the faster == to compare identity, since nil is unique.
#or: is used to get the benefits of partial evaluation (the argument is evaluated only if the receiver is false). But that only works if you have a block as argument, otherwise the expression is evaluated before, to get the argument for the message in the stack.
So I found this quiz on a website that I was excited to solve with my newly acquired Ruby skills (CodeAcademy, not quite finished yet).
What I want to do is make an array with 100 entries, all set to "open". Then, I planned to create a method containing a for loop that iterates through every nth entry of the array and changes it to either "open" or "closed", based on what it was before. In the for loop, n should be increased from 1 to 100.
What I have so far is this:
change_state = Proc.new { |element| element == "open" ? element = "closed" : element = "open" }
def janitor(array,n)
for i in 1..n
array.each { |element| if array.index(element) % i == 0 then element.change_state end }
end
end
lockers = [*1..100]
lockers = lockers.map{ |element| element = "closed" }
result = janitor(lockers,100)
When trying to execute I receive an error saying:
undefined method `change_state' for "closed":String (NoMethodError)
Anybody an idea what is wrong here? I kinda think I'm calling the "change_state" proc incorrectly on the current array element.
If you know the quiz, no spoilers please!
As you have implemented change_state, it is not a method of any class, and definitely not one attached to any of the individual elements of the array, despite you using the same variable name element. So you cannot call it as element.change_state.
Instead, it is a variable pointing to a Proc object.
To call the code in a Proc object, you would use the call method, and syntax like proc_obj.call( params ) - in your case change_state.call( element )
If you just drop in that change, your error message will change to:
NameError: undefined local variable or method `change_state' for main:Object
That's because the change_state variable is not in scope inside the method, in order to be called. There are lots of ways to make it available. One option would be to pass it in as a parameter, so your definition for janitor becomes
def janitor(array,n,state_proc)
(use the variable name state_proc inside your routine instead of change_state - I am suggesting you change the name to avoid confusing yourself)
You could then call it like this:
result = janitor(lockers,100,change_state)
Although your example does not really need this structure, this is one way in which Ruby code can provide a generic "outer" function - working through the elements of an array, say - and have the user of that code provide a small internal custom part of it. A more common way to achieve the same result as your example is to use a Ruby block and the yield method, but Procs also have their uses, because you can treat them like data as well as code - so you can pass them around, put them into hashes or arrays to decide which one to call etc.
There may be other issues to address in your code, but this is the cause of the error message in the question.
I have a BankAccount class. I was trying to create multiple instances of this class and put them into an array. For example
accounts = [Ba1 = BankAccount.new(100), Ba2 = BankAccount.new(100)]
I want to initialize the array with a large number of instances inside, let's say 20, so from Ba1 to Ba20. Is there an easier way to do it instead of just manually inputting it? I have tried a loop but I just can't figure out how to make it work.
This should do the trick:
accounts = 100.times.collect { BankAccount.new(100) }
If you need to do something different for each account based on which one it is then:
accounts = 100.times.collect { |i| BankAccount.new(i) }
i represents each number in the collection being iterated over.
If you actually need to set the variable names using the data you can call eval().
accounts = 100.times.collect { |i| eval("B#{i} = BankAccount.new(100)") }
And now B1 through B100 should be set to the appropriate BankAccount instances.
Disclaimer:
I should say that this approach will be generally frowned upon. In this case you already have an array called accounts. All you need to do is index on it to get the corresponding bank account. accounts[50] for example instead of Ba50. In my years of ruby development I've found few places to use eval that made sense.
I'd like to know if I can add something similar to this in my class and have it build a class attribute that I can reference in other classes. I don't want to have to remember id's and I don't want to keep having to update id's as the id's in the weight tables change. Nor do I want to lock the weight table into a set of specific id's.
So I'd love to do something like the following:
class Weight < ActiveRecord::Base
attr_accessible :name
##kind = {
Weight.all.each do |weight|
weight.name.delete(' ').underscore.to_sym: weight.id,
end
}
cattr_reader :kind, :instance_accessor => false
end
Then in other areas of the code I can do things like..
scope :light, where(weight_id, Weight::kind(:light))
I'm sure there's some magic ruby way, I'm just not sure of the right syntax.
This is the closest I've come so far.
def self.kinds
kinds = Weight.all.map { |weight| { weight.name.delete(' ').underscore.to_sym => weight.id } }
kinds.reduce({}, :update)
end
and then...
scope :light, where(weight_id, Weight.kinds[:light])
Why not turn kind accessor into a class method which would lazily load the weights from database and lookup the neccessary one?
However, the stuff you are trying to do doesn't seem really good. What is the table behind Weight changes? What your classes will be loaded prior to the database connection gets set up (in some test environment, for instance)? I would suggest rewriting the scope to inner joing weight model with the appropriate name...