Ruby on Rails: Anything selected with 'joins' is readonly - ruby-on-rails-3

So we're having some difficulty with the readonly? method in activerecord classes. It appears that anything fetched with a call to joins is being set to readonly and throws this error
ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
For instance:
# fine
User.first.readonly?
User.first.save
# not fine
User.joins(:memberships).first.readonly?
# false
User.joins(:memberships).first.save
# ActiveRecord::ReadOnlyRecord
According to the Rails documentation this will be set to false for "Records loaded through joins with piggy-back attributes."
As far as I'm aware, we're not including any piggy-back attributes here.
I can forcibly circumvent this restriction, but it feels like I'm hacking through the system unnecessarily.
user = User.joins(:memberships).first
User.instance_variable_set(:#readonly, false)
user.save
Does anyone know why joins is returning readonly items? Can I prevent this? (I'm not trying to select any attributes from related objects).
I'm using Ruby on Rails version 3.2.22.5, It looks like this has been a change in behavior from an earlier version of Rails 3.

Try using includes instead of joins, it's better and faster.
User.includes(:memberships).first

Related

Implementing a "soft delete" system using sqlalchemy

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.

use activerecord to retrieve virtual attributes when using select() with includes() and where()

I'm having trouble retrieving virtual attributes when making database queries. The following works as expected:
s = Story.includes(:scenes).select("stories.*, 3 as testval")
s.first.title
=> "My Story"
s.first.testval
=> 3
But when I put in a where clause, it stops working:
s = Story.includes(:scenes).select("stories.*, 3 as testval").where("scenes.id < ?",1000).references(:scenes)
s.first.title
=> "My Story"
s.first.testval
NoMethodError: undefined method `testval' for #<Story:0x007fcd6b93eb68>
I'm guessing the issue is that ActiveRecord doesn't know that 'testval' should belong to 'stories' instead of 'scenes', but I'm not sure. Does anyone know how to resolve this?
Sorry to be the bearer of bad news but you can't do a custom select and eager load as references/eager_load overrides the select with a bunch of t%d_r%d.
You'll need to do approach with a different ActiveRecord strategy if you need virtual attributes.

Rails 3: Excluding Results by Default

On my site, moderators can flag spammy comments. When these comments are flagged, they get quarantined so they no longer appear in regular views, though they can still be seen in the administrative control panel. At the moment, I exclude them from regular views like so:
#comments = Comment.where(:flagged => false)
I do this in every controller that has comments in it, of which there are many. I get the feeling that there's a cleaner way to handle this in Rails. Perhaps somewhere in the comments model I can specify that when querying for comments, only retrieve those that aren't flagged. If so, how is that done? And even if that's not possible, is there some other way to dry this code?
u can use a default scope
default_scope where(:flagged => false)
see http://apidock.com/rails/ActiveRecord/Base/default_scope/class
the default scope can be ignored using unscoped. See http://apidock.com/rails/ActiveRecord/Base/unscoped/class
i would prefer using a scope rather a default scope since i dont have to override it when all the records are needed. Depends upon the frequency of fetching all/unflagged records.
Make a scope (named 'clean' for this example):
class Comment < ActiveRecord
scope :clean, where(:flagged => false)
end
Then use:
#comments = Comment.clean
For future-proofing, you may may want to add a class method called default_view which just calls clean and use that instead. As your 'default' needs change, just modify the default_view method.

rails 3 default_scope(:where) and find

Find doesn't descope the default_scope anymore, what should I do now? I need to find entries that are out of the default scope on so many places and I also need the scoped arrays of entries for so many lists in my application.
Why did they changed it? :(
Take a look at this article as what has been deprecated in Rails3 here.
So if you want to use the model without the default_scope on it, then you can use the following as in the snippet below.(This is extracted from the article I mentioned)
with_scope and with_exclusive_scope
with_scope and with_exclusive_scope are now implemented on top of Relation as well. Making it possible to use any relation with them :
with_scope(where(:name => 'lifo')) do
...
end
Or even use a named scope :
with_exclusive_scope(Item.red) do
...
end

How to sanitize sql fragment in Rails

I have to sanitize a part of sql query. I can do something like this:
class << ActiveRecord::Base
public :sanitize_sql
end
str = ActiveRecord::Base.sanitize_sql(["AND column1 = ?", "two's"], '')
But it is not safe because I expose protected method. What is a better way to do it?
You can just use:
ActiveRecord::Base::sanitize_sql(string)
ActiveRecord::Base.connection.quote does the trick in Rails 3.x
This question does not specify that the answer has to come from ActiveRecord nor does it specify for which version of Rails it should be. For that reason (and because it is one of the top and few) answers on how to sanitize parameters in Rails...
Here a solution that works with Rails 4:
In ActiveRecord::Sanitization::ClassMethods you have sanitize_sql_for_conditions and its two other aliases:
 sanitize_conditions and sanitize_sql. The three do literally the exact same thing.
sanitize_sql_for_conditions
Accepts an array, hash, or string of SQL conditions and sanitizes
them into a valid SQL fragment for a WHERE clause.
Also in ActiveRecord you have
sanitize_sql_for_assignment which
Accepts an array, hash, or string of SQL conditions and sanitizes them
into a valid SQL fragment for a SET clause.
The methods above are included in ActiveRecord::Base by default and therefore are included in any ActiveRecord model.
See docs
Also, however, in ActionController you have ActionController::Parameters which allows you to
choose which attributes should be whitelisted for mass updating and
thus prevent accidentally exposing that which shouldn't be exposed.
Provides two methods for this purpose: require and permit.
   
params = ActionController::Parameters.new(user: { name: 'Bryan', age: 21 })
req  = params.require(:user) # will throw exception if user not present
opt  = params.permit(:name)  # name parameter is optional, returns nil if not present
user = params.require(:user).permit(:name, :age) # user hash is required while `name` and `age` keys are optional
The "Parameters magic" is called Strong Parameters (docs here) and you can use that to sanitize parameters in a controller before sending it to a model.
The methods above are included by default in ActionController::Base and therefore are included in any Rails controller.
I hope that helps anyone, if only to learn and demystify Rails! :)
As of rails 5 the recomended way is to use: ActiveRecord::Base.connection.quote(string)
as stated here: https://github.com/rails/rails/issues/28947
ActiveRecord::Base::sanitize(string) is deprecated
Note that when it comes to sanitizing SQL WHERE conditions, the best solution was sanitize_sql_hash_for_conditions, because it correctly handled NULL conditions (e.g. would generate IS NULL instead of = NULL if a nil attribute was passed).
For some reason, it was deprecated in Rails 5. So I rolled a future-proofed version, see here: https://stackoverflow.com/a/53948665/165673