I have a simple model in RoR and I would like to keep eveything people enter on the site. But I also want to be able to hide some content if the user click on "Remove".
So I added a bolean attribute in my model called "displayed".
I would like to know, what would be the best-practices-styled method.
I guess I have to change the controller with something like :
def destroy
#point = Point.find(params[:id])
#point.displayed = false
#point.save
respond_to do |format|
format.html { redirect_to points_url }
format.json { head :no_content }
end
But I am not sure it is clean. What would be the best way to do it.
As you guess I am noobish with RoR. Chunks of code would be appreciated.
Thank you
Implement it yourself (rather than using a gem). It's much, much easier than it seems at first, and it's less complex than any of the gems out there that change the meaning of the destroy method, which is a bad idea, in my opinion.
I'm not saying that using the gems themselves are complex - I'm saying that by changing the meaning of the destroy method you're changing the meaning of something that people in the Rails world take for granted - that when you call destroy that record is going to go away and that destroy maybe also be called on dependent objects if they are chained together via dependent: destroy callbacks.
Changing the meaning of destroy is also bad because in the "convention over configuration" world, when you screw with conventions you're essentially breaking the "automagic-ness" of your Rails code. All that stuff you take for granted because you read a piece of Rails code and you know that certain assumptions generally apply - those go out the window. When you change those assumptions in ways that aren't obvious you're almost certain to introduce a bug down the line because of it.
Don't get me wrong, there's nothing better than actually reading the code for checking your assumptions, but it's also nice, as a community, to be able to talk about certain things and generally have their behavior act in a certain way.
Consider the following:
There's nothing in Rails that says you have to implement the destroy action in the controller, so don't. It's one of the standard actions, but it's not required.
Use the update action to set and clear an archived boolean attribute (or something similarly named)
I've used the acts_as_paranoid gem, and if you need to add any scopes to your models (other than the ones the gem provides) you're going to find yourself having to hack your way around it, turning off the default "hide archived records" scope, and when you run into that it almost immediately loses its value. Besides, that gem does almost nothing on its own, and its functionality could easily be written yourself (and I mean barely more work than installing the gem itself), so there's really no benefit to using it from that perspective.
As previously stated, overriding the destroy method or action is a bad idea because it breaks the Rails (and ActiveRecord) convention as to what it means to call destroy on an object. Any gem that does this (acts_as_paranoid for example) is also breaking that convention, and you're going to wind up confusing yourself or someone else because destroy simply won't mean what it's supposed to mean. This adds confusion, not clarity to your code. Don't do this - you'll pay for it later.
If you want to use a soft-delete gem because you are protecting against some theoretical, future developer who might hork up your data...well, the best solution to that is not to hire or work with those people. People that inexperienced need mentorship, not a gem to prevent them from making mistakes.
If you really, absolutely, must prevent destroying a record of a given model (in addition to being able to simply archive it), then use the before_destroy callback and simply return false, which will prevent it from being destroyed at all unless an explicit call to delete is used (which isn't the same as destroy anyway). Also, having the callback in place makes it (a) really obvious why destroy doesn't work without changing its meaning, and (b) it's easy to write a test to make sure it's not destroyable. This means in the future, if you should accidentally remove that callback or do something else that makes that model destroyable, then a test will fail, alerting you to the situation.
Something like this:
class Point < ActiveRecord::Base
def archive
update_attribute!(:displayed, false)
end
end
And then call #point.archive in the destroy action of your controller where you would normally call #point.destroy. You can also create a default_scope to hide archived points until you explicitly query for them, seethe RoR guide on appling a default scope.
Edit: Updated my answer as per normalocity & logan's comments below.
Look at the acts_as_archive gem. It will do soft deletes seamlessly.
Your solution is good, but you can use the acts_as_paranoid gem to manage that.
In this scenario, instead of adding a new boolean flag its better to added a deleted_at:datetime column
#point = Point.find(params[:id])
#point.touch(:deleted_at)
...
Then later
Point.where(deleted_at: nil) # these are NOT hidden
Point.where.not(deleted_at: nil) # these are hidden
Related
Although experienced with another frameworks, I'm a rails newbie. I've come upon the main twitter gem and want to use it. I understand the code... but I don't understand where exactly I should work it, at all (I've read plenty of rails but lack of practical examples).
I want to fetch the tweet info (post, user, etc)
Save the tweet to the database using postgre
I have a 'publication' (about the tweet) and 'publication' model, amongst helpers and so on.
Could someone please walk me through on how to do this? I'm not asking for you to do my work. Just please explain me the thought process of rails via a lazy example because I'm not understanding how to work with the gem this way... Thank you very much for your help :)
max's comment is exactly right, but to help put it into context, let me illustrate how, in your situation, you can build and apply a service object.
First, consider what your service object will be doing, and pick a name for it (you'll often change this later, as you figure things out better). For example, TweetFetcher. Then, decide what information it needs before it can do its job. I'm not clear on which tweet(s) you want to fetch, from your question, but let's assume it just wants to fetch the latest tweet for a given handle. Then, your object can start out like so:
class TweetFetcher
def initialize(handle)
#handle = handle
end
end
Now, this file can go anywhere Rails will automatically load it. The lib/ folder is pretty standard (e.g. lib/tweet_fetcher.rb), but you may need to add lib to your autoload paths. Even simpler is to throw it into the app/models folder, though that's a bit confusing for future developers.
Next, make it do its job. You'll need to add a new method to your class which "calls" it; a standard name is call but you can pick what you'd like. At this point, I'd suggest you write tests for your service object, just like you would for a model, but I won't get into details on that. At the end of the process, your code will look like:
class TweetFetcher
def initialize(handle)
#handle = handle
end
def call
# not real code at all
tweet = Twitter::Client.fetch_latest_tweet(#handle)
Publication.create!(tweet_id: tweet.id)
end
end
Rock-solid. So, the next question is, when to call it? In general, I'd suggest calling service objects from your controllers. For example, say this is all supposed to happen when a user submits a form that they just entered a twitter handle into... say, POST /publications, which hits PublicationsController#create:
class PublicationsController < ApplicationController
def create
# validate params, w/e
#publication = TweetFetcher.new(params[:handle]).call
flash[:notice] = "aaaand done."
end
end
However, you could call your service object from anywhere -- that's the beauty of building them. It allows you to extract, encapsulate, and re-use code in any context. It also makes it way easier to test your code, as I think you'll find. Hope this helped, and good luck!
I'm using Rails 4. I have a class, Cart, which needs to be accessed within my application.
I want it accessed using the factory pattern:
class CartFactory
def self.obtain_cart_for_user(user)
...
end
end
I need this approach because sometimes, I want to return an existing cart and sometimes create a new one (based upon the age of the cart, its contents, whether the products in it are still available etc).
This is easy enough.
However, I also want to make sure some other future programmer doesn't instantiate a cart directly, or fetch one by any other means, including via model associations, such as:
Cart.new(...)
user.carts.new(...)
Cart.find(id)
Cart.find_by_attribute(blah: blah)
Cart.where(...).first
Is there any way to prevent that?
Well, it's possible to make the constructor private:
private_class_method :new
And of course, you can try making the ActiveRecord query methods (.find, .where etc.) private as well. But to me that sounds like a good way to end up with erratic behaviour. If you were to go this route, make sure your app is thoroughly tested first.
Another route would be for Cart not to extend ActiveRecord::Base (which I'm assuming it does), and instead include only the parts you need, like ActiveRecord::Persistence. If you are willing to dive in deep, check out the parts that are included in the source for ActiveRecord::Base.
Edit: Still one option would be to make Cart itself private within a module that only exposes CartFactory. There's no built-in syntax for a "private class", but it's possible to achieve since Ruby classes are just regular objects. Again, no idea how well ActiveRecord would deal with that.
But lastly there is of course the question of whether you want to do this at all. In general, Ruby is not very good at protecting you from yourself. :) As expressed in the latter linked answer, documentation and trust go a long way.
I know the dogma says to not access current_user in a model but I don't fully agree with it. For example, I want to write a set of logging functions when an action happens via a rails callback. Or simply writing who wrote a change when an object can have multiple people write to it (not like a message which has a single owner). In many ways, I see current_user more as config for an application - in other words make this app respond to this user. I would rather have my logging via the model DSL rather than in the action where it seems REALLY out of place. What am I missing?
This idea seems rather inelegant Access current_user in model
as does this: http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
thx
edit #1
So my question isn't if there are gems that can do auditing / logging. I currently use paper_trail (although moving away from it because I can do same functionality in approx 10 lines of ruby code); it is more about whether current_user should never be accessed in the model - I essentially want to REDUCE my controller code and push down logic to models where it should be. Part of this might be due to the history of ActiveRecord which is essentially a wrapper around database tables for which RoR has added a lot of functionality over the years.
You've given several examples that you'd like to accomplish, I'll go through the solution to each one separately:
I want to write a set of logging functions when an action happens via
a rails callback
Depending on how you want to log (DB vs writing to the logger). If you want to log to the DB, you should have a separate logging model which is given the appropriate information from the controller, or simply with a belongs_to :user type setup. If you want to write to the logger, you should create a method in your application controller which you can call from your create and update methods (or whatever other actions you wanted to have a callback on.)
Or simply writing who wrote a change when an object can have multiple people write to it
class Foo < ActiveRecord::Base
belongs_to :user, as: :edited_by
end
class FooController < ApplicationController
def update
#foo = Foo.find(params[:id])
#foo.attributes = params[:foo]
#foo.edited_by = current_user
end
end
I think you're misunderstanding what the model in Rails does. Its scope is the database. The reason it can't access current_user, is because the current user is not stored in the database, it is a session variable. This has absolutely nothing to do with the model, as this is something that can not exist without a browser.
ActiveRecord::Base is not a class that is designed to work with the browser, it is something that works with the database and only the database. You are using the browser as an interface to that model, but that layer is what needs to access browser specific things such as session variables, as your model is extending a class that is literally incapable of doing so.
This is not a dogma or style choice. This is a fact of the limitations of the class your model is extending from. That means your options basically boil down to extending from something else, handling it in your controller layer, or passing it to the model from your controller layer. ActiveRecord will not do what you want in this case.
The two links you show (each showing imho the same approach) is very similar to a approach I still use. I store the current_user somewhere (indeed thread-context is the safest), and in an observer I can then create a kind of audit-log of all changes to the watched models, and still log the user.
This is imho a really clean approach.
An alternative method, which is more explicit, less clean but more MVC, is that you let the controller create the audit-log, effectively logging the actions of the users, and less the effects on different models. This might also be useful, and in one website we did both. In a controller you know the current-user, and you know the action, but it is more verbose.
I believe your concerns are that somehow this proposed solution is not good enough, or not MVC enough, or ... what?
Another related question: How to create a full Audit log in Rails for every table?
Also check out the audited gem, which solves this problem as well very cleanly.
Hope this helps.
I have this short and simple code for sending an email notification to the user when someone comments on his post. What I'm concerned about is the location of this snippet.
if user.settings.enabled_notifications && some_other_conditions
NotificationMailer.notify_topic_owner(comment,owner)
end
notify_topic_owner() just shoots a mail according to the parameters passed to it.
Basically, some_other_conditions contain some 3-4 conditions to be evaluated to true so as to send a mail. So clearly a controller isn't the right place for this code (I read somewhere that a controller code should be light and clean).
I dont think i can move this snippet to a helper as helpers contain code for views. Again, models dont look right either as the code is not really about the model (or is it?).
Do I make a new module for this short snippet? Going forward, I would really appreciate if you could also tell about the best practices or some reference for such dull confusions. I find myself struggling with this quite often!
You are asking the right questions. Why not go one step further and attempt to do some OOP :
(the code below is not ideal, but it should give you a good idea of how to approach it). I have not taken "some_other_conditions" into consideration because those are likely something you know best where it will fit into your domain logic.
# A class for notification. I usually avoid depending directly on xxxMailer and similar
class Notifier
# Inject the recipient
def initialize(recipient)
#recipient = recipient
end
def topic_commented(comment)
# Only let Notifier know that NotificationMailer exists. (not perfect OOP. could inject this too)
NotificationMailer.notify_topic_owner(comment,#recipient) if #recipient.notifications_enabled? # Ideally should be telling, not asking. Oh well.
end
end
class User
# Sprinkling of Law of Demeter
def notifications_enabled?
settings.enabled_notifications
end
end
You call Notifier.new(current_user).topic_commented("Hello World"). In future, the topic_commented can send SMS, smoke signals, print, write to database etc. all without you having to change the calling code lke NotificationMailer.xxxx in many places.
I don't see what would be wrong with putting this in a controller. If it's related to a method in your controller, it can definitely go there. If it's called after a save or something, you can probably move it to the model.
Generally I think the best practice is try to put as much stuff into models and classes as possible. Save the controller for controller specific code, and helpers should only contain code related to rendering content in views. A lot of times, I'll take code in my controller and move it to the model while refactoring. My opinion anyway :)
The convention I use to think about it is: "Should the mail be sent every time a comment is added, no matter by what action?". Think about whether if, in the future, you implemented an automated system that added comments, the mail should be sent in that case. If so, it's probably model code; otherwise, it's related to the way in which the comment was added, and it's controller code.
I have an application where I would like to override the behavior of destroy for many of my models. The use case is that users may have a legitimate need to delete a particular record, but actually deleting the row from the database would destroy referential integrity that affects other related models. For example, a user of the system may want to delete a customer with whom they no longer do business, but transactions with that customer need to be maintained.
It seems I have at least two options:
Duplicate data into the necessarily models effectively denormalizing my data model so that deleted records won't affect related data.
Override the "destroy" behavior of ActiveRecord to do something like set a flag indicating the user "deleted" the record and use this flag to hide the record.
Am I missing a better way?
Option 1 seems like a horrible idea to me, though I'd love to hear arguments to the contrary.
Option 2 seems somewhat Rails-ish but I'm wondering the best way to handle it. Should I create my own parent class that inherits from ActiveRecord::Base, override the destroy method there, then inherit from that class in the models where I want this behavior? Should I also override finder behavior so records marked as deleted aren't returned by default?
If I did this, how would I handle dynamic finders? What about named scopes?
If you're not actually interested in seeing those records again, but only care that the children still exist when the parent is destroyed, the job is simple: add :dependent => :nullify to the has_many call to set references to the parent to NULL automatically upon destruction, and teach the view to deal with that reference being missing. However, this only works if you're okay with not ever seeing the row again, i.e. viewing those transactions shows "[NO LONGER EXISTS]" under company name.
If you do want to see that data again, it sounds like what you want has nothing to do with actually destroying records, which means that you will never need to refer to them again. Hiding seems to be the way to go.
Instead of overriding destroy, since you're not actually destroying the record, it seems significantly simpler to put your behavior in a hide method that triggers a flag, as you suggested.
From there, whenever you want to list these records and only include visible records, one simple solution is to include a visible scope that doesn't include hidden records, and not include it when you want to find that specific, hidden record again. Another path is to use default_scope to hide hidden records and use Model.with_exclusive_scope { find(id) } to pull up a hidden record, but I'd recommend against it, since it could be a serious gotcha for an incoming developer, and fundamentally changes what Model.all returns to not at all reflect what the method call suggests.
I understand the desire to make the controllers look like they're doing things the Rails way, but when you're not really doing things the Rails way, it's best to be explicit about it, especially when it's really not that much of a pain to do so.
I wrote a plugin for this exact purpose, called paranoia. I "borrowed" the idea from acts_as_paranoid and basically re-wrote AAP using much less code.
When you call destroy on a record, it doesn't actually delete it. Instead, it will set a deleted_at column in your database to the current time.
The README on the GitHub page should be helpful for installation & usage. If it isn't, then let me know and I'll see if I can fix that for you.