Rails - Creating Associated Object with URL params - ruby-on-rails

I’m wanting to create an Asset from an Organisation Show page. I need the Organisation ID to be passed to the Asset for creation.
# ../models/organisation.rb
has_many :assets
# ../models/asset.rb
belongs_to :organisation
I have a button to create an asset, which is passing the organisation.id in the URL params.
# ../views/organisations/show.html.erb
<%= link_to 'New Asset', new_asset_path(:organisation_id => #organisation.id) %>
I'm able to access the organisation_id value in the ../views/assets/new view, however, when I submit the form I'm receiving a “Method not allowed” error.
# ../controllers/assets_controller.rb
def create
#asset = Asset.new(params[:organisation_id])
...
end
Q1: Why am I getting this error:
Q2: Is there another way to pass the organisation_id through to the Asset new page? I.e. not in the URL.
Q3: If I can only pass the ID through the URL, Is there a way to stop a user tampering with it? I.e. changing the Org ID to something else and saving the asset to another organisation.
Q4: Would nested resources help in this instance?
Rails 5.0.0.1, Ruby 2.3.1

You need to tell Asset.new what the organisation_id param is for eg:
#asset = Asset.new(:organisation_id => params[:organisation_id])
otherwise you're just passing it a random number and it's going "wtf is this?" :D
(well, technically it's looking for a version of the new method that takes a single argument that isn't a hash... which it can't find, then telling you that method doesn't exist... but same diff). ;)

I think you should use like:
def create
#asset = Asset.new(organisation_id:params[:organisation_id])
end

Q.1 - The error is due to the name of the model - "Assets". This is a reserved word in Rails.
It can be fixed by moving the asset pipeline to another mount point. For example:
# config/initializers/assets.rb
Rails.application.config.assets.prefix = '/pipeline_assets'
As mentioned here:
405 Error when trying to POST a file in rails
Rails 4 Method Not Allowed after Upgrading from Rails 3

Related

How can I get url of my attachment stored in active storage in my rails controller

How can I get url of my has_one model attachment stored in active storage in my rails controller. So, that I would be able to send it as full link as api in json.
So far, I have tried following methods but each of them are giving various issues:
current_user.image.service_url ---- undefined method `service_url' for #<ActiveStorage::Attached::One:0x....
Rails.application.routes.url_helpers.rails_disk_blob_path(current_user.image, only_path: true), it gives me an output like:
"/rails/blobs/%23%3CActiveStorage::Attached::One:0x007f991c7b41b8%3E"
but this is not a url, right? I am not able to hit and get image on browser.
url_for ----
undefined method `active_storage_attachment_url' for #<Api::V1::UsersController:0x007f991c1eaa98
Use the method rails_blob_path for attachements in a controller and models
For example, if you need to assign a variable (e.g. cover_url) in a controller, first you should include url_helpers and after use method rails_blob_path with some parameters. You can do the same in any model, worker etc.
Complete example below:
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
def index
#event = Event.first
cover_url = rails_blob_path(#event.cover, disposition: "attachment", only_path: true)
end
end
Sometimes, e.g. an API needs to return the full url with host / protocol for the clients (e.g. mobile phones etc.). In this case, passing the host parameter to all of the rails_blob_url calls is repetitive and not DRY. Even, you might need different settings in dev/test/prod to make it work.
If you are using ActionMailer and have already configuring that host/protocol in the environments/*.rb you can reuse the setting with rails_blob_url or rails_representation_url.
# in your config/environments/*.rb you might be already configuring ActionMailer
config.action_mailer.default_url_options = { host: 'www.my-site.com', protocol: 'https' }
I would recommend just calling the full Rails.application.url_helpers.rails_blob_url instead of dumping at least 50 methods into your model class (depending on your routes.rb), when you only need 2.
class MyModel < ApplicationModel
has_one_attached :logo
# linking to a variant full url
def logo_medium_variant_url
variant = logo.variant(resize: "1600x200>")
Rails.application.routes.url_helpers.rails_representation_url(
variant,
Rails.application.config.action_mailer.default_url_options
)
end
# linking to a original blob full url
def logo_blob_url
Rails.application.routes.url_helpers.rails_blob_url(
logo.blob,
Rails.application.config.action_mailer.default_url_options
)
end
end
I didn't have used rails active storage but what i have read in documentation this might help you
Try rails_blob_url(model.image)
For more http://edgeguides.rubyonrails.org/active_storage_overview.html
I was able to view the image in the browser using the following:
<%= link_to image_tag(upload.variant(resize: "100x100")), upload %>
Where upload is an attached image.

Rails force to_param to return something even when not persisted

I need to handle a particular case of generating email views with URLs constructed from non-persisted data.
Example : assume my user can create posts, and that triggers a post creation notification email, I'd like to send the user an example of fake post creation. For this, I am using a FactoryGirl.build(:post) and passing this to my PostMailer.notify_of_creation(#post)
In everyday Rails life, we use the route url_helpers by passing as argument the model itself, and the route generator will automatically convert the model into its ID to be used for the route URL generation (in article_path(#article), the routes helper converts #article into #article.id for constructing the /articles/:id URL.
I believe it is the same in ActiveRecord, but anyways in Mongoid, this conversion fails if the model is not persisted (and this is somewhat nice as it prevents the generation of URLs that may not correspond to actual data)
So in my specific case, URL generation crashes as the model is not persisted:
<%= post_url(#post_not_persisted) %>
crashes with
ActionView::Template::Error: No route matches {:action=>"show", :controller=>"posts", :post_id=>#<Post _id: 59b3ea2aaba9cf202d4eecb6 ...
Is there a way I can bypass this limitation only in a very specific scope ? Otherwise I could replace all my resource_path(#model) by resource_path(#model.id.to_s) or better #model.class.name but this doesn't feel like the right situation...
EDIT :
The main problem is
Foo.new.to_param # => nil
# whereas
Foo.new.id.to_s # => "59b528e8aba9cf74ce5d06c0"
I need to force to_param to return the ID (or something else) even if the model is not persisted. Right now I'm looking at refinements to see if I can use a scoped monkeypatch but if you have better ideas please be my guest :-)
module ForceToParamToUseIdRefinement
refine Foo do
def to_param
self.class.name + 'ID'
end
end
end
However I seem to have a small scope problem when using my refinement, as this doesn't bubble up as expected to url_helpers. It works fine when using te refinement in the console though (Foo.new.to_param # => 59b528e8aba9cf74ce5d06c0)
I found a way using dynamic method override. I don't really like it but it gets the job done. I am basically monkeypatching the instances I use during my tests.
To make it easier, I have created a class method example_model_accessor that basically behaves like attr_accessor excepts that the setter patches the #to_param method of the object
def example_model_accessor(model_name)
attr_reader model_name
define_method(:"#{model_name}=") do |instance|
def instance.to_param
self.class.name + 'ID'
end
instance_variable_set(:"##{model_name}", instance)
end
end
Then in my code I can just use
class Testing
example_model_accessor :message
def generate_view_with_unpersisted_data
self.message = FactoryGirl.build(:message)
MessageMailer.created(message).deliver_now
end
end
# views/message_mailer/created.html.erb
...
<%= message_path(#message) %> <!-- Will work now and output "/messages/MessageID" ! -->

Create Rails model with argument of associated model?

I have two models, User and PushupReminder, and a method create_a_reminder in my PushupReminder controller (is that the best place to put it?) that I want to have create a new instance of a PushupReminder for a given user when I pass it a user ID. I have the association via the user_id column working correctly in my PushupReminder table and I've tested that I can both create reminders & send the reminder email correctly via the Rails console.
Here is a snippet of the model code:
class User < ActiveRecord::Base
has_many :pushup_reminders
end
class PushupReminder < ActiveRecord::Base
belongs_to :user
end
And the create_a_reminder method:
def create_a_reminder(user)
#user = User.find(user)
#reminder = PushupReminder.create(:user_id => #user.id, :completed => false, :num_pushups => #user.pushups_per_reminder, :when_sent => Time.now)
PushupReminderMailer.reminder_email(#user).deliver
end
I'm at a loss for how to run that create_a_reminder method in my code for a given user (eventually will be in a cron job for all my users). If someone could help me get my thinking on the right track, I'd really appreciate it.
Thanks!
Edit: I've posted a sample Rails app here demonstrating the stuff I'm talking about in my answer. I've also posted a new commit, complete with comments that demonstrates how to handle pushup reminders when they're also available in a non-nested fashion.
Paul's on the right track, for sure. You'll want this create functionality in two places, the second being important if you want to run this as a cron job.
In your PushupRemindersController, as a nested resource for a User; for the sake of creating pushup reminders via the web.
In a rake task, which will be run as a cron job.
Most of the code you need is already provided for you by Rails, and most of it you've already got set in your ActiveRecord associations. For #1, in routes.rb, setup nested routes...
# Creates routes like...
# /users/<user_id>/pushup_reminders
# /users/<user_id>/pushup_reminders/new
# /users/<user_id>/pushup_reminders/<id>
resources :users do
resources :pushup_reminders
end
And your PushupRemindersController should look something like...
class PushupRemindersController < ApplicationController
before_filter :get_user
# Most of this you'll already have.
def index
#pushup_reminders = #user.pushup_reminders
respond_with #pushup_reminders
end
# This is the important one.
def create
attrs = {
:completed => false,
:num_pushups => #user.pushups_per_reminder,
:when_sent => Time.now
}
#pushup_reminder = #user.pushup_reminders.create(attrs)
respond_with #pushup_reminder
end
# This will handle getting the user from the params, thanks to the `before_filter`.
def get_user
#user = User.find(params[:user_id])
end
end
Of course, you'll have a new action that will present a web form to a user, etc. etc.
For the second use case, the cron task, set it up as a Rake task in your lib/tasks directory of your project. This gives you free reign to setup an action that gets hit whenever you need, via a cron task. You'll have full access to all your Rails models and so forth, just like a controller action. The real trick is this: if you've got crazy custom logic for setting up reminders, move it to an action in the PushupReminder model. That way you can fire off a creation method from a rake task, and one from the controller, and you don't have to repeat writing any of your creation logic. Remember, don't repeat yourself (DRY)!
One gem I've found quite useful in setting up cron tasks is the whenever gem. Write your site-specific cron jobs in Ruby, and get the exact output of what you'd need to paste into a cron tab (and if you're deploying via Capistrano, total hands-off management of cron jobs)!
Try setting your attr_accessible to :user instead of :user_id.
attr_accessible :user
An even better way to do this however would be to do
#user.pushup_reminders.create
That way the user_id is automatically assigned.
Use nested routes like this:
:resources :users do
:resources :pushup_reminders
end
This will give you params[:user_id] & params[:id] so you can find your objects in the db.
If you know your user via sessions, you won't need to nest your routes and can use that to save things instead.
Using restful routes, I would recommend using the create action in the pushup_reminders controller. This would be the most conventional and Restful way to do this kind of object creation.
def create
#user = User.find(params[:user_id]
#reminder = #user.pushup_reminders.create()
end
If you need to check whether object creation was successful, try using .new and .save

Rails model create function

I used the nested model gem to create a Picture that can take tags. Now I have added an attribute to my model Picture so it has an attribute taglist. When I create a new tag, I want this to happen
class TagsController < ApplicationController
def create
#tag = Tag.new(params[:id])
if #tag.save
taglist = picture.taglist
taglist+=#tag.tagcontent
#tag.picture.update_attributes(:taglist => taglist)
end
end
end
and in my routes
resources :pictures do
resources :tags
end
When i make a new tag, nothing happens in the taglist attribute, like nothing happened, why?
It's hard to help due to lack of information, but I see two possible issues:
Tag.new(params[:id]) doesn't make sense. Assuming Tag inherits from ActiveRecord::Base, you need to pass it a hash of attributes (e.g. Tag.new(:name => 'mytag')) You are likely not getting into the if #tag.save block at all due to validation errors. Also, you don't need to provide an id to an object you want to create. The database chooses the id.
Inside the block, picture is undefined on the first line.
Why not try debugging with something like:
if #tag.save
taglist = picture.taglist
taglist+=#tag.tagcontent
#tag.picture.update_attributes(:taglist => taglist)
else
p "ERRORS:"
p #tag.errors.full_messages
end
See what errors that prints out into your console.
I definitely think that picture is probably undefined in the create method of the controller. Can you show us the view, the form you're using to create a new tag? Is there a form field through which you're choosing which photo gets the tag?
Please show us the association and your view for creating the new tag.
Actually, what I'd really recommend instead of cooking up your own is to use:
Agile Web Development's acts_as_taggable_on_steroids
It's an excellent plugin to make tagging easy; it has quite a few nifty features built in, including the searches, tag clouds, etc. We use it on our projects.

rails Getting the parameter from the URL

I have a rails application that I want to add attachments to assets, so I want to be able to call http://localhost:3000/attachments/:asset_id/new so that that it will automatically bring in the asset id. I don't know how to configure this in views, though I think I did it once-upon-a-time. So how could I accomplish this task?
As far as I have got so far, and I believe this is correct is adding the following line to routes.rb:
match 'attachments/:asset_id/new'=>'attachments#new'
Note: This is a Rails 3 Application.
You could do it the RESTful way like so:
resources :assets do
resources :attachments # this would give you localhost:3000/assets/:asset_id/attachments/new for your #new action
end
or the non-RESTful way:
match 'attachments/:asset_id/new'=>'attachments#new', :as => "new_attachments_asset"
I'd recommend the former ;) For the restful example, your Attachment#new action could be:
def new
#asset = Asset.find(params[:asset_id])
#attachment = #asset.attachments.build # assuming a has_many/belongs_to association
end
http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions

Resources