Storing user_id in the paper_trail versions table - ruby-on-rails-3

I am using paper trail to audit changes to data and would like to store the user_id of the current user in addition to the "whodunnit" column that paper_trail stores by default.
I had no trouble modifying the versions migration to add the user_id column. But I haven't figured out an easy way to set that column from the various models in my app.
It seems like this should work:
has_paper_trail :meta => { :user_id => current_user.id
}
And, I think it might work if I had access to the current_user in my models. But I don't. After researching how to get access to the current_user in my model, I see there is a philosophical debate here. That's not my question though.
So I'm thinking of using a gem like sentient_user or sentient_model to give me access to the current_user in my models so I can set it with something like the code above.
However, adding these gems seems complicated for the little thing I'm trying to do here. I'm wondering if there is an easier way.
What is the easiest way to add the user_id of the person who took the action to the versions table?

The current_user don't exists in models by itself, it appears from controller. So, standard approach is applicable:
class ApplicationController < ActionController::Base
def user_for_paper_trail
current_user if user_signed_in?
end
def info_for_paper_trail
{ user_id: current_user.id } if user_signed_in?
end
end
# config/initializers/paper_trail.rb
class Version < ActiveRecord::Base
attr_accessible :user_id
end

Related

How does Devise hide some fields when output json

I am using Grape + Mongoid + Devise.
I found that the Devise user model have more fields (e.g. encrypted_password, sign_in_count, last_sign_in_at) than the user json output when I wrote API response.
I have searched in Devise code, didn't find anything like custom to_json, how does Devise achieve that?
I'm not sure on Grape, but on Rails you can do it with a serializer (as Grape has many code compatible with Rails, I think there's a big chance to work).
To use a serializer, you need to include the "active_model_serializers" gem.
Example:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :username
end
On this example, Devise will always print only these 3 fields on a JSON output.
To include all attributes except some of them, you can do something like this:
class UserSerializer < ActiveModel::Serializer
attributes(*(User.attribute_names - ["date_created", "first_name"] ).map(&:to_sym))
end
Also, at least on Rails, you'll want to remove the root from the output. To do this, add this code to your application_controller.rb:
def default_serializer_options
{root: false}
end

What's the best practice to save multiple foreign keys in "create"?

I'm building a crud with nested resource.
Post has_many :comments and my comments belongs_to :user and belongs_to :post. When I am adding a new comment, I am currently doing something like this in the create action of the comment controller:
#post = Post.where(id: params[:post_id]).first
#post_comments = #post.post_comments.build
#post_comments.update_attributes(params[:post_comment])
#post_comments.user = current_user
if #post_comments.save
...
I also saw this post: https://stackoverflow.com/a/5978113 which seems to do what I am doing.
This seems choppy, and I'm not sure if I am doing this correctly. Is there a better way? What is the best practice?
I don't know of any defined best practice for this but with your code, you don't need the call to update_attributes. There are 2 ways to save both foreign keys (actually 4 ways, if you're goining to build the comment from the user)
First option:
params[:post_comment].merge!(user_id: current_user.id)
#post = Post.where(id: params[:post_id]).first
#post_comment = #post.post_comments.build(params[:post_comment])
if #post_comment.save
...
else
...
end
Second option:
#post = Post.where(id: params[:post_id]).first
#post_comment = #post.post_comments.build(params[:post_comment])
#post_comment.user = current_user
if #post_comment.save
...
else
...
end
Just a note though, you should use the singular form if you're dealing with a singular resource so #post_comments should be #post_comment

Rails nested resource join security best practices

I've started a new project which requires accounts to be able to view their candidates and related tasks.
account.rb
has_many :candidates
candidate.rb
has_many :tasks
belongs_to :account
task.rb
belongs_to :candidate
Routes are set up to allow /candidates/4/tasks/3 where account X has access to candidate 4 which has task 3.
tasks_controller.rb is currently like:
def show
#task = Task.find params[:id]
end
QUESTION: What is the best practice approach to ensure that other accounts don't have access to task 3?
One idea might be something like this but seems very messy:
def show
#task = Account.find(current_account).candidates.find(params[:candidate_id]).tasks.find(params[:id)
end
So if the join fails, you don't have access.
Another way might be done using scopes. Where you make sure all tasks queried are joined with candidates and current_account.
I could also do a before_filter to do a standalone query on candidates table to check that the account has access. This will add an extra query so not ideal.
I'm waffling here... but would love to know how others go about this?
Do it by rails way...
current_account = Account.find params[:id]
#tasks = current_account.candidates.collect(&:tasks)
This will do your job.

Rails: How do I transactionally add a has_many association to an existing model?

Let's imagine I run an imaginary art store with a couple models (and by models I'm referring to the Rails term not the arts term as in nude models) that looks something like this:
class Artwork < ActiveRecord::Base
belongs_to :purchase
belongs_to :artist
end
class Purchase < ActiveRecord::Base
has_many :artworks
belongs_to :customer
end
The Artwork is created and sometime later it is included in a Purchase. In my create or update controller method for Purchase I would like to associate the new Purchase with the existing Artwork.
If the Artwork did not exist I could do #purchase.artworks.build or #purchase.artworks.create, but these both assume that I'm creating a new Artwork which I am not. I could add the existing artwork with something like this:
params[:artwork_ids].each do |artwork|
#purchase.artworks << Artwork.find(artwork)
end
However, this isn't transactional. The database is updated immediately. (Unless of course I'm in the create controller in which case I think it may be done "transactionally" since the #purchase doesn't exist until I call save, but that doesn't help me for update.) There is also the #purchase.artwork_ids= method, but that is immediate as well.
I think something like this will work for the update action, but it is very inelegant.
#purchase = Purchase.find(params[:id])
result = #purchase.transaction do
#purchase.update_attributes(params[:purchase])
params[:artwork_ids].each do |artwork|
artwork.purchase = #purchase
artwork.save!
end
end
This would be followed by the conventional:
if result
redirect_to purchase_url(#purchase), notice: 'Purchase was successfully updated.' }
else
render action: "edit"
end
What I'm looking for is something like the way it would work from the other direction where I could just put accepts_nested_attributes_for in my model and then call result = #artwork.save and everything works like magic.
I have figured out a way to do what I want which fairly elegant. I needed to make updates to each part of my Product MVC.
Model:
attr_accessible: artwork_ids
I had to add artwork_ids to attr_accessible since it wasn't included before.
View:
= check_box_tag "purchase[artwork_ids][]", artwork.id, artwork.purchase == #purchase
In my view I have an array for each artwork with a check_box_tag. I couldn't use check_box because of the gotcha where not checking the box would cause a hidden value of "true" to be sent instead of an artwork id. However, this leaves me with the problem of deleting all the artwork from a purchase. When doing update, if I uncheck each check box, then the params[:purchase] hash won't have an :artwork_ids entry.
Controller:
params[:purchase][:artwork_ids] ||= []
Adding this guarantees that the value is set, and will have the desired effect of removing all existing associations. However, this causes a pesky rspec failure
Purchase.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) fails because :update_attributes actually received {"these"=>"params", "artwork_ids"=>[]}). I tried setting a hidden_value_tag in the view instead, but couldn't get it to work. I think this nit is worthy of a new question.
It is probably best to use make the purchase model a join table and have many to many associations.
Here is an example for your use case.
Customer model
has_many :purchases
has_many :artwork, :through => :purchase
Artwork model
has_many :purchases
has_many :customers, :through => :purchase
Purchase model
belongs_to :customer
belongs_to :artwork
The purchase model should contain customer_id and artwork_id.
you would also need to create a purchase controller that allows you create a new purchase object.
When a customer presses the purchase button it would create a new purchase object which includes the customer_id and the artwork_id. This allows you to create an association between the customer and the artwork they purchase. You can also have a price_paid column to save the price the customer paid at the time of purchase.
if you need more help you can research join many to many associations using :through.
hope it helps

Modeling inheritance with Ruby/Rails ORMs

I'm trying to model this inheritance for a simple blog system
Blog has many Entries, but they may be different in their nature. I don't want to model the Blog table, my concern is about the entries:
simplest entry is an Article that has title and text
Quote, however, does not have a title and has short text
Media has a url and a comment...
etc...
What is a proper way to model this with Ruby on Rails? That is
Should I use ActiverRecord for this or switch to DataMapper?
I would like to avoid the "one big table" approach with lots of empty cells
When I split the data into Entry + PostData, QuoteData etc can I have belongs_to :entry in these Datas without having has_one ??? in the Entry class? That would be standard way to do it in sql and entry.post_data may be resolved by the entry_id in the postdata table.
EDIT: I don't want to model the Blog table, I can do that, my concern is about the entries and how would the inheritance be mapped to the table(s).
I've come across this data problem several times and have tried a few different strategies. I think the one I'm a biggest fan of, is the STI approach as mentioned by cicloon. Make sure you have a type column on your entry table.
class Blog < ActiveRecord::Base
# this is your generic association that would return all types of entries
has_many :entries
# you can also add other associations specific to each type.
# through STI, rails is aware that a media_entry is in fact an Entry
# and will do most of the work for you. These will automatically do what cicloon.
# did manually via his methods.
has_many :articles
has_many :quotes
has_many :media
end
class Entry < ActiveRecord::Base
end
class Article < Entry
has_one :article_data
end
class Quote < Entry
has_one :quote_data
end
class Media < Entry
has_one :media_data
end
class ArticleData < ActiveRecord::Base
belongs_to :article # smart enough to know this is actually an entry
end
class QuoteData < ActiveRecord::Base
belongs_to :quote
end
class MediaData < ActiveRecord::Base
belongs_to :media
end
The thing I like about this approach, is you can keep the generic Entry data in the entry model. Abstract out any of the sub-entry type data into their own data tables, and have a has_one association to them, resulting in no extra columns on your entries table. It also works very well for when you're doing your views:
app/views/articles/_article.html.erb
app/views/quotes/_quote.html.erb
app/views/media/_media.html.erb # may be medium here....
and from your views you can do either:
<%= render #blog.entries %> <!-- this will automatically render the appropriate view partial -->
or have more control:
<%= render #blog.quotes %>
<%= render #blog.articles %>
You can find a pretty generic way of generating forms as well, I usually render the generic entry fields in an entries/_form.html.erb partial. Inside that partial, I also have a
<%= form_for #entry do |f| %>
<%= render :partial => "#{f.object.class.name.tableize}/#{f.object.class.name.underscore}_form", :object => f %>
<% end %>
type render for the sub form data. The sub forms in turn can use accepts_nested_attributes_for + fields_for to get the data passed through properly.
The only pain I have with this approach, is how to handle the controllers and route helpers. Since each entry is of its own type, you'll either have to create custom controllers / routes for each type (you may want this...) or make a generic one. If you take the generic approach, two things to remember.
1) You can't set a :type field through update attributes, your controller will have to instantiate the appropriate Article.new to save it (you may use a factory here).
2) You'll have to use the becomes() method (#article.becomes(Entry)) to work with the entry as an Entry and not a subclass.
Hope this helps.
Warning, I've actually used Media as a model name in the past. In my case it resulted in a table called medias in rails 2.3.x however in rails 3, it wanted my model to be named Medium and my table media. You may have to add a custom Inflection on this naming, though I'm not sure.
You can handle this easily using ActiveRecord STI. It requires you to have a type field in your Entries table. This way you can define your models like this:
def Blog > ActiveRecord::Base
has_many :entries
def articles
entries.where('Type =', 'Article')
end
def quotes
entries.where('Type =', 'Quote')
end
def medias
entries.where('Type =', 'Media')
end
end
def Entry > ActiveRecord::Base
belongs_to :blog
end
def Article > Entry
end
def Quote > Entry
end
def Media > Entry
end