I have a Rails4 app with the following models:
1. Systems (has many devices, has many parameters through devices)
2. Devices (belongs to a system, has many parameters)
3. Parameters (belongs to a Device)
4. Events (polymorphic - Systems, Devices and Parameters can have events)
When an event is created, a boolean field (on the event) is assigned a value. False indicated a failure.
I have a scope on my events, to only show failing events:
scope :failing, -> { where(okay: false).order('created_at desc') }
I can retrieve events as follows:
System.events.failing
Device.events.failing
Parameter.events.failing
I am trying to return a list of Systems where either:
1. the most recent event for the system has failed
2. the most recent event for any of it's devices has failed
3. the most recent event for any parameters of it's devices have failed
I have written this (horrible) SQL query which when executed in the console, returns the systems as an array:
"SELECT * FROM Systems WHERE Systems.id IN (SELECT Devices.system_id FROM Devices WHERE Devices.id IN (SELECT Parameters.device_id FROM Parameters JOIN EVENTS ON Parameters.id=Events.eventable_id WHERE Events.okay ='f')) OR Systems.id IN (SELECT Devices.system_id FROM Devices JOIN Events ON Devices.id=Events.eventable_id WHERE Events.okay='f')")
I need to either define a scope on the System model or a class method to return a list of 'failing' systems. Can you help?
You could mix joins and merges (to merge the WHERE clauses of a scope):
class System
# 1. the most recent event for the system has failed
scope :with_failing_events, -> { joins(:events).merge Event.failing }
# 2. the most recent event for any of it's devices has failed
scope :with_failing_devices, -> { joins(devices: :events).merge Event.failing }
# 3. the most recent event for any parameters of it's devices have failed
scope :with_failing_parameters, -> { joins(devices: { parameters: :events }).merge Event.failing }
end
Notice that passing a hash to joins enables a multiple join, or at least that seems to work in the app I'm working now at (Rails 4.0.5 postgresql).
To filter just the latest event (warning does not work in postgres, untested on other adapters), you could append to this queries:
System.joins(:events).merge(Event.failing).where events: { updated_at: 'MAX("events"."updated_at")' }
In any case, you can merge all these scopes with OR like this:
class System
scope :failing, -> do
where(
[
with_failing_events,
with_failing_devices,
with_failing_parameters
].map(&:where_clauses).join(' OR ')
)
end
end
One option, which would simplify things a bit, would be to add the foreign key, system_id, to the events table. You could then do the following:
Event.where(system_id: [system_id], okay: false).order('created_at DESC')
NOTE: I wouldn't define any relations for this new foreign key, I would simply use it to filter the events table.
Related
I've got the problem when two people at the same time tries to add each other to friends. So, at first when one person presses add to friends he checks, whether other user's groups doesn't have my ID. If he has, I don't need to create new group - I will just add this group's ID to my list, else - I will create new group. Now, when two people at the same time press that button, they both get the result that group doesn't exist thus they both create a new group. How to solve these kind of problems?
Structure:
"userGroups" : {
"myId1" : {
"generatedGroupId1" : "myFriendID1"
}
}
Update: I've managed to do it: basically in doTransaction I create group if it doesn't exist and then on onComplete I work with already created group. If two people start creating new group, one end up creating it, second one - reading it.
// function
ref.runTransaction(object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
// create group here if data is null
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
Log.d(TAG, "postTransaction:onComplete:" + error)
// continue doing stuff, group already exists at this point
}
})
}
It's hard to give specific without seeing your code, but the most likely options are (in order of my personal preference):
Base the group ID on the two users in it. If you do this correct, the two users will end up with the same group ID, and it doesn't matter who is first. Creating the groups then becomes a so-called idempotent operation, which means that if you run the same operation multiple times, it will have the same result. For an example of such group IDs, see Best way to manage Chat channels in Firebase
Use a transaction to ensure only one write makes it through. This would mean that the second user ends up reading the group created by the first user, and can then cancel their data creation.
Use a Cloud Function, which can perform more (non-atomic) read operations to check whether the group of users already exists, and reject the request from the second user.
In Rails 5, what is the difference between update and update_attributes methods. I'm seeing the following results for both the methods
Returns true/false
Checking for active record validation
Call backs are triggered
and also regarding update method a new thing was introduced in active record relation. I'm not able to understand it. What is the difference?
Moreover are we using update_attributes in Rails 5. It's not there in active record documentation.
I'm confused with all update methods. I need clarity
As of Rails 4.0.2, #update returns false if the update failed. Before Rails 4.0.2, #update returned the object that got updated. The main difference therefore was the return value. After this change, #update_attributes is just an alias of #update. It seems there are talks to deprecate #update_attributes in Rails 6 which is not released yet.
https://github.com/rails/rails/pull/31998
https://github.com/rails/rails/commit/5645149d3a27054450bd1130ff5715504638a5f5
From the rails 5 files it seems to me update can be used to update multiple objects(array of records) but update_attributes only work on single records otherwise both are same
From rails core files for update_attributes:
Updates a single attribute and saves the record.
This is especially useful for boolean flags on existing records. Also note that
Validation is skipped.
\Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
This method raises an ActiveRecord::ActiveRecordError if the
attribute is marked as readonly.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
save(validate: false)
end
For Update
Updates an object (or multiple objects) and saves it to the database, if validations pass.
The resulting object is returned whether the object was saved successfully to the database or not.
==== Parameters
+id+ - This should be the id or an array of ids to be updated.
+attributes+ - This should be a hash of attributes or an array of hashes.
==== Examples
# Updates one record
Person.update(15, user_name: "Samuel", group: "expert")
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
# Updates multiple records from the result of a relation
people = Person.where(group: "expert")
people.update(group: "masters")
Note: Updating a large number of records will run an UPDATE
query for each record, which may cause a performance issue.
When running callbacks is not needed for each record update,
it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
for updating all records in a single query.
def update(id, attributes)
if id.is_a?(Array)
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
object.update(attributes[idx])
}
else
if ActiveRecord::Base === id
raise ArgumentError,
"You are passing an instance of ActiveRecord::Base to `update`. " \
"Please pass the id of the object by calling `.id`."
end
object = find(id)
object.update(attributes)
object
end
end
When we are working with update_column that time update is done on the database level there is no any contact with the rails ORM so whatever logic we have implemented like callbacks and validations all will be waste and wont be useful as this is going to be bypassed.
I found this article explained really well in just 30 seconds.
.update
Use update when you want to return false, for example in an if/else:
if record.update(params)
display_success
else
react_to_problem
end
.update!
Use update! when you want an error (for example: to avoid erroring silently, which could be very bad if an error was unexpected and you needed to know about it to fix it!):
record.update!(params) # raises is invalid
'update' respects the validation rules on model, while 'update_attributes' ignores validations.
We are creating a service for an app using tornado and sqlalchemy. The application is written in django and uses a "soft delete mechanism". What that means is that there was no deletion in the underlying mysql tables. To mark a row as deleted we simply set the attributed "delete" as True. However, in the service we are using sqlalchemy. Initially, we started to add check for delete in the queries made through sqlalchemy itself like:
customers = db.query(Customer).filter(not_(Customer.deleted)).all()
However this leads to a lot of potential bugs because developers tend to miss the check for deleted in there queries. Hence we decided to override the default querying with our query class that does a "pre-filter":
class SafeDeleteMixin(Query):
def __iter__(self):
return Query.__iter__(self.deleted_filter())
def from_self(self, *ent):
# override from_self() to automatically apply
# the criterion too. this works with count() and
# others.
return Query.from_self(self.deleted_filter(), *ent)
def deleted_filter(self):
mzero = self._mapper_zero()
if mzero is not None:
crit = mzero.class_.deleted == False
return self.enable_assertions(False).filter(crit)
else:
return self
This inspired from a solution on sqlalchemy docs here:
https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/PreFilteredQuery
However, we are still facing issues, like in cases where we are doing filter and update together and using this query class as defined above the update does not respect the criterion of delete=False when applying the filter for update.
db = CustomSession(with_deleted=False)()
result = db.query(Customer).filter(Customer.id == customer_id).update({Customer.last_active_time: last_active_time })
How can I implement the "soft-delete" feature in sqlalchemy
I've done something similar here. We did it a bit differently, we made a service layer that all database access goes through, kind of like a controller, but only for db access, we called it a ResourceManager, and it's heavily inspired by "Domain Driven Design" (great book, invaluable for using SQLAlchemy well). A derived ResourceManager exists for each aggregate root, ie. each resource class you want to get at things through. (Though sometimes for really simple ResourceManagers, the derived manager class itself is generated dynamically) It has a method that gives out your base query, and that base query gets filtered for your soft delete before it's handed out. From then on, you can add to that query generatively for filtering, and finally call it with query.one() or first() or all() or count(). Note, there is one gotcha I encountered for this kind of generative query handling, you can hang yourself if you join a table too many times. In some cases for filtering we had to keep track of which tables had already been joined. If your delete filter is off the primary table, just filter that first, and you can join willy nilly after that.
so something like this:
class ResourceManager(object):
# these will get filled in by the derived class
# you could use ABC tools if you want, we don't bother
model_class = None
serializer_class = None
# the resource manager gets instantiated once per request
# and passed the current requests SQAlchemy session
def __init__(self, dbsession):
self.dbs = dbsession
# hand out base query, assumes we have a boolean 'deleted' column
#property
def query(self):
return self.dbs(self.model_class).filter(
getattr(self.model_class, 'deleted')==False)
class UserManager(ResourceManager):
model_class = User
# some client code might look this
dbs = SomeSessionFactoryIHave()
user_manager = UserManager(dbs)
users = user_manager.query.filter_by(name_last="Duncan").first()
Now as long as I always start off by going through a ResourceManager, which has other benefits too (see aforementioned book), I know my query is pre-filtered. This has worked very well for us on a current project that has soft-delete and quite an extensive and thorny db schema.
hth!
I would create a function
def customer_query():
return db.session.query(Customer).filter(Customer.deleted == False)
I used query functions to not forget default flags, to set flags based on user permission, filter using joins etc, so that these things wont be copy-pasted and forgotten at various places.
I'm using the AR includes method to execute a LEFT OUTER JOIN between objects User and Building, where a User may or may not have a Building association:
users = User.includes(:building).references(:buildings)
Since I'm using references, any associated Building objects will be eager loaded.
My expectation was that I would then be able to iterate through the list of users, and check whether a user had a building associated with them without triggering additional queries, but I see that in fact whenever I try to access the building property of a user that doesn't have one, AR makes another SQL call to try and retrieve that building (though on subsequent tries it will just return nil).
These queries are obviously redundant as the association would have been loaded during the initial join, and seems to defeat the whole purpose of eager loading with includes/references, as now I'm looking at N times the number of queries equal to the number of empty associations.
users.each do | user |
# This will trigger a new query when building is not present:
# SELECT "buildings".* FROM "buildings" WHERE "buildings"."address" = $1 LIMIT 1 [["address", "123 my street"]]
if user.building
puts 'User has building'
else
puts 'User has no building'
end
end
User class:
class User < ActiveRecord::Base
belongs_to :building, foreign_key: 'residence_id'
end
Is there a way to check the presence of the users' building association without triggering extra queries?
ON RAILS 4.2.0 / POSTGRES
UPDATE:
Thank you #BoraMa for putting together this test. Looks like we're getting different behavior across recent Rails versions:
OUTPUT (RAILS 4.2.0):
User 1 has building
User 2 has building
User 3 has no building
D, [2016-05-26T11:48:38.147316 #11910] DEBUG -- : Building Load (0.2ms) SELECT "buildings".* FROM "buildings" WHERE "buildings"."id" = $1 LIMIT 1 [["id", 123]]
User 4 has no building
OUTPUT (RAILS 4.2.6)
User 1 has building
User 2 has building
User 3 has no building
User 4 has no building
OUTPUT (RAILS 5.0.0)
User 1 has building
User 2 has building
User 3 has no building
User 4 has no building
Take aways:
This issue was limited to "dangling foreign keys (ie the residence_id
column is not nil but there is no corresponding building object)"
(THANKS #FrederickCheung)
The issue has been resolved as of Rails 4.2.6
Sounds like you got bit by a bug in Active Record, that was fixed in rails 4.2.3.
In the case where the column was nil Active Record already knows that it doesn't even need to try loading the associated object. The remaining cases were the ones impacted by this bug
Seems like a typo, please notice building instead of buildings: User.includes(:building).references(:buildings)
That should trigger the big query that uses the format of AS tX_rY for each association and table.
It seems that since rails 4.1 there are potential clashes with how just how implicit #includes should be, see the following open issue.
This code is all untested for syntax, but there would be two approaches I would try:
1/ Make the eager loading implicit
users = User.eager_load(:building).preload(:buildings)
2/ Separate out the two types of users, ones where the building is attached, meaning you don't even try and preload the building, removing the innefficiency.
users = User.includes(:building).where.not(residence_id: nil).references(:buildings)
users.each do | user|
puts "User has building: #{user} #{user.building}"
end
# No additional references needed to be eager-loaded.
users = User.where(residence_id: nil)
users.each do | user |
puts "#{User} has no building."
end
This is a somewhat more complicated version of the question I asked previously.
Background:
So what I need is to display a list of articles. An article belongs to a media outlet. A media is located in a particular country and publishes articles in a particular language. So the data structure is as follows:
Article belongs to Media; Media has many Articles
Media belongs to a Country; Country has many Media
Media belongs to a Language; Language has many Media
Now, if I wanted to filter articles by media, I could use the following class method (I prefer class methods over scopes, because I am passing a parameter and am using a conditional statement inside the method):
def self.filter_by_media(parameter)
if parameter == "all"
all
else
where(media_id: parameter)
end
end
Question:
How to write a class method that would filter Articles based by properties of its associated model, the Media? For example, I want to get a list of articles published by media located a certain counrty or in several countries (there is also a default country when the user does not make any choice). Here’s what I tried:
# parameter can be either string 'default' or an array of id’s
def self.filter_by_country(parameter)
if parameter == "default"
joins(:media).where(media: [country_id: 1])
else
joins(:media).where(media: [country_id: parameter])
end
end
But that doesn’t work, and I am not conversant enough with SQL to figure out how to make this work. Could you please help?
Update:
I’m trying out #carlosramireziii's suggestion. I changed arrays into hashes (don't know what possessed me to use arrays in the first place), but I’m getting the following error in the Rails console (to avoid confusion, in my database, media is called agency):
def self.filter_by_country(parameter)
if parameter == "default"
joins(:agency).where(agency: {country_id: 1})
else
joins(:agency).where(agency: {country_id: parameter})
end
end
in Rails console:
> Article.filter_by_country('default')
=> Article Load (1.9ms) SELECT "articles".* FROM "articles" INNER JOIN "agencies" ON "agencies"."id" = "articles"."agency_id" WHERE "agency"."country_id" = 1
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "agency"
LINE 1: ...ON "agencies"."id" = "articles"."agency_id" WHERE "agency"."...
^
: SELECT "articles".* FROM "articles" INNER JOIN "agencies" ON "agencies"."id" = "articles"."agency_id" WHERE "agency"."country_id" = 1
Update 2
My mistake in the Update section above is that I did not pluralize agency in the where clause. The part where(agency: {country_id: 1}) should have read where(agencies: {country_id: 1}). The pluralized word agencies here refers to the name of the table that is being joined.
You are very close, you just need to use a nested hash instead of an array.
Try this
def self.filter_by_country(parameter)
if parameter == "default"
joins(:media).where(media: { country_id: 1 })
else
joins(:media).where(media: { country_id: parameter })
end
end