I am a bit new to the MVC framework and need to know what is the best practice when working with one.
I have a model class called NewsFeed that contains field like news_title, news_publish_date, news_url.
class NewsFeed < ActiveRecord::Base
attr_accessible :description, :feed_id, :link, :publish_date, :title
def save_news_info(feed_id, news_title, news_link, news_publish_date, news_description)
self.feed_id = feed_id
self.title = news_title
self.link = news_link
self.publish_date = news_publish_date
self.description = news_description
end
end
The task is to read an rss feed and gather all the news it contains, so i made a class called FeedReader and in that class I am using a gem feedzirra to parse the feed link
class FeedReader
attr_accessor :title, :url, :publish_date, :news_array
def initialize(feed_url)
#url=feed_url
end
def read
feed = Feedzirra::Feed.fetch_and_parse(#url)
#title = feed.title
#url = feed.feed_url
#publish_date = feed.last_modified
end
end
My question is if this is a good practice to have a seperate class just like the model(NewsFeed) class and have a read function there, or should I declare the read function in my model class and delete FeedReader? (cauze I have been reading that putting too much functionality in model classes is frowned upon!!) and in future all the functionality (like sanitize news description, strip particular tags etc) gets coded in the model class which in turn grows bigger and bigger.
There's several opinion around that. Here's mine : you're doing the right thing. There's a thing called "principle of simple responsability", which is kind of a buzz word these days, but still has value : your objects should do one "thing", and do it well. So, having one class for handling the news feeds, and one that handles the retrieval of news, it makes complete sense to me.
Bonus point : it is (supposedly) easier to test.
The method should stay in FeedReader, but you might benefit from passing the NewsFeed instance (or a collection, which can handle a list of NewsFeed instances) to the method which extracts information from the feed (I am not sure if name read is so good in this context).
You mist keep in mind that there are both Atom and RSS feeds. And you should not weld your code to one of the formats. Instead you should be able to work with both (if required) by utilizing polymorphism.
As for SRP: actually the best explanation for it , that i have heard is following - class should have only one reason to change.
Related
I have spent a lot of thought on this situation and cannot figure out what the best modeling system is:
There is a Test. A test can have a variety of of TestItems. These TestItems can (currently) consist of TrueFalseQuestions, MultipleChoiceQuestions, ShortAnswerQuestions, and TestInfo.
All of the models will implement some sort of Printable module. They will all be printable, but each model handles its printing in a different way. All models will also have a position as they are sortable in relation to all other models. All models can belong to a test.
All models of type XXXQuestion will print numbers when they print. The TestInfo will not do that.
MultipleChoiceQuestions will have Answers as children.
I have tried creating a TestItem class that uses reverse polymorphism and a shareable question module:
class TestItem < ActiveRecord::Base
belongs_to :test
belong_to :item, polymorphic: true
db_fields: :main_text, :position, :item_id, :item_type
def sort(params)
...
end
end
module QuestionPrintable
def get_print_number
...
end
def print
raise NotImplementedError
end
end
module Question
def self.included(klass)
klass.class_eval do
include QuestionPrintable
has_one :test_item, as: :item, dependent: :destroy
delegate :test, :main_text to: :test_item
end
end
end
class MultipleChoiceQuestion < ActiveRecord::Base
include Question
has_many :answers
def print
number = get_print_number
...
end
end
This would work, except that some models (like TrueFalseQuestion) would not actually expand the TestItem class. They would have no extra information in the TrueFalseQuestions table, but they would implement methods unique to TrueFalseQuestions. I realize I could also wrap a TestItem in a TrueFalseQuestion wrapper whenever it's instantiated but then I would need to store the kind of the question on the TestItem to know when to do that. So, in some sense, the TrueFalseQuestion < ActiveRecord::Base class is actually storing the kind implicitly just by existing. I don't know if that is a valid use of ActiveRecord::Base.
All the questions do share the printing features of a number (and several behaviors I anticipate needing, just not quite yet) that are not shared with other types of TestItems (i.e. TestInfo). Additionally, some Question types will store extra data right now. And I believe that all of them will store more data as this problem evolves. So I do think that abstraction is helpful. Is it okay to have an table that more or less exists to allow the implementation of a polymorphic ActiveRecord model?
Also, having the text on the TestItem prevents a crazy amount of joins to display the main text of all items for a test.
The big difficulty, is if I do this a different way (for example not having a TestItem class and just a bunch of shared modules or storing these all as TestItems with a :kind attribute), I need to start switching behavior on the class type or an attribute, and I try to avoid any code that tests on class type or has so much behavior switch based on a attribute value.
I think in general those solutions can be achieved with duck typing, which would work with my empty ActiveRecord class, but this one just has me puzzled.
EDIT:
Another solution that occurred to me, that would prevent switching on kind would be to use some sort of kind value in the TestItem and use it to create a wrapper:
class TestItem < ActiveRecord::Base
belongs_to :test
attr_accessor :main_text, :position, :kind
def wrapped_object
klass = kind.constantize
klass.new(_needed_params)
end
end
class TrueFalseQuestion # DO NOT INHERIT
attr_accessor :kind, :position
def print
...
end
end
I left out the various modules to not distract from the general solution, those can be easily implemented.
So now my potential debate is:
Empty Database Tables
Positives:
No wrappers needed
More extendable in the future
Negatives:
It's an empty table....
Possible YAGNI
Method that returns wrapped object
Positives:
Solves the immediate problem without introducing extra database tables
Allows for all the same abstractions in the previous solution
Negatives:
Relies on the kind attribute (maybe not bad in this case?)
If the domain changes this could easily become too complex to maintain
The Law of Demeter seems to be a very powerful concept. I can understand how it helps writing good and maintainable object-oriented code.
Some people suggest to write a delegate method each time you need to access an attribute of an associated object in a view. Instead of writing something like this in a view
#order.customer.name
you would write this code:
# model
class Order < ActiveRecord::Base
belongs_to :customer
delegate :name, :to => :customer, :prefix => true
end
#view
#order.customer_name
On the other hand, people argue that you views should not dictate models and you should not add methods such as delegate to a model only for the sake of trading a dot for an underscore in a view.
When violating the Law of Demeter in a view, is it considered best practice to write delegate methods in models or not?
I see your customer_name auto-generated delegate method as the Simpliest Thing That Works Right now. Since it's one method call (and not a series of method chains) it's easy to refactor later (or, easier to refactor than some chained methods)
Imagine adding many customers to an order, one of which is the primary customer, for whatever reason. Now your order class might look like
class Order < ActiveRecord::Base
has_many :customers
def customer_name
if customers.first.primary?
customers.first.name
else
customers.last.name
end
end
It was easy to replace that convenience delegate generated method with one of our own.
(It's also super easy to write the first time, as delegate takes care of all the boilerplate. It's very possible you'll use customer_name in this form forever in your app. It's hard to know. But code that's easy/automatic to write the first time is cheap to throw away :))
Of course you have to avoid situations where you are writing method names like customer_streetaddress_is_united_states? (where yes, instead of encoding the object graph in dots you're encoding it in underscores.)
If your view really needs to know if the user is located in the US perhaps a method like this might work:
class Order < ActiveRecord::Base
belongs_to :customer
def shipping_to_us?
customer.shipping_country == "USA"
# Law of Demeter violation would be:
# customer.addresses.first.country == "USA"
end
end
class Customer < ActiveRecord::Base
has_many :addresses
def shipping_country
addresses.first.country
end
end
Notice here how the Order asks the Customer object for the shipping address, vs telling the customer to get it's customer's first address's country. Like a boss that tells you to do something and leaves you alone vs a boss that micromanages exactly how you do your day to day. (For additional edification, read up on the ask, don't tell approach to Ruby development :) )
There is something to be said about using presenters, decorator methods, or helpers to avoid having this potentially just display logic code littering your models. I'll leave that as an exercise for the reader :)
I have following complex method which I cut off from controller:
def self.create_with_company_and_employer(job_params)
company_attributes = job_params.delete(:company_attributes)
employer_attributes = job_params.delete(:employer_attributes)
new(job_params) do |job|
job.employer = Employer.find_or_create_by_email(employer_attributes)
company_attributes[:admin_id] = job.employer.id if Company.find_by_nip(company_attributes[:nip]).nil?
job.company = Company.find_or_create_by_nip(company_attributes)
Employment.create(employer_id: job.employer.id, company_id: job.company.id)
end
end
I using here two nested_attributes functionality for create company and employer.
Whole code you can find here: https://gist.github.com/2c3b52c35df763b6d9b4
company_attributes[:admin_id] = job.employer.id if Company.find_by_nip(company_attributes[:nip]).nil?
Employment.create(employer_id: job.employer.id, company_id: job.company.id)
Basically I would like to refactor that two lines:
I looked at your gist and i think this is a design issue.
your Employment and Job models seem somewhat redundant, but i don't know what are their actual purpose exactly so i can't really help for now on this matter (i have a hunch that your schema could be remodeled with the employements belonging to the jobs). However, if you really want to, you can use an after_create callback to manage the replication :
class Job < ActiveRecord::Base
after_create :create_corresponding_employment
def create_corresponding_employment
Employment.create( employer_id: employer.id, company_id: company.id )
end
end
this gets you rid of the last line of your method.
the other line you want to get rid of is tricky : you assign an admin_id to your company, but why would you want to do that ? In fact, you're just creating a 'hidden' relation between Company and Employer (a belongs_to one). Why do you need that ? Give more information and i can help.
one more thing: it is not advised to delete keys form the params, or even modify the hash directly. Use a copy.
I've got one model with about 50 columns of measurement data, each with a different unit of measurement (ie. grams, ounces, etc.). What is a good way to associate units of measurement with columns in my database? The primary use for this is simply for display purposes. (Ruby on Rails)
EDIT: To clarify, my model is an object, and the attributes are different measurements of that object. So, an example would be if I had the model Car and the attribute columns :power, :torque, :weight, :wheelbase, etc. I would want car.power.unit to return hp and car.weight.unit to return lbs., etc. This way, I would be able to do something like this:
<%= car.power + car.power.unit %>
and it would return
400hp
Updated Answer
Since you're storing many columns of data, but each column is only one type, and your concern is strictly presentational, I would just use a decorator to accomplish what you need. See this railscast for an example of a great way to do this using Draper.
Basically, a decorator wraps your model with presentation specific methods, so instead of:
#CarsController.rb
def show
#car = Car.find(params[:id])
end
You would use
#CarsController.rb
def show
#car = CarDecorator.find(params[:id])
end
You would define a decorator like so:
class CarDecorator < ApplicationDecorator
decorates :car
def horsepower
model.power.to_s + "hp" #call to_s just in case
end
end
Then in your view any time you called #car.horsepower you would get 123hp instead of 123. In this way you can build a big long reusable list of presentation methods. You can share methods between objects using inheritance, and you can allow methods from the original model to be called as well. See the railscast and the docs etc. You can use Draper or you could roll your own presenter class if you don't want to use a library.
Previous Answer (Abridged):
I can see two nice, easy ways to do this:
1) Just add a text column for units to your data model. IE: to get "400hp" use [data.value,data.units].join
2) You could get a little richer association by having a Units model, perhaps with help from something like ActiveEnum.
You could add a unit model with a for attribute, where you save the attribute in the messurement, you want to apply the unit to. Example:
def Unit < ActiveRecord::Base
scope :for, lambda{|messurement| find_by_for( messurement.to_s ) }
end
This allows you stuff like:
<%= #car.torque + Unit.for(:torque).symbol %>
I do not know if this is of so much advantage, but its a way to solve your problem...
I'm getting started with parsing data and getting some structure from user supplied strings (mostly pulling out digits and city names).
I've run a bit of code in the ruby interpreter, and now I want to use that same code in a web application.
I'm struggling as to where in the code my parsing should be, or how it is structured.
My initial instinct was that it belongs in the model, because it is data logic. For example, does the entry have an integer, does it have two integers, does it have a city name, etc. etc.
However, my model would need to inherit both ActiveRecord, and Parslet (for the parsing), and Ruby apparently doesn't allow multiple inheritance.
My current model is looking like this
#concert model
require 'parslet'
class concert < Parlset::Parser
attr_accessible :date, :time, :city_id, :band_id, :original_string
rule(:integer) {match('[0-9]').repeat(1)}
root(:integer)
end
Really not much there, but I think I'm stuck because I've got the structure wrong and don't know how to connect these two pieces.
I'm trying to store the original string, as well as components of the parsed data.
I think what you want is:
#concert model
require 'parslet'
class concert < ActiveRecord::Base
before_save :parse_fields
attr_accessible :date, :time, :city_id, :band_id, :original_string
rule(:integer) {match('[0-9]').repeat(1)}
root(:integer)
private
def parse_fields
date = Parlset::Parser.method_on_original_string_to_extract_date
time = Parlset::Parser.method_on_original_string_to_extract_time
city_id = Parlset::Parser.method_on_original_string_to_extract_city_id
band_id = Parlset::Parser.method_on_original_string_to_extract_band_id
end
end
It looks to me as though you need several parsers (one for city names, one for digits). I would suggest that you create an informal interface for such parsers, such as
class Parser
def parse(str) # returning result
end
end
Then you would create several Ruby classes that each do a parse task in ./lib.
Then in the model, you'd require all these ruby classes, and put them to the task, lets say in a before_save hook or such.
As the author of parslet, I might add that parsing digits or city names is probably not the sweet spot for parslet. Might want to consider regular expressions there.