I am always reading about keeping Controllers thin and doing all logic in models. While this makes senses to me for interacting with databases, what about situations where there is no need to for database interactions?
I have a fairly complex module in my app that interact with several different third party APIs. I use ajax calls to my controller, where all the data is gathered from the APIs and then organized. Then it is displayed via the corresponding .js.erb or .html.erb files.
Is this the proper way to handle this kind of situation? I'm new to rails and don't want to get into habit of doing things wrong.
Models are not just for dealing with database, but for working with data in principle.
As far as we don't know what situations you mean I can just present some situations.
Ajax call for big Math calculating. It is not touching database and even it can be calculating in tableless model.
# in your controller
def calculating
Calculator.get_integral_log_and_furie params[:data]
end
# in your model
class Calculator
def self.get_integral_log_and_furie(data)
... # multi line code
end
end
So you can see that you can calculate it right in your controller, but it should be calculated in your model, so it is reusable and clean solution.
Another example is using some virtual attributes. Names. You can store first, second and third name in saparate columns, so you need to join it. You can create privae method in controler, but of course it is bad idea.
class User < AR::Base
def full_name
[first_name, second_name, third_name].compact.join(" ")
end
end
So you can call it everywhere in your project:
#user.full_name
# Peter Jhonson, or mu is too short
And so on and so on
Do model logic in models.
Maintain associations.
Maintain complex attributes.
Maintain validations.
Represent concepts from the business/industry.
Do controller logic in controllers.
Check that a user is authorized to modify a resource.
Pull and aggregate data to pass into a view template.
Find the right view template.
Compose json for the API response.
Retry failed saves.
Models do not need to be ActiveRecords. You can do a whole lot with models - the "core" of your appliation - that has nothing to do with persistence. Just don't put controller logic into these models.
That's a good question.
Even if you don't need to use a database, you can still take an OOP / MVC approach to organise your code and wrap your data, logic and behaviour in models.
Code organisation and encapsulation within model objects is still useful & important!
In Rails 3, you can make non-persisting models by including just some of the ActiveModel modules that ActiveRecord contains. For example:
# This class exists as a fairly simple DTO that is not expected to be persisted, but
# it does have validation, attributes & a hash constructor, just like ActiveRecord models
class MyProduct
include ActiveModel::Conversion
include ActiveModel::Naming
include ActiveModel::Validations
attr_accessor :title, :quantity
validates :title, :quantity, :presence => true
validates :quantity, :numericality => {:greater_than_or_equal_to => 1}
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
Related
I am going through a co-workers code and am not able to find a single tutorial where this has been used. Can someone point me to some resources where this has been used. This has made code very clean but I haven't found any reference to it. This is only part of this class. It includes other some more methods.
class Manager
include ActiveModel::Model
include ActiveModel::Associations
attr_accessor :application_id, :user_id, :user_application_id,.........
belongs_to :application
belongs_to :user_application
belongs_to :user .. more belongs .......
# This method is necessary to enable this ActiveModel Class to be used in views along with Form helpers
def self._reflect_on_association(association) #:nodoc:
_reflections[association.to_sym]
end
def []=(attr, value)
self.send("#{attr}=", value)
end
def [](attr)
multi_attribute_ids = [:some_ids.to_s, :someid2.to_s]
return if multi_attribute_ids.include?(attr)
self.send(attr)
end
def applicant_name
end
-- some more methods
end
What would be the use of such a "manager". What are the two methods that are using self.send doing here. Is this a common pattern in rails.
Yes, with the introduction of ActiveModel in Rails 3, it has become an increasingly common pattern to use domain objects (called a manager in this case) that are not backed by an actual database table but which look and feel like models.
Even though ActiveModel makes it particularly convenient to pick and choose Rails model features to be incorporated into arbitrary classes, this pattern is something Rails pioneers have been encouraging since a long time.
As has been illustrated clearly in the example you posted, this pattern allows us to define virtual models and virtual associations which can easily take advantage of form helpers and other rails niceties written assuming model objects.
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
I have a rails app that like so many rails apps, has users. In my user model i have password and salt columns, among others. Is there any way i can protect these from appearing when i do for example debug #user or when i render JSON?
Of course i could just make sure to omit these every time i use it, but is there a way i can make really sure that they don't show up anywhere?
Maybe they could be private fields? They are only needed in my User model and my Session controller, but i could make a method in User that compares a given password to the correct one and then only have the variables accessible in the ´User´ module. Would this help, or would they still be rendered in those two places ( and others )?
Very good question, there are several things to consider:
Are you looking to "privatise" the values in the model or in the Rails Core?
Will you need to access these attributes in any specific circumstances? (IE are they always private?)
How will the private methods be populated? Will they ever change?
I have to be honest in saying I don't know the answer. Since I'm interested, I did some research. Here are some resources:
Is there a way to make Rails ActiveRecord attributes private?
Hiding an attribute in an ActiveRecord model
ActiveRecord protects your privates
The consensus seems to be that if you take the Rails model, and apply the logic that every time you populate it, its attributes become instance methods of the, you can begin to privatise those methods within the model itself.
For example...
#app/models/user.rb
class User < ActiveRecord::Base
private :password, :password=
private :salt, :salt=
end
This seems to give you the ability to make certain methods private in your model, which will answer half your question. However, it still leaves the possibility of ActiveRecord pulling the record each time, which could be a danger.
I had a look into this, and found that there are certain ways you can manipulate ActiveRecord to prevent it pulling unwanted data:
ActiveRecord : Hide column while returning object
This resource recommends the use of active_record_serializers. This appears specifically for JSON, but is more along the right track (IE the ability to define which data we return from ActiveRecord queries).
#serializer
class UserSerializer < ActiveModel::Serializer
attributes :username, :created_at
end
#controller
render json: #user, serializer: UserSerializer
There was another suggestion of ActiveRecord Lazy Attributes - stipulating to ActiveRecord which attributes to load:
#app/models/user.rb
class User < ActiveRecord::Base
attr_lazy :data
end
Finally, you always have good ol' default_scope:
#app/models/user.rb
class User < ActiveRecord::Base
default_scope select([:id, :username])
end
Let's say you have several models that contain fields for address, postal code, province/country, phone number, etc.
These are rather common fields that have specific regular expression validations. If you put the same validations and regular expressions in each model, it is duplicated. Also, the tests are duplicated. This is a smell ;)
What is the best approach using ruby and rails to refactor these types of things? A module?
In Java with Hibernate, we'd use a Component class to store the address, and then we'd put the validation logic there. Each model that wanted to use an address would simply contain one, and it will get all the address validation logic.
What is the approach to achieve the same thing in rails? Thanks!
Build custom validators for the various types of validations you need, then invoke them in your model classes.
For example:
class PostalCodeValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, value)
unless value =~ /^\d{5}$/
record.errors[attr_name] << "must be a 5-digit postal code"
end
end
Now use that validation in each model class and for each attribute that is a postal code. For instance, if you Customer has an attribute postal_code:
class Customer < ActiveRecord::Base
validates :postal_code, :postal_code => true
end
There's more detail and lots of fancy options, so I suggest a Google search on rails custom validators.
From the pointview of rails best practices, what is the best place to manipulate form data before saving?
For instace, on a contact form, I want to make sure that all data is saved in capitalized form ( don't you hate when PEOPLE SHOUT AT YOU in their "please contact me" form submission? :-) )
is it better to do manipulation in controller? I could either do it in create, or move it into some sort of private method , that will capitalize all string attributes of the object before saving / updating?
Or
is it better do in the model before_save?
It makes sense to me that it should be done in the model since I probably want that to be the same for all records, no matter whether I manipulate on them in a rake task or through the web interface.
Bonus:
Also where would I place it if I want that that on ALL my models, with the ability to override default on a case by case basis? Application controller?
There might be some special cases where you want to save value without capitalizing - i.e. brand name products that don't capitalize (i.e. utorrent) or a last name that should have multiple caps in the name (i.e. Irish & Scottish names like McDonald)
Thank you!
the easiest place to put this is in your model. I would suggest using either before_save or even before_validation if you feel that fits better. Something like this would do the trick:
before_save :upcase_content
def upcase_content
self.content = self.content.upcase
end
Additionally if you wanted to allow for exceptions of a case by case basis you could add an attr_accessor to your model.
class MyModel < ActiveRecord::Base
attr_accessor :dont_upcase
before_save :upcase_content, :unless => :dont_upcase
...
end
then when you create a model set the accessor to true
#model = Model.new(:brand_name => utorrent)
#model.dont_upcase = true
#model.save!
The best place to put this is in your model, that way you have a fat model and a skinny controller, which is a "good thing".
If you want to have this be available for all of your models my suggestion is to use a module which contains your shared functionality and then include that in all the models you want to have the default behavior.
Ok based on the suggestions from other replies I came up with this solution:
lib/clean_strings.rb
module ActiveRecord
class Base
attr_accessor :dont_capitlize, :dont_strip
before_save :_capitalize_strings, :unless => :dont_capitlize
before_save :_strip_whitespaces, :unless => :dont_strip
def _capitalize_strings
self.attributes.each_pair do |key, value|
self[key] = value.capitalize if value.respond_to?('capitalize')
end
end
def _strip_whitespaces
self.attributes.each_pair do |key, value|
self[key] = value.strip if value.respond_to?('strip')
end
end
end
end
in environment.rb addded
require "clean_strings"
Now whenever I do
#a.dont_capitalize = true
#a.save!
it cleans it before saving according to my rules ( it will strip whitespace, but not capitalize it ). Obviously it needs more fine tuning, but i think it's a good way to define format rules for commonplace things. This way I don't need to sanitize every and each form input for things like extra whitespaces, or people who don't know where the CAPS LOCK is !!!
Thank you all for your input ( all upvoted).