Rails STI: transfer records from one model to another - ruby-on-rails

I have a model called Coupons
Then I have two child models CouponApplicationsand ApprovedCoupons.
The last two inherit from Couponsvia an STI architecture.
Now I want to realise the following:
A user sees CouponApplications
He clicks on an Approve button which makes the CouponApplications ApprovedCoupons
I realise that I could simply update the typecolumn of the Couponsrecord to change types. However, there are several Concerns, hooks etc in the ApprovedCoupons model which are happening after creation so this is not so easy. In fact, I want to create a complete new record to trigger those Concerns, hooks etc.
So I wrote this which I consider really bad:
#coupon_application = CouponApplication.find(params[:id])
#approved_coupon = ApprovedCoupon.new
# copy/paste attributes except the ID as this would be considered a duplication
#approved_coupon.attributes = #coupon_application.attributes.except("id")
# set the new type
#approved_coupon.update_attributes(type: "Advertisement")
#approved_coupon.save
I hope you understand what I want to achieve. It works this way but I doubt this is clean code.
To summarize:
I want to change the Coupontype from CouponApplication to
ApprovedCoupon
I still want to trigger the Concerns, hooks etc. in my ApprovedCoupon model so I decided to create a new ApprovedCoupon
record instead of just changing the type.
Is there any better approach?

You could add an approve method to your CouponApplication model like this:
class CouponApplication < Coupon
...
def approve
data = attributes.except('id', 'created_at', 'updated_at')
ApprovedCoupon.create(data.merge(type: 'Advertisement'))
end
end
Now your code can be simplified to this:
#coupon_application = CouponApplication.find(params[:id])
#approved_coupon = #coupon_application.approve

Related

Can two or more inter-related models have one common controller in rails

am a newbie in rails and I want your help. I want to build a simple project in rails that includes more than one model that are inter-related. So in this case, is it possible to create only one controller that corresponds to all the models or should I create a controller for each model? And please I want reasons for your answers. Thank you.
Of course..
You could easily have something like:
#animals_controller.rb
def animals
#dogs = Dog.all
#cats = Cat.all
#cheetahs = Cheetah.all
end
BUT...
Imagine having an update, edit, new, show, create and destroy for each of these models in the same controller!!
That would be a mess - and you would end up with a complex file that would be (mildly said) annoying to maintain.
Therefore, for your own sake, and because it is good practice, you normally create a corresponding controller for each of your models(animals): model: dog.rb, controller: dogs_controller.rb, model: cat.rb controller: cats_controller.rb etc...
so you, after a few months abroad, can come back and continue your work without thinking What the ....
I would recommend looking into the MVC (for example on youtube) and really understanding how it connects the database with your view and so on.
Understand the data-flow and you will find yourself with a powerfull toll later on when you're debugging, construction forms, creating ajax-calls, custom attributes and so on.
ous,
You need to create a controller for each model in order to have CRUD options. It depends on what the models will do if you don't want a controller, for example if model just does some calculations and you don't really need to pass data to your view then you might not need it.

How to implement edit action in controller if I don't yet have an ActiveRecord object

I am developing my first Rails application, which tracks job statuses for a job shop. I have the following models:
class Job < ActiveRecord::Base
has_many :parts
...
end
class Part < ActiveRecord::Base
belongs_to :job
end
I would like to have a page with a form that accepts a scanned barcode as input. It would find the Part object in the DB using the barcode ID and would set the Part.completed_at attribute to the current time.
I am trying to keep everything as RESTful as possible, and so at first it would seem like this page should be a view for the edit action in the Parts controller. However, in this view I don't yet have the Part object. What is the best way to implement this functionality? Should it be a new action in the Parts controller called something like find_and_update? Thanks in advance for steering me in the right direction.
You can implement a new non-RESTful action called find_and_update, that will work just fine.
If you want to keep it RESTful:
1) you could have a PartCompletions controller, that has a create action that accepts an ID. That is to say, when you submit a barcode, you are "creating" a "part completion". One of the light-bulb moments people have learning REST with Rails is when they learn that your RESTful controllers don't have to map 1-to-1 with your models / database tables. You can create new abstractions to help stick to the RESTful verbs (create, read, update, delete).
2) If you didn't want to create an extra controller, you could use the update method of your existing PartsController. That is to say, you're "updating" a "part" and giving it some new values (in this case a new completed_at date). You'd pass it the id from the barcode reader, and also pass it a hash with the new values you want to change (ie, that'd mean passing in the completed at field with the current time in your post request, rather than setting it in your controller action)
Either of these two approaches can be fine - as is the non-RESTful option, too - it depends on how big your app is, how many other non-RESTful actions you're likely to end up having if you don't strictly follow REST, etc. The smaller and more self-contained your app is, the more it's OK to break REST and other conventions/patterns in order to save time and keep it simple up front. My advice: don't spend too long agonising over which approach is the best. Pick one, and write code. You can re-factor it later.

What's the convention in decoupling components in Rails

Using rails generate model, I created two models/tables, policeman and policewoman. They are lists of officers with many attributes (age, time in the field, case solved, etc.). If I wanted to do a computation that determines who should get the next promotion to Sargent what is the conventional way to do it in rails?
I would want to create a new class just to deal with this situation, and hide all the complex computation (compare attributes between both lists) away from the caller. So maybe in a controller, say Captain, under the method show I would do
class Captain < ApplicationController
def show
promotion = Promotion.new
#ideal_sargent = promotion.sargent(Policeman.find(:all),Policewoman.find(:all))
end
end
Where do I create this Promotion class? Do I use rails generate controller to make it? Or maybe make a gem for it? Or even put it all in a model (I hear thin controllers fat models)?
EDIT:
Maybe this? If so, how do I create a model without a migration file being automatically made?
Your general idea of decoupling it from models and from controllers is a good one. Although you confuse it a bit with controllers, generators and gems...
What you want to do is:
introduce a service object which is plain ruby object
put the logic for calculating promotion order inside it
have it completely decoupled from controller and loosely coupled to police officers models
The interface to use it would be basically as you have already described:
# prepare some officers to choose from
officers = [PoliceWoman.find(1)] + PoliceMan.all
# returns the first ranking officer for promotion
to_be_promoted = SargentPromotionService.new.find_top_candidate(*officers)
Where to put this service model? I suppose it contains application specific logic, that isn't really useful outside of application. Therefore we put it in the app folder. But where in the app?
A good practice is to setup the app/domain folder. There you can put all the app specific domain models (service, policy, value objects, etc...). All you need to setup this folder is add it to the autoload paths inside the config/application.rb:
config.autoload_paths += %W(#{config.root}/app/domain)
This way you have a clear separation of rails models (take care of persistence) and domain models - where you should put most of the application specific code. If your app is really simple and small you could also skip the app/domain folder and just use the app/models. And if you follow the approach to use plain ruby objects with loose coupling, you will get easily testable, maintainable, flexible and reusable code. =)
For this purpose, I wouldn't create two tables to modeling the data. I would use a single table named polices, and keep a column as gender and another one as rank to differ policeman and policewoman, and different rank. Then I would put promote function as a class method inside the Police modal.
class Police < ActiveRecord::Base
def self.promote(param1, param2)
....
end
end
In this way you can incapsulate the business logic inside the promote function, the caller can invoke it without knowing any complex computation in it. Police.promote(a,b)

Which MVC layer should handle a child claiming a toy?

I have two models in my app: a child that has_many :toys and a toy that belongs_to :child. I made the db migration I needed for this to work (added child_id to toys table).
At first, children exist on their own, and toys exist on their own (with no association). At the start of every day in kindergarten, no child owns any toys. To play with a toy, a child must claim it first, and become its owner. So, now I need to somehow implement a child.claim(toy) method, and here I get stuck. Specifically:
Should this go into the child controller or model? Or maybe it should be somehow split between both?
If it should go into the controller, should it correspond to one of the CRUD actions or be its own thing as def claim(toy)?
Edit 1: The child is the user and is logged on via the browser. (Today's kids can do amazing things)
Actually you don't need a claim method if child is the user. You can have a claim_toy method in your controller. In you toys index view for each toy you can give a link as follows.
<%= link_to "claim", claim_toy_path(:toy_id => toy.id) %>
Your controller method will look something like this.
def claim_toy
toy = Toy.find(params[:toy_id])
current_child.toys << toy
end
simple enough. This is not a restful solution by the way.
A Child is a Foreign Key on Toy
There are certainly other ways to do this, but based on your original question, the simplest solution is to make your behavior consistent with the fact that a child is associated with a toy in the Toy table.
Simplest Solution
Setting aside arguments about what one should do in a perfect OOP/MVC design, the sensible place to make the change is in a controller since when a child claims a toy, the claim is processed by a an #update (or perhaps even a #claim) action on the Toy controller.
The child is the user and is logged on via the browser.
In your case, the session already knows who the child is, so adding the foreign key to the Toy model is trivial. The controller is the entity that receives the params for the associated model, so it's the correct place to tell the Toy model what attributes to update for a given toy.
There are certainly more complicated solutions, but they are of dubious merit based on the information provided in your original post. As always, your mileage may vary.
I'd build a separate class handling all the logic regarding a child and a toy. Call it context, call it concern, but do it.
class ToyChild # or ToyInteraction or ChildContext::Toys...
attr_reader :toy, :child
def initialize(toy, chid)
#toy = toy
#child = child
end
def associate
toy.child = child
# could be more difficult: you should check if the child has not enough toys, if the toy is not already assigned to another child etc...
#I'd avoid saving here since you may want to perform other operations
end
def foo
#code here
end
end
And in controller:
assoc = ToyChild.new(toy, child).associate
assoc.save
This style of coding:
is easier to test
splits responsibilities clearly
keeps things dry (no specific code in controllers)

What is the proper way to validate the type when using single table inheritance (STI)?

I am trying to use single table inheritance for some of my models. The base model is a Tournament, and I wish to extend this to create different types of tournaments. For instance, I might want to add a SingleEliminationTournament, or a DoubleEliminationTournament, both of which would inherit from Tournament. I have 2 questions, both of them somewhat related.
1) I would like the user to be able to create tournaments with a form, and to do this they would need to select one of the subclasses. Is there a way to get all of the subclasses and use them to populate a select box or something like that?
2) Since this information is going into a form, it would be nice to be able to validate the input into type. To do this, I would like to add a validation in the Tournament class that could check to make sure the Type was valid.
Obviously, I could hard code the values into the validation and the form, but I would not like to do that. Any help would be appreciated. Thanks!
TheModel.subclasses
would give you a list of types you need to include, but only if the models are loaded at runtime. They will always be loaded in production mode. You will have to load them manually in development mode.
You could create a directory with tournaments in them and load them with Dir.glob('app/tournaments/**/*_tournament.rb'). This gives you a nice listing all the tournament files you've specified. Because of convention, you can then infer the proper class name for each tournament.
Store this list of tournament names somewhere for reference in you validations and forms.
I'm not a Rails expert and I'm not sure if this can be considered clean, but for the validation part of your question, this worked for me:
Inside Tournament model:
def validate_type_implemented
klass = type.constantize rescue Object
raise "Given type not available." unless klass.class == Class and klass <= self.class
end

Resources