I have a structure year which contains groups of students. Student is another stucture
(DEFSTRUCT student
(fname "nil" :type string)
(lname "nil" :type string)
(initGroup 0 :type integer)
(options nil)
(marks nil)
(newGroup nil)
)
Mark is a structure
(DEFSTRUCT mark
(avgy1 0.0 :type single-float)
(avgy22 0.0 :type single-float)
(avgy33 0.0 :type single-float)
(finalAvg 0.0 :type single-float)
)
and I want to access the finalAvg field of this structure through my year structure because I want to sort all the students in my year descending.
I have tried many combinations but nothing seems to be working. Any suggestions?
Something like this:
(setf (year-students *year-2013*)
(sort (year-students *year-2013*)
'>
:key (lambda (student)
(loop for mark in (student-marks student)
maximize (mark-finalavg mark)))))
Style remarks:
avoid dangling parentheses on their own lines
Avoid CamelCase like finalAvg. Use final-avg or similar.
Rainer's answer has this nailed but I just would add the following: the core thing to note is that you can use accessors on the results from other accessors. For example if the variable jim held a student then the following will get you his first avg-mark (assuming that marks is a list of mark structs):
(final-avg (first (marks jim)))
It might be written like this in a language like Python:
jim.marks[0].final-avg
Related
Let's say we have a FoodTable with the following columns: Name, Calories, Carbs, Protein. I have an entry for Name = Chocolate, Calories = 100, Carbs = "10g", and Protein = "2g".
I'm wondering if there's a way to pass in a column name and a new value to update with. For example, I want a method that's like
def updateFood(food, columnName, value):
table.filter(_.name === food).map(x => x.columnName).update(value)
It seems like dynamic columns are not possible with Slick? I want to avoid writing a SQL query because that could lead to security flaws or bugs in the code. Is there really no way to do this?
I also don't want to have to pass in the entire object to update, since ideally, it should be:
I want to update column X to value Y. I should only need to pass in the id of the object, the column, and the value to update to.
I'm wondering if there's a way to pass in a column name and a new value to update with
This depends a little bit on what you want the "column name" to be. To maintain safety, what I'd suggest is having the "column name" be a function that can select a column in your table.
At a high level that would look like this:
// Won't compile, but we'll fix that in a moment
def updateFood[V](food: Food, column: FoodTable => Rep[V], value: V): DBIO[Int] =
foods.filter(_.name === food.name).map(column).update(value)
...which we'd call like this:
updateFood(choc, _.calories, 99)
Notice how the "column name" is a function from FoodTable to a column of some value V. Then you provide a value for the V and we do a normal update.
The problem is that Slick knows how to map certain types of values (String, Int, etc) into SQL, but not any kind of value. And the code above won't compile because V is unconstrained.
We can sort of fix that my adding a constraint on V, and it mostly will work:
// Will compile, will work for basic types
def updateFood[V : slick.ast.BaseTypedType](food: Food, column: FoodTable => Rep[V], value: V): DBIO[Int] =
foods.filter(_.name === food.name).map(column).update(value)
However, if you have custom column mappings, they won't match the constraint. We need to go another step on and have an implicit shape in scope:
def updateFood[V](food: Food, column: FoodTable => Rep[V], value: V)(implicit shape: Shape[_ <: FlatShapeLevel, Rep[V], V, _]): DBIO[Int] =
foods.filter(_.name === food.name).map(column).update(value)
I think of Shape as an extra level of abstraction in Slick, above Rep[V]. The mechanisms of the "shape levels" and other details are not something I can explain because I don't understand them yet! (There is a talk that goes into the design of Slick called "Polymorphic Record Types in a Lifted Embedding" which you can find at http://slick.lightbend.com/docs/)
A final note: if you really want the column name to be a String or something like that, I'd suggest pattern matching the string (or validate in some way) to a FoodTable => Rep function and use that in your SQL. That's going to be tricky because your value V is going to have to match the type of the column you want to update.
Off the top of my head, that could look something like this:
def tryUpdateFood(food: Food, columnName: String, value: String): DBIO[Int] =
columnName match {
case "calories" => updateFood(food, _.calories, value.toInt)
case "carbs" => updateFood(food, _.carbs, value)
// etc...
case unknown => DBIO.failed(new Exception(s"Don't know how to update $unknown columns"))
}
I can imagine better error handling, safer or smarter parsing of the value, but in outline the above could work.
For hints at other ways to approach dynamic problems, take a look at the talk "Patterns for Slick database applications" (also listed at: http://slick.lightbend.com/docs/), and towards the end of the presentation there's a section on "Dynamic sorting".
Im trying to pull all records from a Project model that includes in the project_name the word 'Fox'. I can do an active record search and return specific project_names, like 'Brown Fox':
#projects = Project.where("project_name like ?", "Brown Fox")
But if I want to return all the names that INCLUDE 'Fox', this does not work unless the complete project name is 'Fox':
#projects = Project.where("project_name like ?", "Fox")
How do I do a search that returns all the objects with the word 'Fox' in the name?
Try using:
variable = "Fox"
Project.where("project_name like ?", "%#{variable}%")
You can use the SQL % operator:
#projects = Project.where("project_name like ?", "%Fox%")
Note that if you want your query to return results ignoring the word case, you can use PostgreSQL ilike instead of like.
Did you try ransack ?
With ransack you can do something like
#projects = Project.search(:project_name_cont => "Fox")
If you think it is too much for what you need. you can use the % operator as MurifoX said
Here's a version that will allow you to handle any number of input words and to search for all of them within a name. I was looking for this answer and didn't find the more complicated case, so here it is:
def self.search(pattern)
if pattern.blank? # blank? covers both nil and empty string
all
else
search_functions = []
search_terms = pattern.split(' ').map{|word| "%#{word.downcase}%"}
search_terms.length.times do |i|
search_functions << 'LOWER(project_name) LIKE ?'
end
like_patterns = search_functions.join(' and ')
where("#{like_patterns}", *search_terms)
end
end
How is it possible to get the data of just one column in a database table, e.g. title? I'd like to have an array of strings at the end for bootstrap typeahead.
There's a special method just for that: pluck
Post.pluck :title
If your database model is called "Post" and the column is "title" then
Post.select(:title).all.map(&:title)
Will give you an array of all the titles.
This should work:
def get_titles
Yourcolumn.all.each do |i|
#titles += i.title
end
return #titles
end
I currently have a .find method in one of my rails controller actions - the relevant part is:
.find(:all, :select => 'last_name as id, last_name as name')
I am getting some odd behaviour trying to alias the last_name column as id - if I alias it as anything else, it works fine (i can do last_name as xyz and it outputs the last name in a column called xyz, but as I am using this to populate a drop-down where I need to have the name in the id column, i need it to be called 'id').
I should point out that it does output an id column, but it is always "id":0.
Could anyone shed any light on what I need to do to get this column aliased as 'id'?
Thanks!
I'm not sure of how you can do this in a Rails query statement. Rails is going to try and take over the id column, casting the value returned by the database as id with the type of column that id is (presumably integer). That's why your id column keeps getting set to 0, because "string".to_i #=> 0
However, there is a way to do it, once you have the results back.
Since you have the question tagged as Rails 3, it is preferable to use the new ActiveRelation syntax. You can do the following:
# First, get the results from the query, then loop through all of them.
Customer.select("last_name as 'ln', last_name as 'name'").all.collect do |c|
# The first step of the loop is to get the attributes into a hash form
h = c.attributes
# The next step is to create an "id" key in the hash.
# The Hash#delete method deletes the key/value pair at the key specified and returns the value.
# We'll take that returned value and assign it to the just created "id" key.
h["id"] = h.delete("ln")
# And we have to call out the hash to ensure that it's the returned value from the collect.
h
end
That will get you a hash with the id value as the text string value last_name and a name value as the same.
Hope that helps!
You shouldn't need to setup aliases in the finder SQL just to populate a drop-down. Instead simply use the last_name value for the value attribute (as well as the display text).
Eg if you're using the collection_select helper:
<%= f.collection_select :attribute_id, #collection, :last_name, :last_name %>
Per section 2.2 of rails guide on Active Record query interface here:
which seems to indicate that I can pass a string specifying the condition(s), then an array of values that should be substituted at some point while the arel is being built. So I've got a statement that generates my conditions string, which can be a varying number of attributes chained together with either AND or OR between them, and I pass in an array as the second arg to the where method, and I get:
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 5)
which leads me to believe I'm doing this incorrectly. However, I'm not finding anything on how to do it correctly. To restate the problem another way, I need to pass in a string to the where method such as "table.attribute = ? AND table.attribute1 = ? OR table.attribute1 = ?" with an unknown number of these conditions anded or ored together, and then pass something, what I thought would be an array as the second argument that would be used to substitute the values in the first argument conditions string. Is this the correct approach, or, I'm just missing some other huge concept somewhere and I'm coming at this all wrong? I'd think that somehow, this has to be possible, short of just generating a raw sql string.
This is actually pretty simple:
Model.where(attribute: [value1,value2])
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
Have a look at http://guides.rubyonrails.org/active_record_querying.html#array-conditions for more details on how this works.
Instead of passing the same parameter multiple times to where() like this
User.where(
"first_name like ? or last_name like ? or city like ?",
"%#{search}%", "%#{search}%", "%#{search}%"
)
you can easily provide a hash
User.where(
"first_name like :search or last_name like :search or city like :search",
{search: "%#{search}%"}
)
that makes your query much more readable for long argument lists.
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
#notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value) Have a
look at
http://guides.rubyonrails.org/active_record_querying.html#array-conditions
for more details on how this works.
Was really close. You can turn an array into a list of arguments with *my_list.
Model.where("id = ? OR id = ?", *["1", "2"])
OR
params = ["1", "2"]
Model.where("id = ? OR id = ?", *params)
Should work
If you want to chain together an open-ended list of conditions (attribute names and values), I would suggest using an arel table.
It's a bit hard to give specifics since your question is so vague, so I'll just explain how to do this for a simple case of a Post model and a few attributes, say title, summary, and user_id (i.e. a user has_many posts).
First, get the arel table for the model:
table = Post.arel_table
Then, start building your predicate (which you will eventually use to create an SQL query):
relation = table[:title].eq("Foo")
relation = relation.or(table[:summary].eq("A post about foo"))
relation = relation.and(table[:user_id].eq(5))
Here, table[:title], table[:summary] and table[:user_id] are representations of columns in the posts table. When you call table[:title].eq("Foo"), you are creating a predicate, roughly equivalent to a find condition (get all rows whose title column equals "Foo"). These predicates can be chained together with and and or.
When your aggregate predicate is ready, you can get the result with:
Post.where(relation)
which will generate the SQL:
SELECT "posts".* FROM "posts"
WHERE (("posts"."title" = "Foo" OR "posts"."summary" = "A post about foo")
AND "posts"."user_id" = 5)
This will get you all posts that have either the title "Foo" or the summary "A post about foo", and which belong to a user with id 5.
Notice the way arel predicates can be endlessly chained together to create more and more complex queries. This means that if you have (say) a hash of attribute/value pairs, and some way of knowing whether to use AND or OR on each of them, you can loop through them one by one and build up your condition:
relation = table[:title].eq("Foo")
hash.each do |attr, value|
relation = relation.and(table[attr].eq(value))
# or relation = relation.or(table[attr].eq(value)) for an OR predicate
end
Post.where(relation)
Aside from the ease of chaining conditions, another advantage of arel tables is that they are independent of database, so you don't have to worry whether your MySQL query will work in PostgreSQL, etc.
Here's a Railscast with more on arel: http://railscasts.com/episodes/215-advanced-queries-in-rails-3?view=asciicast
Hope that helps.
You can use a hash rather than a string. Build up a hash with however many conditions and corresponding values you are going to have and put it into the first argument of the where method.
WRONG
This is what I used to do for some reason.
keys = params[:search].split(',').map!(&:downcase)
# keys are now ['brooklyn', 'queens']
query = 'lower(city) LIKE ?'
if keys.size > 1
# I need something like this depending on number of keys
# 'lower(city) LIKE ? OR lower(city) LIKE ? OR lower(city) LIKE ?'
query_array = []
keys.size.times { query_array << query }
#['lower(city) LIKE ?','lower(city) LIKE ?']
query = query_array.join(' OR ')
# which gives me 'lower(city) LIKE ? OR lower(city) LIKE ?'
end
# now I can query my model
# if keys size is one then keys are just 'brooklyn',
# in this case it is 'brooklyn', 'queens'
# #posts = Post.where('lower(city) LIKE ? OR lower(city) LIKE ?','brooklyn', 'queens' )
#posts = Post.where(query, *keys )
now however - yes - it's very simple. as nfriend21 mentioned
Model.where(attribute: [value1,value2])
does the same thing