Being as DRY as possible in a Ruby on Rails App - ruby-on-rails

I'm currently using the awesome attachment-fu plugin for a Rails app, but as a novice developer, I've never encountered a scenario like the one I've found myself in.
Essentially, I'm using the attachment-fu plugin on two levels.
Is for user avatars in the user class.
Is to allow file attachments (PDFs, etc) in a messaging system.
My question is what the best use practice would be in these situations to remain DRY, clear, and consistent.
Clearly it would make no sense to define and execute the plugin in both classes, but there's something deeply strange to me (possibly unfounded) about just going ahead and setting it all up in the godly Application class.
Is there something in between, or is the parent class the way to go?
Thanks!

What's the DRY issue with defining the attachment_fu settings twice?
Unless the files are of the same type and being stored in the same place, you're not going to be repeating anything in the configuration.
Sure, you'll have two has_attachment declarations, but the options will mostly differ (one declaration for your avatars and the other for your pdf's etc.
99.99% of the code to handle attachment will be buried in the attachment_fu libs, your configuration code should be pretty DRY by default =)

Is "outsourcing" avatar support entirely to Gravatar an option? There are some Rails plugins that will display avatars hosted by Gravatar. You might not need to re-invent the wheel there.

What wfarr is describing would be single table inheritance, which is what I currently do in this situation. I have one table for Assets which contains all the necessary attachment_fu columns, plus an extra column called type, which will hold the actual model name. I have a model for assets and additional models for specific upload types that inherit from assets:
asset.rb:
class Asset < ActiveRecord::Base
... attachment_fu logic ...
end
avatar.rb:
class Avatar < Asset
... avatar specific attachment_fu logic ...
end
pdf.rb:
class PDF < Asset
... PDF specific attachment_fu logic ...
end

I would lean towards using a parent class, with subclassing for the different ways you intend to actually use the attachments in your application. It may not be the DRYest solution available, however, it lends itself to a logical pattern rather well.

Couldn't you use Polymorphic Associations?
I'm about to hit this in my app with attachment_fu, so I'm not exactly sure on attachment_fu, but for the old school File Column plugin, I would use Polymorphic Associations.
My "file" model would be:
class FileUpload < ActiveRecord::Base
belongs_to :fileable, :polymorphic => true
file_column :name
end
and then any models that needed a file attachment would be like:
class Company < ActiveRecord::Base
has_many :file_uploads, :as => :fileable
end
File Column is no good anymore as it borks on Safari 3.x and is no longer maintained. It was nice and simple though... Ah, the good old days...

For what it's worth, I think Patrick Berkeley has done a good job with handling multiple attachments through the Paperclip plugin. He's outlined his work here:
http://gist.github.com/33011

Related

Why Rails 5 uses ApplicationRecord instead of ActiveRecord::Base?

We know that Rails 5 added ApplicationRecord as an abstract class which was inherited by our models (ActiveRecord).
But basically, I think every technical requirement we do with ApplicationRecord, we can also do with ActiveRecord::Base. For instance:
module MyFeatures
def do_something
puts "Doing something"
end
end
class ApplicationRecord < ActiveRecord::Base
include MyFeatures
self.abstract_class = true
end
So now every model will be attached the behaviors of MyFeatures. But we can also achieve this in Rails 4:
ActiveRecord::Base.include(MyFeatures)
So what is the benefit of using ApplicationRecord, do you think it is necessary to add ApplicationRecord?
While it may seem the same in basic Rails applications, there actually is an important difference once you begin to use rails engines, plugins / gems or direct methods from ActiveRecord::Base.
ActiveRecord::Base.include(MyFeatures) mixes in the features directly into ActiveRecord::Base and it is present there forever for all later uses of ActiveRecord::Base (it cannot be "unmixed") and there is no way to get the original ActiveRecord::Base anymore in any code after the include. This can easily lead to problems if some of the mixed in feature changed the default ActiveRecord behavior or if e.g. two engines / gems tried to include same-named methods.
On the other hand, the ApplicationRecord approach makes the features present only for the classes (models) that inherit from it, other classes, as well as direct use of ActiveRecord::Base stay pristine, uncluttered by the module features.
This is especially important when engines or rails plugins are used as it allows them to separate their own model logic from the main application's model logic which was not possible before ApplicationRecord.
All of this is also nicely described in this blog post and this github comment.
This is to expand on #BoraMa's answer, and to, hopefully, clear up some confusion around ActiveRecord::Base.abstract_class.
ActiveRecord::Base.abstract_class goes back to at least Rails 3.2.0 (http://api.rubyonrails.org/v3.2.0/classes/ActiveRecord/Inheritance/ClassMethods.html), which was released on January 20, 2012.
Rails 4.0.0 improved the documentation: http://api.rubyonrails.org/v4.0.0/classes/ActiveRecord/Inheritance/ClassMethods.html
So, to everyone who thinks ApplicationRecord is radically new, it's not. It is an improvement, not a breaking change. Nothing was added to ActiveRecord::Base to make this work.
I did the same thing on a Rails 4.2.6 project because the models used UUIDs for ids instead of integers, and this required a change to the default ORDER BY. So, instead of using copy-paste or a concern, I went with inheritance using a UuidModel class and self.abstract_class = true.

Better Rails file structure for big projects?

I always seem to get stuck with 40 different models in the same folder namespaced like post.rb, post_comment.rb, post_rating.rb etc etc. Is there any way to make something like modules in rails, namespacing away everything with posts to a separate directory? (This directory could include the different controllers and views too).
Rails Engines seems promising but maybe there's something else that I've missed?
I think it would make the project easier to overview and to enter as a new collaborator.
If you have opinions against, please tell!
I use namespaces.
When you run command rails g scaffold blog/post, it'll generate Post under Blog namespace.
app/models/blog.rb
module Blog
def self.table_name_prefix
'blog_'
end
end
app/models/blog/post.rb
class Blog::Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
app/models/blog/category.rb
class Blog::Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
It seems your modelling has some problem.
A comment is a comment. A post can have comment, a photo can have comment. But it's definitely not good practice to have models like post_comment, photo_comment. Well, I only see "post_comment" in question, but I guess you name it for such reason.
You'll be busy to follow and work for these models with similar functionalities, and then their controller, their views. Same is true to "post_rating" etc.
I would not say 40+ models is too much. But seeing new Basecamp has 50+ models and Dispora has 30+ models without sub folders, it may worth a review on your app's architecture to cut some.
If you plan to use "comment", "rating" on others such as "photo", you can use the name directly, and plan associations, polymorphic, modules to DRY the code.
As an example you can place all of your post_*.rb models into a posts folder. Make sure to rename each model within the folder with Posts::Post* ie class Posts::PostRating < ActiveRecord::Base
Rather than update all the models' Class references in your codebase, in my opinion it is likely just easier to leave them all in the models directory and deal with the agony of having a bloated models directory.
Relevant Readings:
ActiveRecord: Can haz namespaces?
Namespaced models

How to create a model in rails

I am using the parseresource gem and it says I need to create a model.
Create a model:
class Post < ParseResource
fields :title, :author, :body
validates_presence_of :title
end
I only know how to generate a mode and it always inherits ActiveRecord::Base. What should I type into my command line to create this model?
I think what you are looking for is:
rails generate model Post title:string author:string blob:text
Then change the inherits from ActiveRecord to inherits from ParseResource in the post.rb file created.
class Post < ActiveRecord::Base
becomes
class Post < ParseResource
I don't really have enough details about the model or ParseResource for a better answer.
I do hope that helps though.
If you are new to Ruby and/or Rails I suggest running through an introduction to rails.
http://guides.rubyonrails.org/getting_started.html
or
http://ruby.railstutorial.org/ruby-on-rails-tutorial-book
Cheers!
In this case it looks like you only need to create a single file in your /app/models directory named after the model you're trying to create.
The normal model generation code that comes with Rails and it's generators don't apply here since this gem is specifically intended to only serve as a 'wrapper' to the Parse.com API.
From the project's home page:
ParseResource makes it easy to interact with Parse.com's REST API. It adheres to the ActiveRecord pattern. ParceResource is fully ActiveModel compliant, meaning you can use validations and Rails forms.
It looks like all you do is create a single file descending from ParseResource and then interact with it as if it were a normal ActiveRecord model.
Looks pretty straightforward, though I'd caution that the author says right in the docs:
ParseResource is brand new. Test coverage is decent.
This is my first gem. Be afraid.
Be careful and make sure you report any issues you find through the projects issues page in Github.
Unless the parseresource gem includes a custom generator, I think you will have to generate a standard ActiveRecord model and then edit it to inherit from ParseResource.
As another option you could just create the model from scratch by creating a post.rb file under app/models and putting your model code in there (any decent text editor will work just fine). Remember there is nothing forcing you to use a generator, they are just there to make your life easier.
rails generate model followed by your model name, then the fields with respective datatype and size.
Below is one exhaustive example.
rails generate model MyModel some_id:integer{20} some_name:string{255}
some_text:text some_int:integer{1} some_deci:decimal{10,2}
You can also have other data types such as boolean, date, time, datetime, float, binary.

Rails: Paperclip question regarding column names

I have two scenarios for using Paperclip, but I'm unsure of how to tweak the settings or if it's possible or even necessary. Need the advice of more seasoned professionals on this one.
First up, I have a Document model for uploads such as PDFs, which would be defined:
has_attached_file :document...
This would give me column names like #document.document_file_name. Anyway that I could have #document.file_name instead?
Secondly, I have Gallery.rb which has many Picture.rb. Same scenario here as well. Can I avoid having #picture.picture_file_name? Or is this something that should really be overlooked with the gains that Paperclip affords.
Thanks in advance for any input.
My take on this: The actual document (PDF file) is not the same as a document record (which comprises the physical document plus metadata). Therefore it makes sense to see the Paperclip attachment as an attribute of the model and have its methods be called after the attribute, and not operate on the model record itself.
One of my apps has a Document model with an attached file too, and I simply called the attribute attachment.
If this is too much of an inconvenience for you, you could always implement your own getters in the model:
class Document < ActiveRecord::Base
has_attached_file :attachment # ... or whatever you are calling it
def file_name
self.attachment.file_name
end
def file_size
self.attachment.file_size
end
def file_type
self.attachment.file_type
end
end
The Paperclip gem requires three attributes on the associated object.
attribute_file_name
attribute_file_size
attribute_file_type
attribute of course if the name of your file and it is the has_attached_file :attribute filed commonly called picture, image, whatever.
If you want to change one of those names you will need to edit the gem itself which seems crazy for just changing the attribute name :)
Here are the methods I had to create:
{attribute}_file_name
{attribute}_file_size
{attribute}_content_type
{attribute}_updated_at

Resolving a class name conflict in a Rails application

I have been building a Rails application that performs accounting functionality. As part of this, I have a model with the class name Transaction. So far so good, I have been building this functionality for a month or so, and everything is working as expected.
Until now...
I have just discovered some older reporting functionality that was developed months ago using the Ruport library has stopped working. It appears that Ruport, when generating PDFs, requires a library that also has a class/module named Transaction.
TypeError in Admin/team reportsController#generate
Transaction is not a module
...
This error occurred while loading the following files:
pdf/writer
transaction/simple
So, I'm looking for a quick fix here. One that hopefully doesn't involve renaming my Transaction model and refactoring the last few weeks worth of code.
Looking forward to some clever suggestions :)
Already answered and old, but I came here with the same problem, but solved it in a different way.
I have two Models named Pull and Query.
Trying to reference Query.some_static_method() within a method in Pull resulted in Query resolving to ActiveRecord::AttributeMethods::Query:Module.
Solved it by putting the empty namespace in front of it with ::Query.some_static_method()
I believe the issue is down to Ruport requiring the PDF::Writer gem, which in turn requires the Transaction::Simple gem which defines the module Transaction.
There is certainly a #transaction method in ActiveRecord, but I do not think there is a Transaction module or class within Rails. I'll be happy to be corrected on that one.
Namespacing is usually the best practice for avoiding naming conflicts like this. E.g.
module Account
class Transaction < ActiveRecord::Base
....
end
end
However, namespacing ActiveRecord models may throw up other issues.
As time consuming as it may be, renaming your Transaction model may be the best bet.
You can still keep your existing transactions database table if you wanted, so your migrations don't need to change, by putting self.table_name = "transactions" inside your model.
Your associations with other models can also still be named "transaction(s)" by specifying the class_name in your association call. E.g.
class User < ActiveRecord::Base
has_many :transactions, :class_name => "AccountTransaction"
end
Those two suggestions may or may not save you some time.
Your problem may come from the fact that Transaction is also a reserved word in Rails…

Resources