How to use conditions in update/delete commands with Hanami::Repository? - hanami

Can't figure out how to update/delete records by conditions with Hanami::Repository.
For example, there are these tables: users and clients.
Users have:
deleted_at column, for marking user as deleted
client_id column, as foreign key on clients table
How can I update user by id, only if record not marked as deleted, and scoped by specific client?
Here is the pseudo code:
Users
.joins(:clients)
.where('clients.id = ?', client_id)
.where(deleted_at: nil)
.update(new_attributes)

I think it should works for your case
UserRepository.new.users
.where(id: user_id, deleted_at: nil, client_id: client_id)
.update(attributes)
gem versions:
hanami-model (1.3)
pg (1.1.4)

Just expanding on we138's answer:
Hanami uses ROM and Sequel under the hood for it's repositories.
If you have any doubts about how to add filters for your queries, you can always check the documentation for Hanami repositories, and for those gems.
Using the hanami repository, you could do this update as described by we138:
UserRepository.new.users
.where(id: user_id, deleted_at: nil, client_id: client_id)
.update(attributes)
And that would return you the number of affected rows.
If you wish to update the entry using your filters and return the affected rows, you can use sequel directly:
Sequel::Model.db[:users]
.where(id: user_id, deleted_at: nil, client_id: client_id)
.returning
.update(attributes)
You can find more information on how to filter queries using ROM and Sequel in the following links:
https://rom-rb.org/4.0/learn/sql/queries/
https://sequel.jeremyevans.net/rdoc/files/doc/dataset_filtering_rdoc.html

Related

When an user views all articles, see their own articles first

we have two models
article
user
user have many articles
i need When an user views all articles, see their own articles first.
my idea
make two query
first query return articles related to query
Article.where(user_id: user_id)
second query
Article.where.not(user_id: user_id)
and merge result
second Idea
get all articles and select method in ruby
but i need best way make this
i use Ruby On Rails 6.1 and Ruby 3
You could run one query but sort the articles with SQL depending on if they have a matching user_id:
Article.order(Arel.sql("CASE user_id WHEN #{user_id} THEN 0 ELSE 1 END"))
Note: order does not support multiple arguments and input sanitization out of the box. Use this only, when you are sure that the user_id contains only a valid user id, for example, be using current_user.id instead of user_id
In Rails 7 there will be a new method called in_order_of which would allow writing the same functionality like this:
Article.in_order_of(:user_id, user_id)
More programmatic approach
articles = Article.arel_table
case_statement = Arel::Nodes::Case.new(articles[:user_id])
.when(user_id)
.then(0)
.else(1)
Article.order(case_statement)

Laravel 5.6.9 - database count gives different outputs

Does anyone know why these two ways to count numbers of users in my table give different answers when i run them in tinker?
App\Models\User::count()
=> 92269
$count = \DB::table('users')->count()
=> 92829
Running a SQL query in Sequel Pro gives 92829.
If you have the SoftDelete trait on your User model then when you query via the Model it excludes "deleted" entries. You can include them by adding the withTrashed() constraint.
App\Models\User::withTrashed()->count();
https://laravel.com/docs/7.x/eloquent#soft-deleting

ActiveRecord - Query with Condition of Last Element in Association

Trying to do a sort of complex query in ActiveRecord right now. Not sure if it's possible to do entirely in ActiveRecord (i.e. without just filtering intermediate results in Rails). I'd like to do as much manipulation as possible in the database.
I have a PullRequest and Release model with a many-many relationship. There exist fields on the Release model is_rollback(boolean) and ended_at (datetime). I am trying to find all pull_requests for which their releases association 1) is nonempty and 2) ends with a release that has is_rollback = true when the releases are ordered by ended_at.
Here's what I have so far:
PullRequest
.joins(:releases) # filter pull requests with empty release associations
.having('releases.is_rollback = true AND releases.ended_at = MAX(releases.ended_at)') # where latest release is a rollback
.group('pull_requests.id, releases.ended_at, releases.is_rollback') # necessary with 'having'
The having clause is not working as expected though and the query is still returning pull requests whose latest release is not a rollback. Not hugely experienced with complex SQL queries so any help is appreciated. I'm using Rails 4, PostgreSQL db.
Thanks!
I was able to figure it out - here is the working query:
pull_requests
.joins(:releases).distinct
.group('releases.ended_at, pull_requests.id, releases.id')
.having('releases.ended_at IN (SELECT MAX(releases.ended_at))')
.where('releases.is_rollback = true')
Thanks.

How do I insert data only if it matches a certain condition from one table to another table in Rails?

I'm somewhat of a begginer to SQL and migrations in general, so it's possible my approach is completely wrong here, but bear with me ;)
I have a Company model that has attributes for SMTP mailing configuration (like smtp_address and smtp_port), which are optional.
However, I now need my User model to be able to have an SMTP configuration as well, so I created a SMTPConfiguration model with attributes address, port, and a polymorphic association to smtpable. This model will belong either to the User or Company model
What I'd like to do now is migrate the data I already have in the companies table over to the SMTP configurations table, which sounds pretty straightforward, except I want to create a new SMTPConfiguration ONLY if the Company actually has the smtp_address and smtp_port set. If smtp_address and smtp_port are not present, the Company should not have a belonging SMTPConfiguration.
This would be fairly easy to do using Ruby conditions and ActiveRecord, but from what I've read inserting rows individually with ActiveRecord can be reaaally long for large databases, so it is a much better idea to use raw SQL. Turns out I suck at SQL :( .
I've searched on StackOverFlow quite a bit, and using this and this, I came up with something like
execute <<-SQL
INSERT INTO smtp_configurations (address, port, smtpable_id)
SELECT (c.smtp_address, c.smtp_port, c.id)
FROM companies c
WHERE c.smtp_address <> NULL
SQL
This wouldn't work since it doesn't set created_at, updated_at and smtpable_type, but to be honest I'm out of ideas. Could the activerecord-import gem be of any use?
I am using Rails 5.1.4 and Postgres 10.3
Try next code in your migration:
created_at = Time.current.to_s(:db)
insert_clause = <<-SQL
INSERT INTO smtp_configurations(
smtpable_id,
smtpable_type,
address,
port,
created_at,
updated_at
)
SELECT
c.id,
'Company',
c.smtp_address,
c.smtp_port,
'#{created_at}',
'#{created_at}'
FROM companies c
WHERE (c.smtp_address IS NOT NULL) AND (c.port IS NOT NULL)
SQL
execute(insert_clause)
UPDATED: Changed code for plain Rails.

Join with subquery in Django ORM

I want to run a filter using Django's ORM such that I get a distinct set of users with each user's most recent session. I have the tables set up so that a user has many sessions; there is a User and Session model with the Session model having a user = models.ForeignKey(User).
What I've tried so far is Users.objects.distinct('username').order_by('session__last_accessed'), but I know that this won't work because Django puts the session.last_accessed column into the selection, and so it's returning me, for example, 5 duplicate usernames with 5 distinct sessions rather than the single recent session and user.
Is it possible to query this via Django's ORM?
Edit: Okay, after some testing with SQL I've found that the SQL I want to use is:
select user.username, sub_query.last_accessed from (
select user_id, max(last_accessed) as last_accessed
from session
group by user_id
) sub_query
join user on
user.id = sub_query.user_id
order by sub_query.last_accessed desc
limit 5
And I can do sub_query via Session.objects.values('user').annotate(last_accessed=Max('last_accessed')). How can I use this sub_query to get the data I want with the ORM?
Edit 2: Specifically, I want to do this by performing one query only, like the SQL above does. Of course, I can query twice and do some processing in Python, but I'd prefer to hit the database once while using the ORM.
If you are using mysql backend, the following solution can be useful:
users_in_session = Session.objects.values_list('user_id', flat=True)
sessions_by_the_user_list = Session.objects \
.filter(user__in=set(users_in_session)) \
.order_by('last_accessed').distinct()
If you use the sub_query, then order_by('last_accessed') function should be good enough to get data in ordered list. Although as far as I have tested these results seemed unstable.
Update:
You can try:
Session.objects.values('user') \
.annotate(last_accessed=Max('last_accessed')) \
.orde‌​r_by('last_accessed').distinct()
Calling distinct('username') shouldn't return duplicate usernames ever. Are you sure you are using Django version that supports .dictinct(fields), that is Django version later than 1.4? Prior to Django 1.4 .distinct(fields) was accepted by the oRM, but it didn't actually do the correct DISTINCT ON query.
Another hint that things aren't working as expected is that .distinct(username).order_by(session__last_accessed) isn't a valid query - the order_by should have username as first argument because order_by must be prefixed with the field names in .distinct() call. See https://docs.djangoproject.com/en/1.4/ref/models/querysets/#django.db.models.query.QuerySet.distinct for details.