Trouble Routing to active_storage_attachments - ruby-on-rails

implementing drag and drop for active storage attachments. Some code from goRails and some from my brain/the web.
Since we are ordering attachments, I went ahead and created a model and controller to represent them since installing activestorage does not.
I am using "acts_as_list" so needed a model to add the acts_as_list hook.
So I have:
models/active_storage_attachment.rb
class ActiveStorageAttachment < ApplicationRecord
acts_as_list
end
controllers/active_storage_attachments_controller.rb
class ActiveStorageAttachmentsController < ApplicationController
before_action :set_active_storage_attachment, only: [:move]
def move
#active_storage_attachment.insert_at(params[:position].to_i)
head :ok
end
private
def set_active_storage_attachment
#active_storage_attachment = ActiveStorageAttachment.find(params[:id])
end
end
I also added a route:
resources :active_storage_atachments do
member do
patch :move
end
end
The route is simple for sortablejs to call via js for an ajax request.
The call ends up being something like this:
https://myamazonurl.com/active_storage_attachments/96/move
Rails is telling me no route exists but if I run rails routes I SEE the freaking route.
move_active_storage_attachment_path PATCH /active_storage_attachments/:id/move(.:format)
active_storage_attachments#move
I would assume this is only the beginning of my trouble but I can't sort out why the simplest part of this, the route, is not working.

Since we are ordering attachments, I went ahead and created a model and controller to represent them since installing activestorage does not.
Wrong. ActiveStorage has a model named ActiveStorage::Attachment. The model exists in the Rails gem - not in your application code. I wouldn't advise adding your own ActiveStorageAttachment model as this seems like an open invitation for bugs and confusion - especially as all the macros setup assocations to ActiveStorage::Attachment.
In some cases if you need to add a lot of buisness logic to your attachments you might actually be missing an intermediary model:
class Sortable < ApplicationRecord
has_one_attached :file
belongs_to :listable, polymorphic: true
accepts_nested_attributes_for :file
acts_as_list
delegate :service_url, # ...
to: :file
def move
#...
end
end
I would generally advise this over adding columns to active_storage_attachments and monkeypatching ActiveStorage::Attachment.
I would assume this is only the beginning of my trouble but I can't sort out why the simplest part of this, the route, is not working.
My guess is that you're not using the correct HTTP method. You want to use a POST request with the _method=PATCH parameter or a native PATCH request. Check your logs.
I would advise an integration test (request spec in RSpec) before you do the JS integration as you want to ensure that the server side works as intended before you start approaching the problem from both ends.

Related

Override gem behaviour

I'm using the acts_as_bookable gem for some basic reservation/booking stuff in a Rails app, and I need to add an additional validation to the Booking model that the gem creates.
What I mean by that is, inside the gem, located at lib/acts_as_bookable/booking.rb is the following module/class:
module ActsAsBookable
class Booking < ::ActiveRecord::Base
self.table_name = 'acts_as_bookable_bookings'
belongs_to :bookable, polymorphic: true
belongs_to :booker, polymorphic: true
validates_presence_of :bookable
validates_presence_of :booker
validate :bookable_must_be_bookable,
:booker_must_be_booker
# A bunch of other stuff
end
end
Which is fine. However, I want to add an additional piece of logic that stops a booker from booking the same instance of a bookable. Basically, a new validator.
I thought I could just add a file in my /models directory called acts_as_bookable.rb and just modify the class like this:
module ActsAsBookable
class Booking
validates_uniqueness_of :booker, scope: [:time, :bookable]
end
end
But this doesn't work. I could modify the gem itself (I've already forked it to bring a few dependencies up to date, since it's a pretty old gem) but that doesn't feel like the right solution. This is logic specific to this app's implementation, and so my gut feeling is that it belongs in an override inside this specific project, not the base gem.
What am I doing wrong here? And is there a better/alternative approach that would be more suitable?
A clean way to create monkeypatches/augmentations to objects outside of your control is to create a seperate module:
module BookingMonkeyPatch
extend ActiveSupport::Concern
included do
validates_uniqueness_of :booker, scope: [:time, :bookable]
end
end
This lets you test the monkeypatch seperately - and you can "turn the monkeypatch on" by including the module:
ActsAsBookable::Booking.include(BookingMonkeyPatch)
This can be done in an initializer or anywhere else in the lifecycle.
Altough if bookable is a polymorpic assocation you need to use:
validates_uniqueness_of :booker_id, scope: [:time, :bookable_id, :bookable_type]
The uniqueness validation does not work correctly when just passed the name of an assocation as it creates a query based on database columns. This is an example of a leaky abstraction.
See:
Justin Weiss - 3 Ways to Monkey-Patch Without Making a Mess

Extending application's model in rails engine

I have an application which defines some models. I want to extend the functionality of some models(eg. adding methods,adding associations) from application to my engine.
I tried adding a model in the engine with the same name as my application's model and Rails will automatically merge them, however it doesn't work.
eg:
(Application's model)
class Article < ActiveRecord:Base
def from_application
puts "application"
end
end
(Inside my Engine)
module MyEngine
class Article < ::Article
has_many :metrics, :class_name => 'Metric'
end
end
has_many association is not getting applied to my Articles model when I try to access #articles.metrics. Any ideas ?
You have the right idea and are close. But your implementation is a little off.
Generally, your engine should have no knowledge of your host app. That way, your engine and the host app(s) stay loosely coupled. So, classes in your engine should not inherit from classes in your host app. (BTW, your approach doesn't work, I believe, because of the way ruby does constant lookups, but that's a different discussion.)
Instead, use the included hook. In the host app, you do something like:
class Article < ActiveRecord:Base
include FooEngine::BarModule
def from_application
puts "application"
end
end
And inside the engine:
module FooEngine
module BarModule
def self.included(base)
base.class_eval do
has_many :metrics, :class_name => 'Metric'
end
end
end
end
When the ruby interpreter goes to include FooEngine::BarModule in Article, it will look for and run (if found) the self.included method in FooEngine::BarModule, passing in the Articleclass as base.
You then call class_eval on the base (Article) which re-opens the Article class so that you can add methods or whatever monkey business you're up to (define new methods in situ, include or extend other modules, etc.).
In your example, you call the has_many method, which will create the various association methods provided by has_many.
If (a) you're going to add a lot of metrics-related functionality through your engine, (b) you want to have lots of classes make use of the metrics-related functionality, and (c) you want some of the functionality to vary from class-to-class (where included), you might consider creating an acts_as_having_metrics (or similar). Once you head down this path, it's a whole new world of wondrous metaprogramming.
Best of luck.
Do you have your metrics model have a belongs_to association with Articles.
You might want to give the other side of the association, Metrics a belongs_to Articles to have this work properly. Also, make sure to have a migration to hold articles_id on the metrics table. Everything should work fine.

Rails Single Table Inheritance (STI): Use the same views for different models makes problems with form_for in 'new' action

I have a Boilerplate model which has two descending models:
BoilerplateOriginal
BoilerplateCopy
While BoilerplateOriginals is sort of a template that admins create, BoilerplateCopys are copies of the originals that are free to edit by everyone, and they also have some more associated objects (e.g. BoilerplateCopy belongs_to: BoilerplateOriginal, BoilerplateCopy belongs_to: Project or BoilerplateCopy has_many: Findings, all of which BoilerplateOriginal doesn't).
So in my opinion, it makes sense to maintain two different model classes that share the same basic functionalities.
Because they also look quite the same, I want to use the same views for them. But under the hood, they are treated a bit different, so I also have two different controllers inheriting from a base controller.
Everything seems to work fine, except that form_for boilerplate, as: resource_instance_name raises an exception undefined methodboilerplates_path', but only when called asnewaction, not when called asedit` action.
Here's what I have done so far to make it work (and everything else seems to work fine):
# config/routes.rb
resources :boilerplate_originals
# app/models/boilerplate.rb
class Boilerplate < ActiveRecord::Base
def to_partial_path
'boilerplates/boilerplate'
end
end
# app/models/boilerplate_original.rb
class BoilerplateOriginal < Boilerplate
end
# app/controllers/boilerplates_controller.rb
class BoilerplatesController < InheritedResources::Base
load_and_authorize_resource
private
def boilerplate_params
params.require(:boilerplate).permit(:title)
end
end
# app/controllers/boilerplate_originals_controller.rb
class BoilerplateOriginalsController < BoilerplatesController
defaults instance_name: 'boilerplate'
end
# app/views/boilerplates/_form.html.slim
= form_for boilerplate, as: resource_instance_name
# ...
As pointed out, new/create works flawlessly, but edit doesn't. And I'm using InheritedResources, by the way.
Rails is dong it correctly, you're slightly doing it wrong
the problem is:
resources :boilerplate_originals
that will just generate routes for especially boilerplate_originals.
when using form_helpers of rails rails will lookup for a route based on the models class which is in this case "boilerplate_copy". that means its looking for a edit_boilerplate_copy_path (which isnt generated by rails)
You said that BoilerplateCopy and BoilerplateOriginal are pretty much looking the same (i guess you just copy a model, there are gems out for doing that for you...)
If you go correct STI it "should" be
class Boilerplate; end
class BoilerplateCopy < Boilerplate; end
class BoilerplateOriginal < Boilerplate; end
for that you need only a route for Boilerplate
resources :boilerplate
and of course a boilerplate_controller
everything is handled as a boilerplate and the form_helper will look up for a new_boilerplate_path, which exists, no matter if its a copy or a original.
I found the problem.
# app/controllers/boilerplate_originals_controller.rb
class BoilerplateOriginalsController < BoilerplatesController
defaults instance_name: 'boilerplate' # This is wrong!
defaults instance_name: 'boilerplate', resource_class: BoilerplateOriginal # This is right!
end
Now a BoilerplateOriginal object is passed to the new action, and not a Boilerplate object.

Ruby on Rails Activemodel with Associations

Acutally i face some hard exercises in computer science (hard for me i think, haha).
We're doing some basic stuff with Ruby on Rails an i have to open a csv file to get additional information on my 'User' model which is a normal rails scaffold.
So at the moment i open the csv file in my users_controller.rb file and search for the right row an add them to an instance variable.
But i wonder if i can write a class that acts like an ActiveRecord Model. So i change the code to use ActiveModel. But as i read in some google results, ActiveModel can't make use of ActiveRecord like associations. But it would great to have them.
So i hope you can help me. How can i provide my model with ActiveRecors like associations?
Greetings
Melanie
It's absolutely right that the CSV file should be represented as a model, as it's data.
However, trying to incorporate Active Model sounds tricky and would almost certainly require a great deal of hacking or monkey patching.
Unless you really need associations to other models, I would create a standalone class (i.e. not inheriting from ActiveRecord::Base) in the models directory, and put the logic for parsing the CSV in there:
class User
attr_accessor :name, :email, ...
def initialize(name,email,...)
# set data
end
def self.find(param_for_search)
# Parse CSV file, find line you want
# return a User instance
self.new(name, email)
end
end
I don't know exactly how your system works, but this way you can make it behave in a similar way to Active Model stuff. You can add similar class methods and each instance method represents a CSV file row.
Every time , when you are creating your own model , it is inheritance of ActiveRecord :
class Project < ActiveRecord::Base
attr_accessible :content, :name, :user
end
Then you can tell your model to have many (let's say) Project's Tasks , which creates an association . Please , provide an example of your app's logic.
Here is a quote from RailsCasts.com :
"In Rails 3 the non-database functionality of Active Record is extracted out into Active Model. This allows you to cleanly add validations and other features to tableless models."
There is also a nice description how to add functionality in you model by adding modules .
I understand, that using ActiveRecord to use an non database source is difficult, but i think it would be vewy charming if i could write something like this:
user.worktimes.first.value
in my view and get the information like it is a database table. I visit railscast.com an i found a episode where this ist discussed. But i would like to digg deeper in this. Are there any further ressources i could read?
As i understand, ActiveModel does not support associations? I wonder why associations wasn't moved to ActiveModel as it is a very useful thing. :)
So here is my code, that i was working on:
User-Model:
class User < ActiveRecord::Base
attr_accessible :department_id, :name
belongs_to :department
end
Department-Model:
class Department < ActiveRecord::Base
attr_accessible :name
has_many :users
end
And here is my CSV Model, that i created:
class Worktime
attr_accessor :user_id,:date,:value
def initialize(params)
dir = Rails.root.join('app', 'models', 'worktimes.csv').to_s
source = File.open(dir,'r')
while(line=source.gets)
data = line.split(';')
if data[0] = params[:user_id] && data[1] = params[:date]
#value = data[2]
end
end
end
end
I am very thankful for your help as its my first time using rails.

Same Controller/View for multiple resources

I have a workflow app written in Rails and there are a number of prompts which the user will go through. When these prompts/manual actions are "proceeded" a time stamp is stored against the a workflow model.
Code looks something like so
class Workflow < ActiveRecord::Base
end
class OpenBook < ActiveRecord::Base
belongs_to :workflow, :polymorphic => true
end
class ReadFirstPage <
belongs_to :workflow, :polymorphic => true
end
At the moment each workflow item ( OpenBook ) and spits out a bit of text (set in the model) and a forward / backward button on the view. The controllers for these workitems also do the similar things (new, done, edit, proceed, rewind).
Ideally I would like to just use the same controller and views and just change the model. Is theer something I can do with the routes eg?
resources :open_books, :controller => "workflow_item"
Im not sure how I would go about getting the correct assignments in the controller.
Or am I just doing this completely wrong and I should be using helpers?
You might have an easier time using a series of controllers for each specific object type that inherit from a common parent. That way most of the duplication can be in the base class where only the differences are expressed in the children.
To ensure your templates are rendered correctly, you may have to specify the full path to them or it will look for customized versions:
def index
# ...
render(:template => 'parent/index')
end
Usually this is redundant, but in your case you may need it or it will default to showing the non-existent child-template first.

Resources