Rails 4 - coding through models - ruby-on-rails

I have a user model, with a separate profile model. Each user has a profile. I then have 8 models for things within a profile (for example, each profile has a dashboard, feedback and publications). The profile belongs_to user and the dashboard etc belongs_to profile.
I am creating a profile view and would like to know how I write the line of code that will collate relevant information from the other models to display in the profile.
For example,the profile will be displayed with the name of its owner (which is stored in the user model). It will also have feedback stored in the feedback model. Is there a way to write that the profile view should display the user.first_name user.last_name, and user.feedback?

You can chain calls through Profile, like so:
#profile.user.first_name
But this violates a principle known as the “Law of Demeter”. There's a complex definition, but suffice to say that when you are accessing one object (User) through another (Profile), you begin to violate this law. It's not a huge deal when you're accessing user properties through the profile, necessarily, but things get messy quickly:
#dashboard.profile.feedback.order(:rating).where(user: #dashboard.profile.user)
Gross. And brittle, too. When you need to compose multiple models into a single view, there's a better pattern known as a Decorator. The job of a decorator is to give you a single object that appropriately collects data from the models for presentation, without tying your view code directly to your models. For example:
class DashboardDecorator
def initialize(dashboard, profile, user)
#dashboard = dashboard
#profile = profile
#user = user
end
def full_name
"#{#user.first_name} #{#user.last_name}"
end
def feedback_count
#profile.feedback.count
end
def days_since_last_post
Date.today - #dashboard.last_login
end
end
# /app/controllers/dashboard_controller.rb
def show
# ...
#dashboard = DashboardDecorator.new(dashboard, profile, current_user)
end
Then your view can access the data through the decorator:
<%= #dashboard.full_name %>
While you can write your own decorators like above, things get pretty tedious pretty quick. If you like to automate some of these parts, you should check out Draper, a handy gem that makes creating decorators a little easier, especially when your decorator methods map 1:1 with model methods.

Related

In rails how to allow creation of new class and models at run time

I am facing a design problem with respect to a rails app I am developing for my company product right now. My app allows creation of two classes which are subclasses of a parent class.
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
end
class DealershipCoupon < Coupon
end
When you go to the view and you want to create a new coupon, you select either of the two and a new coupon is created depending upon the params[:coupon_type]
In the controller:
if params[:coupon_type] == 'dealershipcoupon'
#coupon = DealershipCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
elsif params[:coupon_type] == 'servicecentercoupon'
#coupon = ServiceCenterCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
end
I wanna give admin users the ability to create new coupon types at the run time as well. Say, someone wants to create Repairshopcoupons class. What changes do I need to bring to the views for example add a new form or what params I need to add to the existing form to be able to create new sub classes of Coupons at run time?
I do understand that using
repairshopcoupon = Class.new()
can work. For example anonymous function like this code in the controller can work:
Repairshopcoupon = Class.new(Coupon) do
include ActiveModel::Validations
validates_presence_of :title
def self.name
"Oil Change"
end
end
#newrepairshopcoupon = Repairshopcoupon.new
#newrepairshopcoupon.save
But I am not sure.
My first questions is: What would be the proper flow if I want users to create new classes from the view. What should controller handle and how will it save?
My second question is: There are few customers who belong to both dealerships and service centers group. Each group has authority over what coupon type they can manage. I want these users who belong to multiple groups to be able to see respective coupon inventory as well as which users downloaded those. I feel the need of changing my data model so that all coupon inventory and download lists belong to exactly one authorized group but I don't have a concrete idea of what would be the best way.
My third question is: What would be the best approach to change my view/UX for creating and managing coupons so that the users of multiple groups would be able to switch between each inventory ? What would be the professional industry standard for UX deign in this case ?
Would really appreciate your help.
Letting the users of an application generate code at runtime is just a really bad idea as the amount of potential bugs and vulnerabilities is mind boggling as your basically allowing untested code to be injected into the app at runtime.
It will also wreck havoc with any class based caching in the application.
It also won't work with cloud platforms like Heroku that use an ephemeral file system which is created from the last code commit.
First off you probably don't actually need different classes for each "type" of coupon. You need to consider if the logic for each class is substantially different.
You can probably get by just by creating a polymorphic association to the issuer (the dealship or service center).
class Coupon < Coupon
belongs_to :issuer, polymorphic: true
end
If you want to avoid polymorphism than just set it up as a standard STI setup:
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
self.table_name = 'coupons'
belongs_to :service_center
end
class DealershipCoupon < Coupon
self.table_name = 'coupons'
belongs_to :dealership
end

Saving multiple instances of models in a rails form using CRUD

I'm trying to create a form that has multiple instances of different models at once.
I have my main model visualizations. A Visualization (:title, :cover_image) has_many Rows. A Row has_many Panes (:text_field, :image)
Basically when a user tries to create a Visualization, they can choose the cover image and title easily enough. But I get a bit confused when I come to the next two levels.
The user is prompted to create a new Row in the form and they can choose either 1, 2, or 3 Panes per Row. Each pane can take in text and an image, but Row doesn't necessarily have any attributes itself.
How can I generate multiple Rows with multiple Panes in this form? The end result will need to possess a bunch of rows consisting of many panes. Can I even do this in rails?
Thanks for any help!
You can do anything in rails! The best approach in my opinion is to create what is known as a Form Model since this form will have a lot going on and you don't want to bog down several models with validations and such for one view of your app. To do this you're basically going to create a class that will take all of this information in, run whatever validations you need, and then create whatever records you need in whatever models you have. To do this lets create a new file in your model folder called so_much.rb (You can make any filename you want just make sure you name the class the same as the file so Rails finds it automagically!)
Then in your so_much.rb file do:
class SoMuch
include ActiveModel::Model #This gives us rails validations & model helpers
attr_accessor :visual_title
attr_accessor :visual_cover #These are virtual attributes so you can make as many as needed to handle all of your form fields. Obviously these aren't tied to a database table so we'll run our validations and then save them to their proper models as needed below!
#Add whatever other form fields youll have
validate :some_validator_i_made
def initialize(params={})
self.visual_title = params[:visual_title]
self.visual_cover = params[:visual_cover]
#Assign whatever fields you added here
end
def some_validator_i_made
if self.visual_title.blank?
errors.add(:visual_title, "This can't be blank!")
end
end
end
Now you can go into your controller that is processing this form and do something like:
def new
#so_much = SoMuch.new
end
def create
user_input = SoMuch.new(form_params)
if user_input.valid? #This runs our validations before we try to save
#Save the params to their appropriate models
else
#errors = user_input.errors
end
end
private
def form_params
params.require(#so_much).permit(all your virtual attributes we just made here)
end
Then in your view you would set your form_for up with #so_much like:
<%= form_for #so_much do %>
whatever virtual attributes etc
<% end %>
Form Models are a bit advanced in Rails but are a life saver when it comes to larger apps where you have many different types of forms for one model and you don't want all of the clutter.

How to reduce number of calls to a helper method

In my view, I need a User object to display a few different properties. There is an instance variable #comments that's being sent from the controller. I loop through the comments and get the User information through a helper method in order to reduce db calls.
Here is the helper method:
def user(id)
if #user.blank? == false && id == #user.id
return #user
else
return #user = User.find(id)
end
end
And in the view, I display the details as follows:
<h4> <%=user(comment.user_id).name%> </h4>
<p><%=user(comment.user_id).bio%></p>
<p><%=user(comment.user_id).long_bio%></p>
<p><%=user(comment.user_id).email%></p>
<hr>
<p><%=user(comment.admin_id).bio%></p>
<p><%=user(comment.admin_id).long_bio%></p>
<p><%=user(comment.admin_id).email%></p>
I was told that assigning a variable in the view is bad practice and hence I am calling the helper method multiple times instead of assigning the returned User object.
Is there a better way to do this?
I think you are overcomplicating things here.
Let's say you have a user model
class User < ActiveRecord::Base
has_many :comments
end
an admin model
class Admin < User
end
a comment model
class Comment < ActiveRecord::Base
belongs_to :user
end
Now you only need a type column in your users table and you can do things like this:
Admin.all (All users with type "Admin")
User.all (Really all users including type "Admin" and all other types)
and for every comment you can just use
comment.user.bio
and it doesn't matter if it's an admin or not.
See http://www.therailworld.com/posts/18-Single-Table-Inheritance-with-Rails for example
Additional info: To reduce db calls in general(N+1 queries) watch http://railscasts.com/episodes/372-bullet
It's perfectly fine to pass models to your view and build the data on the view off of the data contained in the model. Keep in mind that I'm not entirely certain how you want your page to work, but one option you may have is to use a partial view and pass it the user object. This allows you to still only have the one model in your partial view without setting additional variables.
Also, without knowing what kind of database you're using or if your models have any associations, and assuming that you're doing some input validation, you may not need this helper method and may be able to lean on your ORM to get the user object.
For Example:
<%= comment.user.age %>
This isn't any more efficient than what you've currently got, but it certainly makes the code look cleaner.
Another alternative: set a user variable in the view. You're not performing logic in your view at this point, you're simply storing some data to the heap for later use.

Rails forms - Should I build `accepts_nested_attributes_for` associations in the Controller, Model, or View?

The Question
I have a parent that accepts_nested_attributes_for a child. So, when I have a form for the parent, I need to build the child so I can display form fields for it as well. What I want to know is: where should I build the child? In the Model, View, or Controller?
Why I Am Asking This
You may be shaking your head and thinking I'm a madman for asking a question like this, but here's the line of thinking that got me here.
I have a Customer model that accepts_nested_attributes_for a billing_address, like so:
class Customer
belongs_to :billing_address, class_name: 'Address'
accepts_nested_attributes_for :billing_address
end
When I present a form for a new Customer to the user, I want to make sure there is a blank billing_address, so that the user actually sees fields for the billing_address. So I have something like this in my controller:
def new
#customer = Customer.new
#customer.build_billing_address
end
However, if the user doesn't fill out any of the billing_address fields, but tries to submit an invalid form, they will be presented with a form that no longer has fields for the billing_address, unless I put something like this in the create action of my controller:
def create
#customer = Customer.new(params[:customer])
#customer.build_billing_address if #customer.billing_address.nil?
end
There is another issue, which is that if a user tries to edit a Customer, but that Customer doesn't have an associated billing_address already, they won't see fields for the billing_address. So I have to add somethign like this to the controller:
def edit
#customer = Customer.find(params[:id])
#customer.build_billing_address if #customer.billing_address.nil?
end
And something similar needs to happen in the controller's update method.
Anyway, this is highly repetitive, so I thought about doing something in the model. My initial thinking was to add a callback to the model's after_initialize event, like so:
class CustomerModel
after_initialize :build_billing_address, if: 'billing_address.nil?'
end
But my spidey sense started tingling. Who's to say I won't instantiate a Customer in some other part of my code in the future and have this wreak havoc in some unexpected ways.
So my current thinking is that the best place to do this is in the form view itself, since what I'm trying to accomplish is to have a blank billing_address for the form and the form itself is the only place in the code where I know for sure that I'm about to show a form for the billing_address.
But, you know, I'm just some guy on the Internet. Where should I build_billing_address?
Though this advice by Xavier Shay is from 2011, he suggests putting it in the view, "since this is a view problem (do we display fields or not?)":
app/helpers/form_helper.rb:
module FormHelper
def setup_user(user)
user.address ||= Address.new
user
end
end
app/views/users/_form.html.erb:
<%= form_for setup_user(#user) do |f| %>
Note that I had to change the helper method to the following:
def setup_user(user)
user.addresses.build if user.addresses.empty?
user
end
The controller remains completely unchanged.
If you know your model should always have a billing address, you can override the getter for this attribute in your model class as described in the docs:
def billing_address
super || build_billing_address
end
Optionally pass in any attributes to build_billing_address as required by your particular needs.
You would use build if you want to build up something and save it later. I would say, use it in nested routes.
def create
#address = #customer.billing_addresses.build(params[:billing_address])
if #address.save
redirect_to #customer.billing_addresses
else
render "create"
end
end
Something like that. I also use the build when I'm in the console.
You have to remember the principles of MVC, which is to create DRY(don't repeat yourself) code, which is efficiently distributed between the various moving parts of the app
accepts_nested_attributes_for Is Great For Keeping Things DRY
accepts_nested_attributes_for is a model function which allows you to pass data through an association to another model. The reason why it exists is to give you the ability to populate another model's data based on a single form, and is excellent for extending functionality without too much extra code
The problem you're citing is that if you want to use the code in other areas of the app, you'll end up having all sorts of problems
My rebuttal to that is in order to create as efficient an application as possible, you want to write as little code as possible - letting Rails handle everything. The accepts_nested_attributes_for function does allow you to do this, but obviously has a cost, in that you have to accommodate it every time you want to use it
My recommendation is to use what you feel is the most efficient code you can, but also keep to conventions; as this will ensure speed & efficiency
You should handle all these scenarios in controller, since it is not a responsibility of model.
Just in terms of keeping things DRY, you can write a method,
def build_customer(customer)
customer.build_billing_address if customer.billing_address.nil?
#add more code if needed
end
And inside controller you can call this method wherever it is needed. e.g.
def create
#customer = Customer.new(params[:customer])
if #customer.save
redirect_to #customer.billing_addresses
else
build_customer(#customer)
render "new"
end
end

Creating an additional related model with Devise

I've started to implement a new project using Devise, which is pretty fantastic for handling users. However, when a user signs up, they're not just creating a User model, but also need to create a related Account model that represents the company. Additional users will also belongs_to this Account model.
I can't seem to find a hook for this in Devise, though it seems like a pretty common pattern. What's the best practice for this?
I should also mention that there are a couple of fields for the Account that need to be provided on the sign_up form, so just something like this in the User model:
after_create :make_sure_account_exists
def make_sure_account_exists
if self.account.nil?
#account = self.create_account({ :company_name => '???' })
end
.. as I'm not sure how to get the company name.

Resources