Ecto update_all example - orm

I would like to update all fields that match query
MyModel
|> where([m], m.state == "begin")
|> update([set: %{state: "commit"}])
|> Repo.update_all()
But I get:
malformed update `[set: %{state: "commit"}]` in query expression, expected a keyword list with set/push/pop as keys with field-value pairs as values
What I do wrong here?

I'm not sure what your update function does but can you try passing in directly to Repo.update_all()
Repo.update_all(from(m in MyModel, where: m.state == "begin", update: [set: [%{state: "commit"}]))

Related

Sqlalchemy use json_set to update specific JSON field

I intend to use func function to update a specific JSON field in Sqlalchemy, but I get some problem, here is my code to update field:
self.db.query(TestModel).filter(TestModel.test_id == self._test_id).update(
{field_name: func.json_set(
field_name,
"$." + key,
formatted_val)}
, synchronize_session='fetch'
)
self.db.commit()
I ran the code above and got the error:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) malformed JSON
So, I go to check the log, found Sqlalchemy form an SQL clause like that:
UPDATE test_model SET field_name=json_set('field_name', '$.keyname', 'value') WHERE test_model.test_id = 1;
the problem is Sqlalchemy should not use 'field_name' to specific the field it should use field_name to specific the field, and I try to run corrected sql clause below in sql client:
UPDATE test_model SET field_name=json_set(field_name, '$.keyname', 'value') WHERE test_model.test_id = 1;
and it work find
I just want to know how to make the Sqlalchemy form the correct field from 'field_name' to field_name?
You should pass first parameter with a name of model to function func.json_set:
self.db.query(TestModel).filter(TestModel.test_id == self._test_id).update(
{field_name: func.json_set(
TestModel.field_name,
"$." + key,
formatted_val)},
synchronize_session='fetch'
)
self.db.commit()

How to group records in Ecto and return them as a map?

I have "influencer_platforms" table in my DB which consists of:
id | influencer_handle | name
I want to query them and group by name like:
%{
name1: [%{id: 1, influencer_handle: "i1"}, %{id: 3, influencer_handle: "i2"}],
name2: [%{id: 2, influencer_handle: "i3"}]
}
How do do that in Ecto? So far I have:
defmacrop influencer_platform_json(influencer) do
quote do
fragment(
"jsonb_agg(?)",
fragment(
"json_build_object(?, ?, ?, ?)",
"id", unquote(influencer).id,
"influencer_handle", unquote(influencer).influencer_handle
)
)
end
end
def all do
from ip in InfluencerPlatform,
group_by: :name,
select: %{
ip.name => influencer_platform_json(ip)
}
end
Is is a more elegant way to achieve it?
If you want all this to be done in the database, you could collapse the two fragments into one. You also don't need to pass the column names as arguments to fragment if they're simple constants; you can put the column names inside the query in fragment.
defmacrop influencer_platform_json(influencer) do
quote do
fragment("jsonb_agg(jsonb_build_object('id', ?, 'influencer_handle', ?))", unquote(influencer).id, unquote(influencer).influencer_handle)
end
end
You could also fetch the necessary data and do the group by in Elixir using Enum.group_by/2. This would be much more elegant but may be less performant than the above, depending on how optimized PostgreSQL's JSON handling is for the above query.
from(ip in InfluencerPlatform, select: map(ip, [:id, :name, :influencer_handle]))
|> Repo.all
|> Enum.group_by(fn ip -> ip.name end)
# Delete `:name` from each map.
|> Enum.map(fn {k, v} ->
{k, Enum.map(v, &Map.delete(&1, :name))}
end)
|> Map.new

Better query using WHERE IN () when param can be nil

For example we have model TableRow - columns (:account_number, :month, :department, :phone_number). And have a method that returns filtered rows by arrays of this params.
For required params we can use
TableRow.where('account_number IN (?)', param)
Is there best way to add in this query unrequired params (department, phone_number) that can be nill and we should return records with any params in this column?
There are a couple ways to approach this. If you want your query to be static, you can check the literal value of your param with the SQL logic itself:
TableRow.where('COALESCE(:depts) IS NULL OR department IN (:depts)', depts: param)
You can also build up your relation incrementally in Ruby:
relation = TableRow.all
relation = relation.where(department: depts) if depts.present?
Your question is hard to understand, but if what you want is to filter by phone_number while still retrieving records where phone_number is null, you just have to that:
TableRow.where('phone_number IN (?)', param << nil)

SQL injections in Rails 4 issue

I'm trying to learn about SQL injections and have tried to implement these, but when I put this code in my controller:
params[:username] = "johndoe') OR admin = 't' --"
#user_query = User.find(:first, :conditions => "username = '#{params[:username]}'")
I get the following error:
Couldn't find all Users with 'id': (first, {:conditions=>"username = 'johndoe') OR admin = 't' --'"}) (found 0 results, but was looking for 2)
I have created a User Model with the username "johndoe", but I am still getting no proper response. BTW I am using Rails 4.
You're using an ancient Rails syntax. Don't use
find(:first, :condition => <condition>) ...
Instead use
User.where(<condtion>).first
find accepts a list of IDs to lookup records for. You're giving it an ID of :first and an ID of condition: ..., which aren't going to match any records.
User.where(attr1: value, attr2: value2)
or for single items
User.find_by(attr1: value, attr2: value)
Bear in mind that while doing all this, it would be valuable to check what the actual sql statement is by adding "to_sql" to the end of the query method (From what I remember, find_by just does a LIMIT by 1)

How to specify multiple values in where with AR query interface in rails3

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