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!
Related
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 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.
Requirements
I would like to keep an audit log of particular events, such as:
User logged in successfully
User could not log in (with reason: wrong password, unconfirmed, etc.)
SuperUser modified another users details (with what they changed)
etc.
This log should include details such as:
The logged in user performing the action (based on the controller's current_user)
The record being modified (such as the other user record in the case of super users)
The IP address of request for that action
This log should also be stored in a file on the filesystem, not a database table so it can be ingested by another service on the machine at a later time.
Some possibilities
Here is a short list of the approaches I have considered so far:
ActiveRecord::Observer
Observers give a nice separated way of watching for these particular events.
I could then get the observer to append to a log file, but I'm not sure how easy I would be able to obtain the result of such a call (such as login failed or worked) and I would need to somehow call the controller method current_user to find out the logged in user and get the HTTP request to obtain the IP address.
An auditing gem (such as audited, auditable, paper_trail, etc.)
These gems have the convenience of knowing how to get access to the controller for the current user and IP address, but they all log to an audit table in the database. Auditable is particularly nice because it can audit any method call on an object, not just an AR callback, but I may need to patch it to write to a file instead of the database.. or something?
ActiveSupport::Notifications
I still need to read up on this, but I believe it offers low-level a way of subscribing to low-level events within rails. This might be too low-level for this situation, but I need to investigate further.
Log4r
It seems this will make a nice log file, but I don't think it has any way of watching for events. This would only be part of the problem.
Any advice?
Is there a best practices way of doing this? Can you recommend any gems or lessons learned from previous experience? Anything else I should consider?
Thanks to everyone for the responses.
Casper, I did decide to build something custom.
I see your point with writing to a local db anyway, but the requirement for this project is to dump log files so a more elaborate log parsing service can interrogate the files and even combine them with information from other sources.
In order to get logging from both models and controllers, I ended up making a module that I would include in both the observers and ApplicationController.
The module looks a little something like this:
module MyEventLogger
mattr_accessor :logged_current_user
mattr_accessor :logged_remote_ip
def log_event(message)
##logger ||= Logger.new(Rails.root.join('log', 'audit.log'))
##logger.info "#{Time.now} | #{logged_current_user}##{logged_remote_ip} | #{message}"
end
def logged_current_user
##logged_current_user || "SYSTEM"
end
def logged_remote_ip
##logged_remote_ip || "NO IP ADDRESS"
end
end
ApplicationController would have:
include MyEventLogger
before_filter :setup_logger
...
def setup_logger
MyEventLogger.logged_current_user = current_user
MyEventLogger.logged_ip_address = request.remote_ip
end
The observer would just have to include MyEventLogger and it would have access to the log_event method and the current user and ip address. For example:
class UserObserver < ActiveRecord::Observer
include MyEventLogger
def after_save(user)
log_event "The User #{user} was saved by #{logged_current_user}"
end
end
Some thoughts:
The auditing gems sound closest to what you want. If you look at their source code they are not that complex, and most seem to actually be built around Rails Observers. You could easily use their code as a base to create your own specialized versions.
If you look at he code of Audited for example you will see it's actually quite simple to store the current_user (if you decide to roll your own implementation):
https://github.com/collectiveidea/audited/blob/master/lib/audited/sweeper.rb
I don't think having the auditing data in a DB is necessarily bad. You might actually find that useful one day when you need to track down some complex problem from thousands of previous transactions. You can always create a simple Rake task to dump the data into a logfile format when needed.
However I will say that Log4r is quite nice, and I've used it myself in a couple of projects. But I did not have an audit-type need. Only basic logging for debugging and troubleshooting.
You COULD also consider combining something like your own Observer-type system with a Log4r driver instead of a DB driver, if that's what you want. Because it does sound a little bit like you might need to trigger logging events outside of what the Observer system provides anyway. Which means you're going to have to either implement extensions to existing gems, or use a gem as a base and extend it with your own functionality.
Anyway - I still see the DB approach as actually a benefit, and kind of cool to be able to run queries on your audit-trail. Stuff like that can't hurt (Log4r btw. supports custom "output drivers" also, so even that could be used to log into a DB).
Just sharing my personal experience with this:
I created something very similar to what you mention here. I used a table in the database, trapped the relevant post request in the application_controller, and passed the info to the model associated with my log table. The logic was quite trivial, and I had all the control I wanted. The only effort was in selecting/ rejecting particular transactions, and restructuring all useful params to fit nicely in a text field.
If you decide to take that route, I will be happy to share more details.
Good luck.
One way of doing that, is wherever you want to run a logged action, run it in a block
I really recommend looking at Chapter 18 of Eloquent Ruby by Russ Olsen http://books.google.com/books?id=-s2xL0pVsLUC&lpg=PA219&ots=l7I3oAK3M2&dq=eloquent%20ruby%20chapter%2018&pg=PA219#v=onepage&q&f=false and maybe 'Using Blocks' in Chapter 2 of Gregory Brown's Ruby Best Practices http://majesticseacreature.com/rbp-book/pdfs/rbp_1-0.pdf
e.g.
def with_logging(description)
begin
#logger.debug( "Starting #{description}" )
yield # this is when the code in the block executes
#logger.debug( "Completed #{description}" )
rescue
#logger.error( "#{description} failed!!")
raise
end
end
with_logging('code example') { puts "just printing something" }
Also: It might be worth looking into tools like graylog http://graylog2.org/about/gelf (and see http://arrrrcamp.be/videos/2011/lennart-koopmann---managing-the-logs-of-your-rails-applications/ ) or these posts http://openmymind.net/2012/4/4/You-Really-Should-Log-Client-Side-Error/ (javascript), https://github.com/TwP/logging , http://amon.cx/