e168f08: Protected: Assignment 2B: Optional ActiveRecord practice
Student1
· 1 year ago
Could you explain what ordered_cards is doing ? I typed it in irb, and it complained. Mainly, I am confused with self[i]. It seems to be that we are calling the each method on a Range which is 0..4, so self would be pointing to the Range ?
In any case, I do this in irb: >> cards = Array.new => [] >> (0..4).each { |i| cards[i] = self[i] } NoMethodError: undefined method `[]' for main:Object from (irb):24 from (irb):24:in `each' from (irb):24 from :0
Could you explain ? Thanks
jgn
· 1 year ago
That bit of code is inside of a method (note this -- think about what I am saying), which is inside of a class (ok?).
When you type code into irb, it is not in that context.
When the code runs inside of an instance method inside of a class, "self" is a reference to that very instance. Since the Hand class defines a method [], when you write self[i] inside of an instance method, it will call [] on that very instance.
(0..4) is just an easy way to iterate through 0, 1, 2, 3, 4, which are used as the indices so that we can retrieve card 0 of the hand, card 1 of the hand, etc.
Please review the lectures, or read p. 382 in the 3rd edition of the Pickaxe.
Student1
· 1 year ago
Ok, thanks. I see how it works now.
Morris
· 1 year ago
An interesting twist here is the assumption made about sorting hands here, and the under-specification of Card#<=> in assignment 2. Assignment 2 just said to implement <=> to compare "according to the rules of the spaceship operator", and nothing in assignment 2 actually exercises this operator.
I chose to implement Card comparison based only on rank, so two of spades is considered spaceship-equal to two of diamonds, for example. But in order for the new ordered_cards method to return a predictable (reproducible) string, the same string for any ordering of the same set of cards, <=> needs to be specified to impose some ordering on cards of the same rank but different suits.
jgn
· 1 year ago
Good point.
I think in the typical case most students would have implement Card#<=> by immediately delegating to Fixnum#<=> to whatever internally was providing card_number back. You are right that I underspecified it, and under-tested it -- I didn't anticipate that someone would implement Card#<=> as though it can only be used for Poker.
For 2B: In your case, adapt the code so that the sort comes out so that ordered_cards is reproducible.
Morris
· 1 year ago
That's what I had done, before posting, and subsequently I did it in two other ways:
First, I modified my <=> operator to provide a total ordering, but ordering by card_number just feels wrong, for any card game, so I made my Card#<=> return (card_number <=> card_number) only if the ranks were equal.
Second approach was for Hand#ordered_cards to pass a block to sort, imposing its own ordering on the cards.
My third approach is the one I like the best, which doesn't rely on the existence of Card#<=> at all, which was to map each card to its abbrev before sorting the array: the body of Hand#ordered_cards is then
(where CARDS_PER_HAND was already defined as 5 in constants.rb, because I get severe twitches when I see raw numerical constants anywhere in any code).
Modifying your version of ordered_cards, this would be
I don't think you should twitch too much when a constant is defined -- 'cos the refactoring to create objects based on such constants is probably going to be pretty easy. The twitching should occur when you see "5" used in a computation.
MaureenB
· 1 year ago
When is this one due? I've been looking for the date for awhile :)
jgn
· 1 year ago
Maureen: Assignment 2B is entirely optional. There is no due date, and we won't be grading it.
Having said that, the time to do it is RIGHT NOW, because it works quite well with the lecture I just gave.
philadelphia
· 1 year ago
Hi,
I'm confused on 2B part 2... I've read AWDR; the class notes; and the rails guide online. I understand pretty well what the models do and how they are used. But I'm not sure what functionality you are interested in implementing...
Mike
Morris
· 1 year ago
The assumption (probably not valid in this case) is that figuring out the strength of a hand is computation-intensive, and that it would be efficient to cache the result of that computation, once you've figured out the strength once, and then more efficient to look it up in the database than to re-compute it. Try to imagine hand-strength computation as some computation that takes a lot of compute time.
The functionality we're implementing here is storing hand strengths in a database for lookup. It's not really a good real-world example, just an exercise in Creating and Reading database rows using ActiveRecord.
jgn
· 1 year ago
Some students have reported that their solutions to assignment 2 take many seconds to pass the tests -- it it may in fact be more relevant in the real world than it appears at first blush.
jgn
· 1 year ago
Mike,
It is not uncommon that instead of computing the result of a method, you look up a pre-computed value somewhere. This is called "memoization" or "caching."
In this exercise, you check the incoming hand that is being evaluated for strength. If the strength for the hand has already been computed and is found in a database table, you return that pre-computed value. If the incoming hand isn't found in the database, then you compute the strength (as you already already doing), and store the result in the database (so that the next time you can use the database value rather than compute it).
That's it. Nothing more, nothing less.
The upshot for you is:
(1) You get experience with ActiveRecord; (2) You get some experience with ActiveRecord outside of Rails (believe me, this is important), (3) You get to do so with a problem domain you already understand; (4) You exercise a technique that you will almost surely need to implement at some point;
OK?
John
philadelphia
· 1 year ago
Yup... I got all that. I had already done the exercise, and it works fine... I was referring to the second part of the assignment, captured below.
"7. Second job: Review the associations section of AWDR. If you could introduce one or two more models, what would they be? And how would they be associated with the History model?"
I just do not know what you had in mind here.
Mike
jgn
· 1 year ago
Oh, good question.
Think broadly. For example, at present the idea of a dealt hand is represented by a String. But arguably you could represent each *dealt* card in a separate table ("dealt_cards") with foreign keys to a row in a "cards" table and then to a specific Hand into which it was dealt.
In the model I proposed for 2B to get you started, there is only the fact of the hand having had its strength computed before (or not). But there are good arguments for representing quite a lot more of what is happening.
If you had a table called dealt_cards or some such, you could then run queries over that table to determine which cards have historically generated the strongest hands and so forth.
-----
Another way to look at this is as follows:
Most times when you save data, you are leaving things out (you are choosing to save certain aspects of the state of your app instead of others). So think about the strength computation in its context -- what data isn't getting saved? How could you save it? Does it have any value? Etc.
That question #7 is really a challenge to you to take a fresh look at the assignment and think about what else might be persisted.
philadelphia
· 1 year ago
OK... got it.
Thanks, Mike
philadelphia
· 1 year ago
Hi,
I'm trying to implement the "Second Job" on Assignment 2b. I've created a new migration (creating a table) and a helper associated with the new table. In the History helper, I've added a has_many pointing to the new table, and in the new helper, I've added a belongs_to pointing to the histories table.
What I want to do is use the find(some id #) to look for a specific id and then return the corresponding record from the histories table.
I've read through the docs, and I thought I could call <new>.find(1).history.strength_human_readable, where the result of the find would return an object with a reference to the history table, which I could then use to call the strength_human_readable method. No joy here. I _can_ call the find method on the histories and the <new> independently, but I can't seem to get to the PK table... Do I need to use :through???
Any suggestions are appreciated.
Mike
philadelphia
· 1 year ago
Never mind... brain freeze... Should have been looking at the column name in the resulting row.... Dumb mistake. All working now.
Mike
jgn
· 1 year ago
Mike,
Great.
Could you explain at a high level what your extra tables are doing? I think other students would be very interested. You don't have to get into implementation details: Just what you're "modeling" with the tables.
John
philadelphia
· 1 year ago
I don't have it fully functioning. I'm trying to create a new table which will contain all of the cards used. Then, the plan is to be able to create a relationship between the cards used and the hands formulated from them. So, for example, you should be able to see the hands in which the 3 of Clubs was a member. The tables are tied together using a PK-FK relationship which maps to a belongs_to/has_many relationship in Ruby.
The thing is that I'm still having a bit of an issue getting it to work quite right... I'm trying to use the "line_items/product" example in the rails book. In that example, there is a third table which maps the keys from the product and orders tables to the line_items table. There, they use the find method against the line_items table to return a reference to a line_items object. And this object, in turn, has a reference to the products (and orders) table. This is used to get access to column information in the products table.
In using mine, I'm not trying to use the third table. Instead, I'm using the belongs_to in the model for the new table I created to contain the cards, and I'm using has_many in the model I'm using for the history. Each pointing to each other's auto generated id.... BUT it's currently returning nil from my query on the new cards table. I had hoped it would return a reference which I could use to get at the history table and its member columns. So, I guess I'm a bit stuck right now... Still scratching my head a bit.
The above link points out my issue. I confirmed that the DB has "ID" as the key names. So, when using the belongs_to/has_many, I needed to specify that "id" is the foreign key. Once I did that using the following syntax, "belongs_to :history, :foreign_key => :id", it works just fine.
I am now able to get at the data in the associated table by calling a find in a table with a FK/PK relationship. So, I can call <new table>.find(1) and get at the contents of the table <new table> links to.
OK... enough for tonight. WIl complete tomorrow.
Take care, Mike
jgn
· 1 year ago
One thing I will be talking about Wednesday night is the need to always follow Rails conventions for foreign key column names. If you look at the migrations1 project (from the downloads page), look at User and Stock -- notice that I didn't have to specify any :foreign_key
philadelphia
· 1 year ago
I was shocked as well... I thought that the convention used in the migration would have created a history_id key, for example. Sadly, this was not the case. Instead, I wound up with a plain old id column. That was what caused me about 4 hours of confusion... How can we get the migration to work as expected?
Here is my migration to create the histories table:
class CreateHistories < ActiveRecord::Migration
def self.up create_table :histories, :force => true do |t| t.string :card_sequence t.integer :strength end end
def self.down drop_table :histories end
end
Why would it not create a history_id column, and instead create a generic "id" col?
Mike
jgn
· 1 year ago
Mike,
The "id" column on histories is that table's PRIMARY KEY. If, for instance, you created a table cards which belonged to a history, then cards would have a foreign key called history_id. For each row in cards, Each value in cards.history_id would be a value from the id column of histories. In other words, each row in cards "refers" to a row in histories.
If you have the newest PDF for AWDR, take a look at the top of p. 339. For both line_items and orders, the primary key is called "id." Since a row in line_items "belongs to" an order, line_items gets the foreign key with the name "order_id."
What is your "other" table? Does a History "have many" of that other table, or does it "belong to" that other table? That will help you see how the migration should be set up.
John
philadelphia
· 1 year ago
I'll grab the latest AWDR tomorrow morning...
My "other" table is called CardHistory (card_histories in sql land). It has a a "belongs_to :history" declared within it.
Within History, I had declared: "has_many :card_histories".
I had hoped the two statements would create the necessary FKs. I see that they do not. Am I supposed to create them in the migration step? i.e. within the 1_create_histories.rb? (I did not think so based upon the docs).
Mike
jgn
· 1 year ago
-- Create foreign keys in the migration step. -- Create associations (i.e., declare methods that the Ruby code will use to follow the keys) in the Models.
Again, take a look at migrations1. One thing you will see there is that in a migration, you can use a pseudo-data-type called "references" which will create the properly named key for you. It's confusing because you use the model name. In your case, you would put in your migration for card_histories:
t.references :history
poof. Magic. This is just a synonym for: t.integer :history_id
philadelphia
· 1 year ago
Thanks... I like the poof. Magic part. Will check it out in the AM.
Take care, Mike
philadelphia
· 1 year ago
ok... got it now... adding the t.references to the migration made it work...
I just needed to create the table as normal, using the migration and then add in t.references. That automatically generated the reference between the tables. And when I call find on the table linked to the history table, I can access data in the history table through the reference which is returned.
Like pretty much everything in life, it's easy once you know how to do it.
Mike
jgn
· 1 year ago
Well, I have some tips.
One is that you want to write a list of sentences that describe the relationships between the models. E.g., you want to write sentences like:
A Hand has many Cards. A Card belongs to a Hand.
You should also try and make some statements about uniqueness. These statements about uniqueness will frequent reveal other things you need to model. For example:
For all Hands, each Card must be unique.
This is a very tricky sentence. The reason is that we rarely think of cards needing to be unique across HANDS; rather, we think of uniqueness in terms of a Deck. This starts to push you to modeling:
A Card belongs to a Deck. A Card also belongs to a Hand. Each card is unique in the Deck.
Finally, you should think about whether an entity CAN belong to another entity -- but maybe it doesn't have to. E.g.,
A Card MUST belong to a Deck. A Card MAY belong to a Hand. Each card is unique in the Deck.
Etc.
All of these things will help you clarify what you're doing.
I typed it in irb, and it complained. Mainly, I am confused with self[i].
It seems to be that we are calling the each method on a Range which is 0..4, so self would be pointing to the Range ?
In any case, I do this in irb:
>> cards = Array.new
=> []
>> (0..4).each { |i| cards[i] = self[i] }
NoMethodError: undefined method `[]' for main:Object
from (irb):24
from (irb):24:in `each'
from (irb):24
from :0
Could you explain ? Thanks
When you type code into irb, it is not in that context.
When the code runs inside of an instance method inside of a class, "self" is a reference to that very instance. Since the Hand class defines a method [], when you write self[i] inside of an instance method, it will call [] on that very instance.
(0..4) is just an easy way to iterate through 0, 1, 2, 3, 4, which are used as the indices so that we can retrieve card 0 of the hand, card 1 of the hand, etc.
Please review the lectures, or read p. 382 in the 3rd edition of the Pickaxe.
I chose to implement Card comparison based only on rank, so two of spades is considered spaceship-equal to two of diamonds, for example. But in order for the new ordered_cards method to return a predictable (reproducible) string, the same string for any ordering of the same set of cards, <=> needs to be specified to impose some ordering on cards of the same rank but different suits.
I think in the typical case most students would have implement Card#<=> by immediately delegating to Fixnum#<=> to whatever internally was providing card_number back. You are right that I underspecified it, and under-tested it -- I didn't anticipate that someone would implement Card#<=> as though it can only be used for Poker.
For 2B: In your case, adapt the code so that the sort comes out so that ordered_cards is reproducible.
First, I modified my <=> operator to provide a total ordering, but ordering by card_number just feels wrong, for any card game, so I made my Card#<=> return (card_number <=> card_number) only if the ranks were equal.
Second approach was for Hand#ordered_cards to pass a block to sort, imposing its own ordering on the cards.
My third approach is the one I like the best, which doesn't rely on the existence of Card#<=> at all, which was to map each card to its abbrev before sorting the array: the body of Hand#ordered_cards is then
Array.new(CARDS_PER_HAND) {|i| self[i].abbrev}.sort.join('')
(where CARDS_PER_HAND was already defined as 5 in constants.rb, because I get severe twitches when I see raw numerical constants anywhere in any code).
Modifying your version of ordered_cards, this would be
def ordered_cards
cards = Array.new
(0..4).each { |i| cards[i] = self[i].abbrev }
end
I don't think you should twitch too much when a constant is defined -- 'cos the refactoring to create objects based on such constants is probably going to be pretty easy. The twitching should occur when you see "5" used in a computation.
Having said that, the time to do it is RIGHT NOW, because it works quite well with the lecture I just gave.
I'm confused on 2B part 2... I've read AWDR; the class notes; and the rails guide online. I understand pretty well what the models do and how they are used. But I'm not sure what functionality you are interested in implementing...
Mike
The functionality we're implementing here is storing hand strengths in a database for lookup. It's not really a good real-world example, just an exercise in Creating and Reading database rows using ActiveRecord.
It is not uncommon that instead of computing the result of a method, you look up a pre-computed value somewhere. This is called "memoization" or "caching."
In this exercise, you check the incoming hand that is being evaluated for strength. If the strength for the hand has already been computed and is found in a database table, you return that pre-computed value. If the incoming hand isn't found in the database, then you compute the strength (as you already already doing), and store the result in the database (so that the next time you can use the database value rather than compute it).
That's it. Nothing more, nothing less.
The upshot for you is:
(1) You get experience with ActiveRecord;
(2) You get some experience with ActiveRecord outside of Rails (believe me, this is important),
(3) You get to do so with a problem domain you already understand;
(4) You exercise a technique that you will almost surely need to implement at some point;
OK?
John
"7. Second job: Review the associations section of AWDR. If you could introduce one or two more models, what would they be? And how would they be associated with the History model?"
I just do not know what you had in mind here.
Mike
Think broadly. For example, at present the idea of a dealt hand is represented by a String. But arguably you could represent each *dealt* card in a separate table ("dealt_cards") with foreign keys to a row in a "cards" table and then to a specific Hand into which it was dealt.
In the model I proposed for 2B to get you started, there is only the fact of the hand having had its strength computed before (or not). But there are good arguments for representing quite a lot more of what is happening.
If you had a table called dealt_cards or some such, you could then run queries over that table to determine which cards have historically generated the strongest hands and so forth.
-----
Another way to look at this is as follows:
Most times when you save data, you are leaving things out (you are choosing to save certain aspects of the state of your app instead of others). So think about the strength computation in its context -- what data isn't getting saved? How could you save it? Does it have any value? Etc.
That question #7 is really a challenge to you to take a fresh look at the assignment and think about what else might be persisted.
Thanks,
Mike
I'm trying to implement the "Second Job" on Assignment 2b. I've created a new migration (creating a table) and a helper associated with the new table. In the History helper, I've added a has_many pointing to the new table, and in the new helper, I've added a belongs_to pointing to the histories table.
What I want to do is use the find(some id #) to look for a specific id and then return the corresponding record from the histories table.
I've read through the docs, and I thought I could call <new>.find(1).history.strength_human_readable, where the result of the find would return an object with a reference to the history table, which I could then use to call the strength_human_readable method. No joy here. I _can_ call the find method on the histories and the <new> independently, but I can't seem to get to the PK table... Do I need to use :through???
Any suggestions are appreciated.
Mike
Mike
Great.
Could you explain at a high level what your extra tables are doing? I think other students would be very interested. You don't have to get into implementation details: Just what you're "modeling" with the tables.
John
The thing is that I'm still having a bit of an issue getting it to work quite right... I'm trying to use the "line_items/product" example in the rails book. In that example, there is a third table which maps the keys from the product and orders tables to the line_items table. There, they use the find method against the line_items table to return a reference to a line_items object. And this object, in turn, has a reference to the products (and orders) table. This is used to get access to column information in the products table.
In using mine, I'm not trying to use the third table. Instead, I'm using the belongs_to in the model for the new table I created to contain the cards, and I'm using has_many in the model I'm using for the history. Each pointing to each other's auto generated id.... BUT it's currently returning nil from my query on the new cards table. I had hoped it would return a reference which I could use to get at the history table and its member columns. So, I guess I'm a bit stuck right now... Still scratching my head a bit.
Mike
Gotta love Google :-) http://www.nabble.com/has_many-and-belongs_to-i...
The above link points out my issue. I confirmed that the DB has "ID" as the key names. So, when using the belongs_to/has_many, I needed to specify that "id" is the foreign key. Once I did that using the following syntax, "belongs_to :history, :foreign_key => :id", it works just fine.
I am now able to get at the data in the associated table by calling a find in a table with a FK/PK relationship. So, I can call <new table>.find(1) and get at the contents of the table <new table> links to.
OK... enough for tonight. WIl complete tomorrow.
Take care,
Mike
Here is my migration to create the histories table:
class CreateHistories < ActiveRecord::Migration
def self.up
create_table :histories, :force => true do |t|
t.string :card_sequence
t.integer :strength
end
end
def self.down
drop_table :histories
end
end
Why would it not create a history_id column, and instead create a generic "id" col?
Mike
The "id" column on histories is that table's PRIMARY KEY. If, for instance, you created a table cards which belonged to a history, then cards would have a foreign key called history_id. For each row in cards, Each value in cards.history_id would be a value from the id column of histories. In other words, each row in cards "refers" to a row in histories.
If you have the newest PDF for AWDR, take a look at the top of p. 339. For both line_items and orders, the primary key is called "id." Since a row in line_items "belongs to" an order, line_items gets the foreign key with the name "order_id."
What is your "other" table? Does a History "have many" of that other table, or does it "belong to" that other table? That will help you see how the migration should be set up.
John
My "other" table is called CardHistory (card_histories in sql land). It has a a "belongs_to :history" declared within it.
Within History, I had declared: "has_many :card_histories".
I had hoped the two statements would create the necessary FKs. I see that they do not. Am I supposed to create them in the migration step? i.e. within the 1_create_histories.rb? (I did not think so based upon the docs).
Mike
-- Create associations (i.e., declare methods that the Ruby code will use to follow the keys) in the Models.
Again, take a look at migrations1. One thing you will see there is that in a migration, you can use a pseudo-data-type called "references" which will create the properly named key for you. It's confusing because you use the model name. In your case, you would put in your migration for card_histories:
t.references :history
poof. Magic. This is just a synonym for: t.integer :history_id
Take care,
Mike
I just needed to create the table as normal, using the migration and then add in t.references. That automatically generated the reference between the tables. And when I call find on the table linked to the history table, I can access data in the history table through the reference which is returned.
Like pretty much everything in life, it's easy once you know how to do it.
Mike
One is that you want to write a list of sentences that describe the relationships between the models. E.g., you want to write sentences like:
A Hand has many Cards.
A Card belongs to a Hand.
You should also try and make some statements about uniqueness. These statements about uniqueness will frequent reveal other things you need to model. For example:
For all Hands, each Card must be unique.
This is a very tricky sentence. The reason is that we rarely think of cards needing to be unique across HANDS; rather, we think of uniqueness in terms of a Deck. This starts to push you to modeling:
A Card belongs to a Deck.
A Card also belongs to a Hand.
Each card is unique in the Deck.
Finally, you should think about whether an entity CAN belong to another entity -- but maybe it doesn't have to. E.g.,
A Card MUST belong to a Deck.
A Card MAY belong to a Hand.
Each card is unique in the Deck.
Etc.
All of these things will help you clarify what you're doing.