We want to achieve the following:
Be able to compare 'Projects' with other (multiple) Projects.
Save the comparison reference in the database.
In the past we did this by storing an array in the database like below:
t.string "comparisons", default: [], array: true
Now we are thinking about using another table - unless there is a better way?
Ideally something like this:
We already have this table:
| projects |
|-----------|
| id | name |
| 1 | abc |
| 2 | def |
| 3 | ggg |
| 4 | fff |
We want to create another table similar to this:
| project_comparisons |
|-------------------------------|
| id | project_id | compared_id |
| 1 | 1 | 2 |
| 2 | 1 | 4 |
| 3 | 2 | 3 |
| 4 | 2 | 4 |
Where in the end we could do something like this:
Project.find(1).project_comparisons.each do |x|
x.name
end
# Output:
'def'
'fff'
But we are getting lost in the relationship setup.
This is what we have so far:
rails g model ProjectComparison project:references compared:references
# Migration file (edited)
create_table :project_comparisons do |t|
t.references :project, foreign_key: true
t.references :compared
end
class Project
has_many :project_comparisons
has_many :projects, through: :project_comparisons
# ... Here I think we need something in the line above?
end
class ProjectComparison
belongs_to :project
end
This now has the the incorrect output.
If we now iterate the example:
Project.find(1).project_comparisons.each do |x|
x.name
end
# Output:
# aaa
# aaa
It should have been 'def' and 'fff'
Can I somehow specify that we want the 'compared_id' to get the correct Project, or are we on the wrong path here?
What about something like:
create_table :project_comparisons do |t|
t.references :project, foreign_key: true
t.references :compared, foreign_key: { to_table: 'projects'}
end
class Project
has_many :project_comparisons
has_many :compared, through: :project_comparisons
end
class ProjectComparison
belongs_to :project
belongs_to :compared, class_name: "Project", foreign_key: "compared_id"
end
Project.find(1).compared.each do |x|
x.name
end
Related
In Ruby 2.7 and 3.1 this script does the same thing whether or not the % signs are there:
def count(str)
state = :start
tbr = []
str.each_char do
% %case state
when :start
tbr << 0
% %state = :symbol
% when :symbol
tbr << 1
% % state = :start
% end
end
tbr
end
p count("Foobar")
How is this parsed? You can add more % or remove some and it will still work, but not any combination. I found this example through trial and error.
I was teaching someone Ruby and noticed only after their script was working that they had a random % in the margin. I pushed it a little further to see how many it would accept.
Syntax
Percent String Literal
This is a Percent String Literal receiving the message %.
A Percent String Literal has the form:
% character
opening-delimiter
string content
closing-delimiter
If the opening-delimiter is one of <, [, (, or {, then the closing-delimiter must be the corresponding >, ], ), or }. Otherwise, the opening-delimiter can be any arbitrary character and the closing-delimiter must be the same character.
So,
%
(that is, % SPACE SPACE)
is a Percent String Literal with SPACE as the delimiter and no content. I.e. it is equivalent to "".
Operator Message Send a % b
a % b
is equivalent to
a.%(b)
I.e. sending the message % to the result of evaluating the expression a, passing the result of evaluating the expression b as the single argument.
Which means
% % b
is (roughly) equivalent to
"".%(b)
Argument List
So, what's b then? Well, it's the expression following the % operator (not to be confused with the % sigil of the Percent String Literal).
The entire code is (roughly) equivalent to this:
def count(str)
state = :start
tbr = []
str.each_char do
"".%(case state
when :start
tbr << 0
"".%(state = :symbol)
""when :symbol
tbr << 1
"".%(state = :start)
""end)
end
tbr
end
p count("Foobar")
AST
You can figure this out yourself by just asking Ruby:
# ruby --dump=parsetree_with_comment test.rb
###########################################################
## Do NOT use this node dump for any purpose other than ##
## debug and research. Compatibility is not guaranteed. ##
###########################################################
# # NODE_SCOPE (id: 62, line: 1, location: (1,0)-(17,17))
# | # new scope
# | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body
# +- nd_tbl (local table): (empty)
# +- nd_args (arguments):
# | (null node)
[…]
# | | +- nd_body (body):
# | | # NODE_OPCALL (id: 48, line: 5, location: (5,0)-(12,7))*
# | | | # method invocation
# | | | # format: [nd_recv] [nd_mid] [nd_args]
# | | | # example: foo + bar
# | | +- nd_mid (method id): :%
# | | +- nd_recv (receiver):
# | | | # NODE_STR (id: 12, line: 5, location: (5,0)-(5,3))
# | | | | # string literal
# | | | | # format: [nd_lit]
# | | | | # example: 'foo'
# | | | +- nd_lit (literal): ""
# | | +- nd_args (arguments):
# | | # NODE_LIST (id: 47, line: 5, location: (5,4)-(12,7))
# | | | # list constructor
# | | | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
# | | | # example: [1, 2, 3]
# | | +- nd_alen (length): 1
# | | +- nd_head (element):
# | | | # NODE_CASE (id: 46, line: 5, location: (5,4)-(12,7))
# | | | | # case statement
# | | | | # format: case [nd_head]; [nd_body]; end
# | | | | # example: case x; when 1; foo; when 2; bar; else baz; end
# | | | +- nd_head (case expr):
# | | | | # NODE_DVAR (id: 13, line: 5, location: (5,9)-(5,14))
# | | | | | # dynamic variable reference
# | | | | | # format: [nd_vid](dvar)
# | | | | | # example: 1.times { x = 1; x }
# | | | | +- nd_vid (local variable): :state
[…]
Some of the interesting places here are the node at (id: 12, line: 5, location: (5,0)-(5,3)) which is the first string literal, and (id: 48, line: 5, location: (5,0)-(12,7)) which is the first % message send:
# | | +- nd_body (body):
# | | # NODE_OPCALL (id: 48, line: 5, location: (5,0)-(12,7))*
# | | | # method invocation
# | | | # format: [nd_recv] [nd_mid] [nd_args]
# | | | # example: foo + bar
# | | +- nd_mid (method id): :%
# | | +- nd_recv (receiver):
# | | | # NODE_STR (id: 12, line: 5, location: (5,0)-(5,3))
# | | | | # string literal
# | | | | # format: [nd_lit]
# | | | | # example: 'foo'
# | | | +- nd_lit (literal): ""
Note: this is just the simplest possible method of obtaining a parse tree, which unfortunately contains a lot of internal minutiae that are not really relevant to figuring out what is going on. There are other methods such as the parser gem or its companion ast which produce far more readable results:
# ruby-parse count.rb
(begin
(def :count
(args
(arg :str))
(begin
(lvasgn :state
(sym :start))
(lvasgn :tbr
(array))
(block
(send
(lvar :str) :each_char)
(args)
(send
(dstr) :%
(case
(lvar :state)
(when
(sym :start)
(begin
(send
(lvar :tbr) :<<
(int 0))
(send
(dstr) :%
(lvasgn :state
(sym :symbol)))
(dstr)))
(when
(sym :symbol)
(begin
(send
(lvar :tbr) :<<
(int 1))
(send
(dstr) :%
(lvasgn :state
(sym :start)))
(dstr))) nil)))
(lvar :tbr)))
(send nil :p
(send nil :count
(str "Foobar"))))
Semantics
So far, all we have talked about is the Syntax, i.e. the grammatical structure of the code. But what does it mean?
The method String#% performs String Formatting a la C's printf family of functions. However, since the format string (the receiver of the % message) is the empty string, the result of the message send is the empty string as well, since there is nothing to format.
If Ruby were a purely functional, lazy, non-strict language, the result would be equivalent to this:
def count(str)
state = :start
tbr = []
str.each_char do
"".%(case state
when :start
tbr << 0
""
""when :symbol
tbr << 1
""
""end)
end
tbr
end
p count("Foobar")
which in turn is equivalent to this
def count(str)
state = :start
tbr = []
str.each_char do
"".%(case state
when :start
tbr << 0
""
when :symbol
tbr << 1
""
end)
end
tbr
end
p count("Foobar")
which is equivalent to this
def count(str)
state = :start
tbr = []
str.each_char do
"".%(case state
when :start
""
when :symbol
""
end)
end
tbr
end
p count("Foobar")
which is equivalent to this
def count(str)
state = :start
tbr = []
str.each_char do
"".%(case state
when :start, :symbol
""
end)
end
tbr
end
p count("Foobar")
which is equivalent to this
def count(str)
state = :start
tbr = []
str.each_char do
""
end
tbr
end
p count("Foobar")
which is equivalent to this
def count(str)
state = :start
tbr = []
tbr
end
p count("Foobar")
which is equivalent to this
def count(str)
[]
end
p count("Foobar")
Clearly, that is not what is happening, and the reason is that Ruby isn't a purely functional, lazy, non-strict language. While the arguments which are passed to the % message sends are irrelevant to the result of the message send, they are nevertheless evaluated (because Ruby is strict and eager) and they have side-effects (because Ruby is not purely functional), i.e. their side-effects of re-assigning variables and mutating the tbr result array are still executed.
If this code were written in a more Ruby-like style with less mutation and fewer side-effects and instead using functional transformations, then arbitrarily replacing results with empty strings would immediately break it. The only reason there is no effect here is because the abundant use of side-effects and mutation.
I need to sort a list of tasks:
|----------------------------------------------------------------------|
| title | priority | due_at |
| ---------------------------------|-------------|---------------------|
| Mow the lawn | 1 | 2011-09-11 22:00:00 |
| Call mom | 3 | 2010-01-26 09:29:03 |
| Bake a cake | 2 | 2013-09-13 08:45:37 |
| Feed the cat | 2 | 2015-09-12 16:03:51 |
| Remember you don't like the cat | 2 | 2014-03-19 23:00:00 |
|----------------------------------------------------------------------|
The order clause should sort overdue tasks by priority, all others by due_at, e.g. the resulting order should be
Mow the lawn
Bake a cake
Call mom
Remember you don't like the cat
Feed the cat
I ended up with the following (plain SQL, not yet translated to AR):
SELECT *
FROM tasks
ORDER BY
due_at <= Now() DESC,
CASE due_at <= Now() WHEN true THEN priority END ASC,
CASE due_at <= Now() WHEN true THEN due_at END ASC,
CASE due_at <= Now() WHEN false THEN due_at END DESC,
CASE due_at <= Now() WHEN false THEN priority END ASC
If you just need an array of the tasks stored in #tasks you can do this:
#tasks = Task.where(due_at: 10.years.ago..Time.now).order(:priority)
#tasks += Task.where.not(due_at: 10.years.ago..Time.now).order(:due_at)
If you need an Task::ActiveRecord_Relation you'll have to do this:
Task.where(id: Task.where(due_at: 10.years.ago..Time.now).
order(:priority).pluck(:id) +
Task.where.not(due_at: 10.years.ago..Time.now).
order(:due_at).pluck(:id))
For personal homework I am trying out a digital clock program to display 'big' numbers.
A ruby program that has parts of the clock that come from strings stored in an array and then a routine (not yet fully written) that can display any numbers as 'big numbers'.
This is to help me learn more about manipulating hashes and iterating through arrays, concatenating string, etc.
I am stuck right now with this:
all_nums=''
(0..3).each do |num|
all_nums+= (line_parts[num][0].values).to_s
#puts line_parts[num][0].values
end
puts all_nums
because I am getting back array elements that I can't convert to strings, i.e.
ruby digital_clock,rb
[" == "]["| | "]["| | "][" == "]
but when I just puts them (commented out 3rd line from the bottom) it is OK but, I get strings, i.e.
ruby digital_clock.rb
==
| |
| |
==
I want to be able to build up the string (big 0 in this case) but I can only puts out the pieces and I've been unable to assign them to a variable and print it. How could I do that?
The full program is:
top= ' == | == == | | == | == == == '
top_middle= '| | | __| __| |__| |__ |__ | |__| |__|'
bottom_middle='| | | | | | | | | | | | |'
bottom= ' == | == == | == == | == |'
number_parts=[{},{},{},{},{},{},{},{},{},{}]
line_parts=[[],[],[],[]]
['top','top_middle','bottom_middle','bottom'].each_with_index do |one_line, i|
number_parts=[{},{},{},{},{},{},{},{},{},{}]
(0..9).each do |the_num|
number_parts[the_num] = {one_line.to_sym => eval(one_line)[the_num*5,5].to_s}
end
line_parts[i]= number_parts
end
all_nums=''
(0..3).each do |num|
all_nums+= (line_parts[num][0].values).to_s
#puts line_parts[num][0].values
end
puts all_nums
I think this will fix your immediate problem -- namely, printing a big "0".
all_nums = []
(0..3).each do |num|
all_nums.push(line_parts[num][0].values)
end
puts all_nums.join("\n")
A better way to do that would be with map:
all_nums = (0..3).map { |num| line_parts[num][0].values }
puts all_nums.join("\n")
Another issue: you don't need to use eval. In fact, one almost never needs to use eval.
# Use variables rather than the strings 'top', 'top_middle', etc.
[top,top_middle,bottom_middle,bottom].each_with_index do |one_line, i|
...
# Then the eval() is unnecessary.
number_parts[the_num] = {one_line.to_sym => one_line[the_num*5,5]}
....
But you can really simplify the code along the following lines:
# Use an array, not a set of specifically named variables.
# This makes it a lot easier to iterate over the data.
rows = [
' -- | -- -- | | -- | -- -- -- ',
' | | | __| __| |__| |__ |__ | |__| |__|',
' | | | | | | | | | | | | |',
' -- | -- -- | -- -- | -- |',
]
# Create an array-of-arrays.
# num_parts[NUMBER] = [an array of four strings]
width = 5
num_parts = (0..9).map { |n| rows.map { |r| r[n * width, width] } }
# Inspect.
num_parts.each do |np|
puts
puts np.join("\n")
end
You might also take a look at the Unix command called banner, which will inspire you to make the numbers EVEN BIGGER. That would allow you to start adding curves and other design touches.
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
Ok, I'm green, but the following def seems double-negative-y
def signed_in?
!current_user.nil? #the current user is not...um...not
end
Since my patient mentor M.Hartl uses it in his Rails tutorial. I gotta believe it's squeaky, but...
wouldn't something that says "the current user is" be cleaner?
def signed_in?
current_user
current_user.present?
current_user.any?
!!current_user
end
What's the benefit of the bang?
current_user | nil | false | true | "" | [] | [nil] | [0]
-----------------------------------------------------------------------------------
current_user | nil | false | true | "" | [] | [nil] | [0]
!current_user.nil? | false | true | true | true | true | true | true
!!current_user | false | false | true | true | true | true | true
current_user.present? | false | false | true | false | false | true | true
current_user.any? | error | error | error | error | false | false | true
You could:
def signed_in?
current_user
end
if you can stand it being not a boolean returned from the method. If current_user is nil signed_in? will evaluate to false when used in an if statement. To check for nil? is not necessary and I guess it is a matter of style.
Or in your calling code you could:
if current_user
# do stuff
end
and get rid of the extra method.
In that example you don't care who the user is. All you want to know is that a user, any user, is signed in. If so, you will show links for signed-in users.
current_user.nil? is a boolean. It will be false is a user is signed in. The bang in front inverts it, so !current_user.nil? will be true if any user is signed in, false otherwise.
Can you think of another equally succinct way to accomplish exactly that, nothing more and nothing less?
I have a table of products with attributes like SKU, Name, etc.
It seems like I need to have separate tables for each category of product due to the large variety of features depending on the category.
Say I have a table/class for boots, tools and hats
I'd like (unless there's a better way) to join/associate my products table to the other tables where appropriate (depending on category)?
Products Table
id | Name | SKU | Category(Table) | CategoryTableForeignKey
----------------------------------------------------------------------
1 | boot1 | 123 | Boots | 2
2 | knife1 | 345 | Tools | 42
-
Boots Table
id | product_id | Size | Width | Color | Sex
----------------------------------------------------------------------
2 | 1 | 9.5 | Wide | Olive | Male
-
Tools Table
id | product_id | length | Fixed | Coating | Etc
----------------------------------------------------------------------
42 | 2 | 4.5 | False | ... | ...
I'm not sure how to do this with DataMapper classes.
Ideally, I'd like to be able to:
my_boot = Boots.get(1)
my_boot.product.sku
Edit: dkubb pointed out a flaw in my code.
When you use has() what you're saying is that the foreign key is in the other model. If you want the foreign key to be in the table associated with the current model you want to use belongs_to.
class Product
include DataMapper::Resource
#descripe product properties ...
has n, :boots
end
Class Boot
include DataMapper::Resource
#describe boot properties ...
belongs_to :product
end
About DataMapper Associations: http://datamapper.org/docs/associations.html