My application has a Group object, of which a user has many. The navbar displays which group is currently selected, and the objects displayed on various pages are based on this. A number of my models have 'group_id' fields, and I'd like for these fields to be populated with the id of the currently selected group when they're saved.
In my application controller I have a helper_method which returns the current_group however this can't and shouldn't be accessed from a model which is the DRYest way I could think of doing this.
#inhereted_model.rb
before_save :assign_group_reference
def assign_group_reference
self.group_id = current_group.id
end
Is there an efficient and DRY way to do this that I'm missing?
You are right; any controller helper-methods cannot and should not be accessed directly from a model method.
I think a standard DRY way is to set the parameter of a model in your Controller methods. For example, do as follows in your Controller(s):
# In a Controller
def my_helper(mymodel)
mymodel.group_id = current_group
# where current_group is your Controller helper method to obtain the group name.
end
def create # or update etc.
#mymodel = set_my_model # your arbitrary method to set a model
my_helper(#mymodel)
respond_to do |format|
if #mymodel.save
format.html { redirect_to #mymodel, notice: 'Success.' }
else
raise
end
end
end
If you want, you can write my_helper (which in this case takes no argument and sets the instance variable #mymodel instead of a local variable) in before_action in combination with only or except, where you make sure the method is called after a model #mymodel is set, in order to avoid calling my_helper repeatedly in many methods in the Controller.
Alternatively, if you really want to set it at a model level for some reason, a potential workaround is to use a Ruby Thread variable, like the following.
# In a controller
def create
model = set_my_model # Arbitrary routine to set a model
Thread.new{
Thread.current.thread_variable_set(:grp, current_group)
# where current_group is your Controller helper method to obtain the group name.
model.save!
# In the model, you define a before_save callback
# in which you write something like
# self.group_id = Thread.current.thread_variable_get(:grp)
}.join
end
But I think this is a little dirty hack and I would avoid it in principle.
Related
The question is what do you think about this pattern?
Problem:
You've got controller with index action, and this action is huge.
Action is full of ActiveRecord chaining and maybe some computations with records.
When you are adding new methods controller is getting bigger.
I've heard about "skinny controller fat model", and I'm just what? My models are already fat, are you crazy?
I've heard about service objects, they are not very usable as for me.
result = SomeService.new(params, request, flash, current_user).call
After such service object you could try:
result = SomeService.new(controller).call
# or
result = SomeService.new.(controller)
And what to do with returning error statutes from that service? As answered below, exceptions.
So, you need to create exceptions class, throw it, catch it and only then render something or make redirect.
Here is the pattern from subject:
# controllers/some_controller.rb
class SomeController < OtherController
before_actions
include Index
include Show
def create_update_and_destroy
# small methods have no reason to leave controller
end
private
def common_private_method
end
end
# controllers/some_controller/index.rb
module SomeController::Index
def index
# code here
end
private
def index_do_some_stuff
# this method is prefixed by "index" to avoid name collision
end
end
Yes, there is some_controller.rb and some_controller directory with actions as files.
Nobody in OOP likes prefixes and if your method has well explaining not short name - prefix is not necessary.
In my opinion, this is the most simple and obvious way. Just take fat code and split to modules!
What do you think?
Explainations about why I have many code:
In one project I have view, it requires records from several models, some related, some not related.
So I had started to write many scopes in models, time passed, I realized that this is wrong approach.
Different actions required very specific records selections, only action should know about such specifics. And I had scopes named "for_index", "for_show".
Now I'm creating module Index with metod index, and all record fetching and computing code is splitted into private methods right in place.
In other project I have API. Specific endpoint is returning specific deep nested json, several models are fetching. I already know that creating scopes in model for one specific endpoint is bad idea, so I'm splitting code amoung private methods. One action and five private methods for it. And next five public methods and 25 private? In single controller?
Combining form objects with service objects and other patterns can make your model and controller thin. You can also add an ActiveResource::Errors object in your service object to collect errors.
Here's an example using user's input. Customize it according to your specification
class ProductForm
...
def save
if service_object.call
self
else
append_errors(service_object)
false
end
end
def service_object
#service_object ||= ProductCreationService.new(params)
end
def append_errors object
errors.append object.errors # just for simplicity
end
end
In your controller
def create
#product = ProductForm.new params
if #product.save
...
else
...
end
end
Controller actions should be straight forward and as short as possible. Complexities inside these actions can be lessen with other design patterns.
Actions that collects data can be abstracted by using query/finder objects.
Here's a rough example
class DashboardQuery
attr_reader :options
def initalize(options = {})
#options = options
end
def branches
#branches ||= Branch.all
end
def branch_count
#branch_count ||= branches.count
end
end
# Usage
#dashboard = DashboardQuery.new(params)
#dashboard.branch_count
#dashabord.branches
If I have a controller
class MyController < ApplicationController
vals = [...]
def new
...
end
def create
if save
...
else
render 'new'
end
end
how can I make the "vals" variable accessible to both methods? In my "new" view I want to use the "vals" variable for a drop-down menu, but rails is giving me errors. Of course, I could just copy the variable twice, but this solution is inelegant.
As Sebastion mentions a before_ hook / callback is one way to go about it, however as you mentioned it is for a dropdown menu, I am guessing it is a non-changing list, if so I would suggest perhaps using a Constant to define the values, perhaps in the model they are specific to, or if it is to be used in many places a PORO would do nicely to keep things DRY. This will then also allow you to easily access it anywhere, for example in models for a validation check, or to set the options of the dropdown menu in the view, or in the controller if you so wish:
class ExampleModel
DROPDOWN_VALUES = [...].freeze
validates :some_attr, inclusion: { in: DROPDOWN_VALUES }
end
class SomeController < ApplicationController
def new
# can call ExampleModel::DROPDOWN_VALUES here
end
def create
# also here, anywhere actually
end
end
You could use a before_* callback, e.g a before_action, this way you sets your vals variable as an instance one and make it to be available for your both new and create methods, something like:
class SomeController < ApplicationController
before_action :set_vals, only: [:new, :create]
def new
...
# #vals is available here
end
def create
if save
...
# and here
else
render 'new'
end
end
private
def set_vals
#vals = [...]
end
end
A different way from the ones before (although probably just having the instance method is preferred as in Sebastian's solution) is, take advantage of the fact that functions and local variables are called in the same way in ruby and just write:
def vals
#vals ||= [...]
end
and you should be able to access it on the controllers (not the views). If you want it on your views as well you can call at the beginning of the controller
helper_method :vals
If you want to be able to modify vals using vals="some value"
def vals= vals_value
#vals = vals_value
end
Take into account that probably using the intance variable as in Sebastian's solution is preferred, but if you, for whatever reason, are settled on being able to call "vals" instead of "#vals" on the view (for example if you are using send or try), then this should be able to do it for you.
Define in corresponding model
Eg :
class User < ActiveRecord::Base
TYPES = %w{ type1 type2 type3 }
end
and use in ur form like
User::TYPES
=> ["type1", "type2", "type3"]
You can reuse this anywhere in the application.
I am rather new to Rails, and would greatly appreciate any bit of help. I have created the following method:
def name_fix
name = self.split
mod_name = []
name.each do |n|
n.split("")
if n[0]
n.upcase
else
n.downcase
end
mod_name.push(n)
end
mod_name.join
end
I would like to use this method in my Controller as such:
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name].name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
How can I go about accomplishing this? Will this method reside within my Model or Controller? Previously, I've run into an undefined method error.
Note: I'm sure that there is a way to better write my code. I am grateful for help with that as well.
#app/models/patient.rb
class Patient < ActiveRecord::Base
protected
def name=(value)
mod_name = []
value.split.each do |n|
n.split("")
type = n[0] ? "up" : "down"
n.send("#{type}case")
mod_name.push(n)
end
#name = mod_name.join
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient = Patient.new patient_params
#patient.save ? redirect_to(patients_path) : render(:new)
end
private
def patient_params
params.require(:patient).permit(:name)
end
end
What you're doing is trying to override the setter method, which can be done using the above code. Much more efficient and out of the way.
I have created the following method
Since you're new, let me explain something else.
It is important to note where you're using this method.
You've currently put it in the model, which means you'll have to call it to manipulate some attribute / functionality of any object created with said model.
--
Models - in Rails - build the objects which populate your app. Ruby is an object orientated language, which means that every element of your program should revolve around data objects in some degree.
As you can see above, the method of building objects in your system is really about invoking classes. These classes contain methods which can be called, either at class level (IE invoking the class through the method), or at instance level (IE calling a method on an already invoked object).
This is where you get "class" methods (Model.method) and "instance" methods (#model.method) from:
#app/models/patient.rb
class Patient < ActiveRecord::Base
def explode
#this is an instance method
puts "Instance Explode"
end
def self.explode
#this is a class method
puts "Exploded"
end
end
Thus you can call the following:
#patient = Patient.find params[:id]
#patient.explode #-> "Instance explode"
Patient.explode #-> "Exploded"
--
This is important because it gives you a strict framework of where you should, and shouldn't use methods in your models.
It explains why you have controllers & helpers, and allows you to formulate the best way to structure your application as to get the most out of the least code.
For example...
Your use of #patient.name = params[:params][:name].name_fix is incorrect.
It's wrong because you're calling the instance method .name_fix on a piece of data totally unrelated to your model. If you wanted to use .name_fix in a general sense like this, you'd probably use a helper:
#app/helpers/patients_helper.rb
class PatientsHelper
def name_fix value
# stuff here
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient.name = name_fix params[:patient][:name]
end
end
Since you're using the method to populate the .name attribute of your model, it makes sense to override the name= setter. This will not only provide added functionality, but is much smoother and efficient than any other way.
Methods that are called directly are best put in the Controller (or in ApplicationController if you think more than one controller might want to use it).
These are methods like
# app/controllers/my_controller.rb
def foo(bar)
# do something here
end
def create
id = params[:id]
value = foo(id)
end
If you want a chained method that acts as a property method of whatever you're calling it on. Those are characteristic of how Models work - you have your main model and you call attributes or methods on the instance of that model.
# app/models/my_model.rb
def full_name
first_name + " " + last_name
end
# app/controller/my_controller.rb
def create
id = params[:id]
model = MyModel.find(id)
full_name = model.full_name
end
In your case, you want to call name_fix ON whatever is returned by params[:params][:name], which is (I'm guessing) a String.
You have two options
Modify the String class to define a method named name_fix. I highly recommend against this. It's call "monkeypatching" and shouldn't be done without good reason. Just letting you know you can do it in some cases.
Use a direct method in your controller or ApplicationController like the first example above.
#patient.name = name_fix(params[:params][:name])
Edit: As for your request about a better way to write your code... that's difficult to teach or convey in one answer. I'd say read some open source projects out there to see how people write Ruby and some common idioms used to clean up the code. To get you started, here's how I'd re-write your code
def create
#patient = Patient.new(params[:patient])
# 1. Be descriptive with your method names. `name_fix` is vague
# 2. Why is `:name` nested under another `[:params]` hash?
#patient.name = capitalize_name(params[:name])
if #patient.save
# 1. I think `patient_path` has to be singular
# 2. It needs a `Patient` object to know how to construct the URL
# e.g. `/patients/:id`
redirect_to patient_path(#patient)
else
render :new
end
end
def capitalize_name(full_name)
# Example: julio jones
#
# 1. `split` produces an array => ["julio", "jones"]
# 2. `map` applies a function (`capitalize`) to each element
# => ["Julio", "Jones"]
# 3. `join(" ")` rejoins it => "Julio Jones"
full_name.split.map(&:capitalize).join(" ")
end
Assuming your goal with the name_fix method is just to capitalize the first letter of each name, you could just pass name as an argument and store it as a private method on the Controller:
# app/controllers/patient_controller.rb
private
def name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do
#patient.name = name_fix(params[:params][:name])
in the create method.
OR, you could store this method in the model:
# app/models/patient.rb
def self.name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do this instead, in the controller:
#patient.name = Patient.name_fix(params[:params][:name])
I would also suggest renaming your name_fix method to something like capitalize_name.
update your create method as below
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name]
#patient = #patient.name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
It should work.
How do you pass data from a controller to a model?
In my application_controller I grab the user's location (state and city) and include a before_filter to make it accesible in all my controllers via
before_filter :community
def community
#city = request.location.city
#state = request.location.state
#community = #city+#state
end
Then I try add the data retrieved in the controller to the model via:
before_save :add_community
def add_community
self.community = #community
end
The data, however, never makes its way from the controller to the model. If I use:
def add_community
#city = request.location.city
#state = request.location.state
#community = #city+#state
self.community = #community
end
The methods request.location.city and request.location.state do not function from the model. I know that everything else is working because if I define #city and #state as strings, under def_community, then everything works, except I don't have a dynamic variable, just a string placed in the model. Also, I know the requests are working in the controller/views, because I can get them to display the proper dynamic info. The issue is simply getting the data from the controller to the model. Thanks a lot for your time.
The concept you're wrestling with is MVC architecture, which is about separating responsibilities. The models should handle interaction with the DB (or other backend) without needing any knowledge of the context they're being used in (whether it be a an HTTP request or otherwise), views should not need to know about the backend, and controllers handle interactions between the two.
So in the case of your Rails app, the views and controllers have access to the request object, while your models do not. If you want to pass information from the current request to your model, it's up to your controller to do so. I would define your add_community as follows:
class User < ActiveRecord::Base
def add_community(city, state)
self.community = city.to_s + state.to_s # to_s just in case you got nils
end
end
And then in your controller:
class UsersController < ApplicationController
def create # I'm assuming it's create you're dealing with
...
#user.add_community(request.location.city, request.location.state)
...
end
end
I prefer not to pass the request object directly, because that really maintains the separation of the model from the current request. The User model doesn't need to know about request objects or how they work. All it knows is it's getting a city and a state.
Hope that helps.
The class instance variables (those that start with #) in the controllers are separate from those in the models. This is the Model vs the Controller in MVC architecture. The Model and Controller (and view) are separated.
You move info from a controller to a model explicitly. In Rails and other object oriented systems, you have several options:
Use function parameters
# In the controller
user = User.new(:community => #community)
# In this example, :community is a database field/column of the
# User model
Docs
Use instance variables attribute setters
# In the controller
user = User.new
user.community = #community
# same as above, :community is a database field
Passing data to models when the data is not a database field
# In the model
class User < ActiveRecord::Base
attr_accessor :community
# In this example, :community is NOT a database attribute of theĀ
# User model. It is an instance variable that can be used
# by the model's calculations. It is not automatically stored in the db
# In the controller -- Note, same as above -- the controller
# doesn't know if the field is a database attribute or not.
# (This is a good thing)
user = User.new
user.community = #community
Docs
I have a Rails 3 app that I am trying to implement devise and declarative_authorization.
An important part of declarative_authorization is the presence of a function "role_symbols" within the user model.
Because of the way I implement roles, I am implementing an instance method within the User model to keep track of a value (let's call foo) as such:
attr_accessor :foo
def foo=(val)
#foo = val
end
def foo
#foo
end
Then we will use the value of foo inside the role_symbols method to limit the valid roles, maybe like this:
def role_symbols
roles.where("foo = ?", #foo).name.underscore.to_sym
end
The issue is when I try to set the value of foo for the current_user in a controller, the value doesn't stick, for example:
current_user.foo = 99
is successful, but when I check the value in another view (or controller), the value of current_user.foo is nil.
Isn't the current_user object just a User object that is persisted in the Session?
If so, is there some lock on setting instance values within the current_user object?
Was lucky enough to find the answer here:
https://web.archive.org/web/20140113211231/http://blog.drivingthevortex.nl/2010/01/24/using-declarative_authorization-with-subdomains/