Should I use class method or instance method, and why? - ruby-on-rails

In my Rails app, when creating a business I have a form that has the following field:
<%= check_box_tag(:default_company) %>
<%= label_tag(:default_company, "Set Company as Default") %>
Essentially when I create a business, if they check this box, I need it to run something like the following code:
def set_default_company(company, user)
exists = DefaultCompany.find(user.id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: user)
end
end
While learning, I would usually do that stuff in my controller but i'm trying to follow best practices and use a fat model, skinny controller, so I'm wanting to use logic like this:
def create
#company = Company.new(params[:company])
if #company.save
if params[:default_company]
Company.set_default_company(#company.id, current_user.id,)
end
flash[:notice] = "Company was successfully created."
redirect_to #company
else
redirect_to new_company_path
end
end
Here is where I am getting confused on whether to use a class method or an instance method, to call set_default_company. They both seem like they would work and I can't see a benefit to one or the other.
In addition to giving me any information as to which method to use, if someone could show me a brief implementation of writing that as a class method vs. instance method it may give me a better understanding as to why.
Here is how I would write them:
def self.set_default_company(company, user)
# Logic here
end
def set_default_company(company, user)
# Logic here
end
Writing them that way I don't see a benefit to either.

As their name suggests, instance methods on a model should be used for logic/operations that relate to a specific instance of a user (the one on which the method is called.) So you could think of setting the default company for a user as an instance method on User. Class methods are for things which don't operate on an individual instance of a model or for cases where you don't have the instance available to you. e.g. you might have a class method to tidy up your database such as User.purge_expired_users which would not apply to an individual user object.
e.g.
class User
def set_default_company(company)
exists = DefaultCompany.find(self.id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: self)
end
end
end
then your controller method would look like:
def create
#company = Company.new(params[:company])
if #company.save
if params[:default_company]
current_user.set_default_company #company
end
flash[:notice] = "Company was successfully created."
redirect_to #company
else
redirect_to new_company_path
end
end
Alternatively, you could think of the relationship from the other perspective and put an instance method on Company e.g. company.set_as_default_for(user).

I would actually make set_default_company an instance method on User. A User has a default Company; why should a Company need to what users it is default for?
class User
def set_default_company(company)
exists = DefaultCompany.find(id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: self)
end
end
end

In my opinion, I always create a class method if the method in question represents information/behavior that is quite generic among all the objects instantiated, different from the instance methods, that I use when I believe it's more like a specific action of the instantiated object in question.
But that is my point-of-view.

A few things: do you have a separate table for DefaultCompany? This seems like it should be a boolean flag on the company table.
Next, is there an association between companies and users? If so, it seems the best way to do it would be
In the user model
def set_default_company(company)
self.companies.each do |c|
c.update_attributes(:default => false)
end
company.update_attributes(:default => true)
end
Or in the Company model
def set_as_default
update_attributes(:default_company => true)
end

Related

Rails has_one build_association deletes record before save

So this has been asked previously, but with no satisfying answers.
Consider two models, User, and Subscription associated as such:
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Inside of SubscriptionsController, I have a new action that looks like this
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
Given that a subscription already exists for a user record, I'm faced with the following problem:
user.build_subscription is destructive, meaning that simply visiting the new action actually destroys the association, thereby losing the current subscription record.
Now, I could simply check for the subscription's existence and redirect like this:
def new
user = User.find(params[:user_id])
if user.subscription.present?
redirect_to root_path
else
#subscription = user.build_subscription
end
end
But that doesn't seem all that elegant.
Here's my question
Shouldn't just building a tentative record for an association not be destructive?
Doesn't that violate RESTful routing, since new is accessed with a GET request, which should not modify the record?
Or perhaps I'm doing something wrong. Should I be building the record differently? Maybe via Subscription.new(user_id: user.id)? Doesn't seem to make much sense.
Would much appreciate an explanation as to why this is implemented this way and how you'd go about dealing with this.
Thanks!
It depends on what you want to do
Thoughts
From what you've posted, it seems the RESTful structure is still valid for you. You're calling the new action on the subscriptions controller, which, by definition, means you're making a new subscription (not loading a current subscription)?
You have to remember that Rails is basically just a group of Ruby classes, with instance methods. This means that you don't need to keep entirely to the RESTful structure if it doesn't suit
I think your issue is how you're handling the request / action:
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
#subscription is building a new ActiveRecord object, but doesn't need to be that way. You presumably want to change the subscription (if they have one), or create an association if they don't
Logic
Perhaps you could include some logic in an instance method:
#app/models/user.rb
Class User < ActiveRecord::Base
def build
if subscription
subscription
else
build_subscription
end
end
end
#app/controllers/subscriptions_controller.rb
def new
user = User.find(params[:user_id])
#subscription = user.build
end
This will give you a populated ActiveRecord, either with data from the subscription, or the new ActiveRecord object.
View
In the view, you can then use a select box like this:
#app/views/subscriptions/new.html.erb
<%= form_for #subscription do |f| %>
<%= "User #{params[:user_id]}'s subscription: %>
<%= f.collection_select :subscription_id, Subscription.all,:id , :name %>
<% end %>
They are my thoughts, but I think you want to do something else with your code. If you give me some comments on this answer, we can fix it accordingly!
I also always thought, that a user.build_foobar would only be written to the db, if afterwards a user.save is called. One question: After calling user.build_subscription, is the old subscription still in the database?
What is the output user.persisted? and user.subscription.persisted?, after calling user.build_subscription?
Your method to check if a subscription is present, is IMHO absolutely ok and valid.
I came across this today and agree that deleting something from the db when you call build is a very unexpected outcome (caused us to have bad data). As you suggested, you can work around if very easily by simply doing Subscription.new(user: user). I personally don't think that is much less readable then user.build_subscription.
As of 2018 Richard Peck's solution worked for me:
#app/models/user.rb
Class User < ActiveRecord::Base
def build_a_subscription
if subscription
subscription
else
build_subscription
end
end
end
My issue was that a user controller didn't have a new method, because users came from an api or from a seed file.
So mine looked like:
#app/controllers/subscriptions_controller.rb
def update
#user = User.find(params[:id])
#user.build_a_subscription
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user), notice: 'User was successfully updated.'
else
render :edit
end
end
And I was finally able to have the correct singular version of subscriptions in my fields_for, so :subscription verses :subscriptions
#app/views
<%= f.fields_for :subscription do |sub| %>
<%= render 'subscription', f: sub %>
<% end %>
Before I could only get the fields_for to show in the view if I made subscriptions plural. And then it wouldn't save.
But now, everything works.

How to write a duplicate record method in Ruby on Rails?

In my Rails app I have an invoices_controller.rb with these actions:
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
#invoice.build_item(current_user)
#invoice.set_number(current_user)
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
Essentially, the new method instantiates a new invoice record plus one associated item record.
Now, what sort of method do I need if I want to duplicate an existing invoice?
I am a big fan of Rails's RESTful approach, so I wonder if I should add a new method like
def duplicate
end
or if I can use the existing new method and pass in the values of the invoice to be duplicated there?
What is the best approach and what might that method look like?
Naturally, you can extend RESTful routes and controllers.
To be rally RESTful, it is important to look exactly, what you want.
i.e. if you want a new invoice and use an existing one as a kind of template, then it is comparable to a new action, and the verb should be GET (get the input form). As is it based on an existing invoice, it should reference that object. After that you would create the new invoice in the usual way.
So in you routes:
resources :invoices do
member do
get 'duplicate'
end
end
giving you a route duplicate_invoice GET /invoices/:id/duplicate(.format) invoices#duplicate
So in your view you can say
<%= link_to 'duplicate this', duplicate_invoice_path(#invoice) %>
and in your controller
def duplicate
template = Invoice.find(params[:id])
#invoice= template.duplicate # define in Invoice.duplicate how to create a dup
render action: 'new'
end
If I understand correctly your question you can:
resources :invoices do
collection do
get 'duplicate'
end
end
and with this you can do:
def duplicate
# #invoice = [get the invoice]
#invoice.clone_invoice
render 'edit' # or 'new', depends on your needs
end
clone_invoice could be a custom method which should have a invoice.clone call in your custom method.
If you question if you can use additional methods except REST, you absolutely can. Google, for example, encourage developers to use something, what they call "extended RESTful" on GoogleIO, http://www.youtube.com/watch?v=nyu5ZxGUfgs
So use additional method duplicate, but don't forget about "Thin controllers, fat models" approach to incapsulate your duplicating logic inside model.

Passing parameters for nested relationship

I am having trouble passing parameters
My application that is setup like this:
Fact belongs_to Source
Source has_many Facts
Source is nested under User in routes
I am using the Facts form to create the Source data. So I have getter and setter methods in the Facts model like this:
def source_name
source.try(:name)
end
def source_name=(name)
self.source = source.find_or_create_by_name(name) if name.present?
end
This is working great, but it is not setting the user_id for the parent User attribute. As a result, sources are created, but they are not associated with the User.
I have a hidden field with user_id in the form, but the user_id is still being set. What is the easiest way to pass and save the user_id so the nested relationship is set?
Here is the create method for the Source controller:
def create
#user = User.find(params[:user_id])
#source = #user.source.build(params[:source])
...
end
I think the problem is that you are creating source directly from the setter method in the Fact model. Unless you establish the chain by using something like build in the FactController, the user_id will not be set. What you are doing in SourceController needs to be done in the FactsController too. Also, it seems that the ids are set only for the immediate parent when you use the build command. You can try something as below:
def create
#source = current_user.sources.find_or_create_by_name(params["source_name"])
#fact = #source.facts.build(:user_id => #source.user_id)
....
end
Hope that helps.
If your user has a single Source, try the following as your create() method:
def create
#user = User.find params[:user_id]
#user.source = Source.new params[:source]
if #user.save
redirect_to #user, :flash => { :success => "Source updated!" }
else
flash[:error] = "Failed to update the source!"
render :action => "new"
end
end
Creating the Source as an attribute on the User object and then saving the User object should automatically associate the Source with the User.

How do I log create actions?

I am trying to create a logging feature on my RoR application that logs all the actions performed by the user on a given controller in a model. I tried implementing it with filters but they didn't solve my problem because they didn't allow me to properly log "create" actions.
"Create" actions are tricky because, when the before/after filters are called the action hasn't been saved yet, therefore, I don't have the corresponding id of the model (which I need).
I know I could use "after_commit", but this would greatly increase the complexity of the logging feature, since the "parameters" saved in each log entry are exposed to the controller, but not to the model.
Is there a way to dynamically add an "after_commit" filter to an instance of ActiveRecord?
Read this, I think this is nice solution: Notifications
This is how i log, i have a users controller create action like this:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Welcome, #{#user.username}"
redirect_to(:controller => "users", :action => "home")
session[:id] = #user.id
else
render("home")
end
end
Now i would like to log that a user was created, then i do this:
First create an AuditLogger class in User.rb(model):
class User < ActiveRecord::Base
...some stuff other....
class AuditLogger < Logger
def format_message(severity, timestamp, progname, msg)
"#{timestamp.to_formatted_s(:db)} #{severity} #{msg}\n"
end
end
Then back to the controller(users.rb)
def create
#user = User.new(params[:user])
if #user.save
logfile = File.open("#{Rails.root}/log/my_log.log", 'a')
audit_log = AuditLogger.new(logfile)
audit_log.info "#{#user.firstname} was created successfully"
redirect_to(:controller => "users", :action => "home")
else
render("home")
end
end
Also you will need to create a file in your log directory called my_log.log. Hopefully it should be able to log. I know its not the best solution and there i are better ways of doing it, but at the time i needed something to work urgently, so i stuck with it.
Checkout these links:
rails logging tips
alternative logging solution

Custom method redirect in Rails

I've created a custom method in my model, which finds a record by name:
def find_city
Place.find_by_name(city_name)
end
I can call this method in my view with place_path(#place.find_city), which works great when a place with the appropriate name exists. What I would like to be able to do is write in a redirect for when the place doesn't exist, and I'm unsure about where the logic should go. Basically, I would like to write something like:
respond_to do |format|
if #place.find_city.blank?
format.html { redirect_to :action => "new" }
else
format.html { render :action => "show" }
end
end
...but I would still like the controller to respond to place_path(#place) in the usual manner as well. Any help would be much appreciated!
EDIT: sorry for the confusion, should have explained my example further. I have a Place model that has both 'city_name' and 'name' as attributes. The find_city custom method that I detailed above finds the place whose name matches the city_name for another place eg.
Place.name = "foo"
Place.city_name = "baz"
So therefore Place.find_city gives the record where Place.name = "baz". Cheers!
Do something like this
keep your model as
class City < ActiveRecord::Base
named_scope :by_name, lambda {|city|{:conditions => "name=#{city}"}}
end
in your controller
class CitiesController < ApplicationController
def index
#city = City.by_name(<city name>)
if #city.nil?
<redirect to one place>
else
<redirect to another place>
end
end
end
and in your view access the #city parameter.
** Normally we shouldnt access Models directly from views. Either we should do it through controllers or helpers
Hope this helps
cheers
sameera
I've decided to create a helper method for this problem - what I've described above might seem a bit of an unorthodox approach, but I only need the find_city method to create a links bar for each place, so I don't think I really need to create a separate city model, or define a formal self-referential relationship. Just in case anyone might find this useful, I've created a links_bar helper:
module PlacesHelper
def links_bar(place)
if place.city_name.blank?
render "no_city_links"
elsif place.find_city.nil?
render "nil_find_city_links"
else
render "complete_links"
end
end
end
Each partial then has the required behaviour depending upon whether or not the find_city method returns a record or not.

Resources