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.
Related
I'm in the process of updating a website I made almost 2 years ago. It was my first real website and I made some mistakes (some more serious that others).
What apparently is one of my biggest is making database calls from the view.
I'm pretty damn sure there is a better way to do this:
Use Case:
Someone fills out a form for a new subject, populating the Subject table, and they have been marked "enrolled", Subject.enrolled = 1
Based on that, I now need to create a record in 5 other tables (such as Baseline)
Downhill from here, here is my method
Determine if the record exist based on subject_id from Subject (sub)
<$ if Baseline.where(subject_id: sub.subject_id).first != nil $>
If it does not exist, create the record, (otherwise display the link)
<%= Baseline.create(subject_id: sub.subject_id) %>
This all happens in the view, and creates a front-end table with links to each record in the process. So I'm creating records based on for-loop logic...
Question:
So I'm looking for direction. I don't want to guess how to do this - I'm pretty sure the model/controller should do this - I want to learn how to do it correctly. How do I create records automatically, based on a value in a table?
Thank you for your time.
Not quite sure how your domain and code looks like, but to answer this question: 'How do I create records automatically, based on a value in a table?', it seems that you could use ActiveRecord callbacks, like this:
class Subject < ActiveRecord::Base
after_commit :create_baseline_if_enrolled, on: [:create, :update]
private
def create_baseline_if_enrolled
return unless enrolled?
# enrolled? == true, you may create these models here
end
end
To answer your question:
It depends :) This is just one possible solution. Another one would be to put such a custom logic in your SubjectsController and call it directly from the #create, #update methods. Both approaches have pros and cons. For example, abusing callbacks (anywhere) makes code less readable and harder to debug. On the other hand, putting such logic in controllers puts a burden on you that you have to remember about calling it if you happen to be editing subjects in other places (but is more explicit). Whichever way you choose, remember not to make your classes too fat, for example try to use service object pattern to separate such custom logic as soon as you feel like it is getting out of hand. :) And don't forget about tests - when things go wrong, tests make refactoring easier.
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!
In case you were unaware, this is a beginner's question.
Having learned a bit of Ruby, I have ventured onto Rails, but have run into a brick wall when it comes to organizing methods. I'm building something which is supposed to take the data/parameters you get when someone creates something, but instead of showing it like you would a tweet or blog post, I want to use them as variables in some mathematical calculations, the results of which I then want to show.
Now, in Ruby, I would make a method for each little math operation, to make sure they (the methods) have a single responsibility each. In Rails, though, what am I supposed to do? In case you don't understand my problem, I think it has to do with my lack of understanding of instances in Rails. Instances in Ruby can call upon the methods I make, but where do I call upon my methods (actions?) in Rails?
You should follow the MVC paradigm:
The controller should receive the parameters that the user gave via some html form.
Then that controller should instantiate an object that is doing the math calculation and then the controller should render a view to present the results to the user.
The place where the controller and the views are stored is already decided by the framework:
controllers are in app/controllers and the views in app/views/<controller_name>
Now the question is where you put the class that performs the calculations.
You might think about the app/models folder, but that one is typically for the models that inherit from ActiveRecord::Base, ie, all those that are persisted in the database.
Normally the kind of class that you are implementing lives in the lib folder.
For example, you might have the following structure:
app/controllers/calculations_controller.rb
def perform_calculations
math_calculator = MathCalculator.new params[:operation]
#result = math_calculator.calculate
end
lib/math_calculator.rb
class MathCalculator
def calculate
# whatever you need to do here
end
end
app/views/calculations/perform_calculations.html.erb
<%= #result %>
There's nothing official, I think its helping you
http://www.caliban.org/ruby/rubyguide.shtml
I'm working on an app at work. Basic stuff, user signs up (with an associated organization).
Initially I started off with a simple controller -
# Need to check if organization exists already; deny user creation if it does
if #organization.save
#user.save
redirect_to user_dashboard_path...
I soon found myself in a callback soup:
After the organization is validated, we save the user.
When the organization is created, I create another two models, EmailTemplate and PassTemplate (an organization has_one :email_template, has_one :pass_template)
after_create :init_company, :init_email_template, :init_pass_template, :init_form
Each of those callbacks generally calls method on the model, something like:
def init_email_template
self.email_template.create_with_defaults
end
Initially I thought this was quite clever - doing so much behind the scenes, but I've been reading Code Complete by Steve McConnell, and feel this is not simple at all. If I didn't know what was happening already, There's no hint that any time an organization is created it creates 3 associated objects (and some of those objects in turn initialize children objects).
It seems like a bad programming practice, as it obfuscates what's going on.
I thought about moving all of those initalizations to the controller, as an organization is only ever created once:
class OrganizationsController < AC
...
def create
if #organization.save
#organization.create_user
#organization.create_email_template
#organization.create_pass_template
end
That seems like cleaner code, and much easier to follow.
Question 1
*Are there better solutions, or best practices for handling creating associated objects upon creation of the hub object that I'm unaware of?*
Side note - I would have to rewrite a bunch of tests that assume that associations are automatically created via callbacks - I'm okay with that if it's better, easier to understand code.
Question 2
**What about a similar situation with after_save callbacks?**
I have a customer model that checks to see if it has an associated user_account after creation, and if not, creates it. It also creates a Tag model for that user_account once we've created the user_account
class Customer < AR
after_create :find_or_create_user_account
def find_or_create_user_account
if !self.user_account_exists?
#create the user
end
Tag.create(:user_id => self.user_account.id)
end
end
Somewhat simplified, but again, I believe it's not particularly good programming. For one, I'm putting logic to create two different models in a third model. Seems sloppy and again the principle of separating logic. Secondly, the method name does not fully describe what it's doing. Perhaps find_or_create_user_account_and_tag would be a better name, but it also goes against the principle of having the method do one thing- keeping it simple.
After reading about observers and services, my world was thrown for a bit of a loop.
A few months ago I put everything in controllers. It was impossible to test well (which was fine because I didn't test). Now I have skinny controllers, but my models are obese and, I think, unhealthy (not clear, not obvious, harder to read and decipher for another programmer/myself in a few months).
Overall I'm just wondering if there are some good guides, information, or best practices on separation of logic, avoiding callback soup, and where to different sorts of code
Why not the following?
after_create :init_associated_objects
def init_associated_objects
init_company
init_email_template
init_pass_template
init_form
end
My interpretation with "a method should do one thing" isn't strict and that I usually have a method that calls other method (much like the one above). At the end of the day, it's a divide and conquer strategy.
Sometimes I create utility POROs (plain old ruby objects) when it doesn't make sense to have an AR model but a group of functionalities is a class' responsibility. Reports, for instance, are not AR-backed models but it's easier when a report that needs to call multiple models is just instantiated once where the reporting period start and end are instance variables.
A rule of thumb that I follow: if I instantiate the models outside of the whole MVC stack (e.g. Rails console), the things that I expect to happen should stay inside the model.
I don't claim best practices but these have worked for me so far. I'm sure other people would have a better idea on this.
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.