I want to run methods like Render and flash from model file inside rails. My code is this:
class Product < ActiveRecord::Base
attr_accessible :name, :price, :released_on
validates :name, uniqueness: true
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
def self.import(file)
CSV.foreach(file.path , headers:true) do |row|
#product=Product.new(row.to_hash)
if #product.valid?
#product.save
flash[:notice] = "Product created!"
redirect_to(#product) and return
else
redirect_to action: :index
end
end
end
end
When I run this and enter model I got an error that Flash method . Similarly Render Method not defined. any guesses.
Short answer: You don't.
Longer answer: You're trying to do things in a way that don't suit the Rails way of working. There is a very solid separation between the things that display and the data underlying those things.
You need to rethink how these things fit together. The Controller is responsible for dealing with the models and then preparing the information for display by the View. Rather than create the flash in the model, use the controller to find out if the product was created and allow it to display the flash.
Related
I want to have two input fields, but only one column in the database.
The first input is stored data in numbers and the other one is stored data in numbers divided by 24.
You can put data only in one field.
Is there any possible way to do this?
UPD:
Migration:
def change
add_column :employees, :hourly_payment , :integer
end
View:
employees/_form.html.erb
<%= simple_form_for #employee do |form| %>
<%= form.input :name %>
<%= form.input :hourly_payment %>
<%= form.input :monthly_payment %>
<%= form.button :submit, class: "btn btn-success" %>
<% end %>
Your model and database tables are the internals of your application and are not actually tied to the view by anything except how easy ActiveRecord makes it to use convention over configuration to link the two*.
In Rails style MVC the controller is responsible for passing user input to the model. Usually you would just do this with simple mass assignment:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user)
.permit(:email, :salary)
end
end
This is basically just passing a whitelisted hash of parameters straight to the model as is and it all gets passed to the setters that ActiveRecord magically created for you by reading the database schema.
But there is nothing stopping you from assigning attributes manually:
class UsersController < ApplicationController
def create
#user = User.new(user_params) do |user|
user.salary = calculated_salary
end
# ...
end
private
def user_params
params.require(:user)
.permit(:email)
end
def calculated_salary
if params[:user][:hourly_payment].present?
params[:user][:hourly_payment]
elsif params[:user][:monthly_payment].present?
params[:user][:monthly_payment].to_i / 168
else
0 # sorry no cookies for you
end
end
end
Or monkeying with the parameters object:
def user_params
params.require(:user)
.permit(:email)
.merge(salary: calculated_salary)
end
It is after all just a hash on steroids. The only thing that Rails will prevent you from is passing a parameters object that has not been whitelisted.
There is no stone tablet for what you can do in a controller. The only thing to bear in mind is that controllers are notoriously hard to test and fat controllers are a recipe for disaster.
If you're doing anything more complicated there are better solutions such as form objects, decorators or service objects.
You'll need to create a view for that. Here is an example of migration:
def up
sql = %(CREATE OR REPLACE VIEW my_view_models AS
SELECT m.*,m.int_field*12 as my_new_value from my_models m
)
self.connection.execute(sql)
end
def down
self.connection.execute('DROP VIEW IF EXISTS my_view_models')
end
Then, you can access your value with method my_new_value on your model. You'll need to change the name of the default table matching your model.
class MyModel
self.table_name = 'my_view_models'
end
And access it via
MyModel.first.my_new_value
I'm getting the following error when trying to export to a csv:
undefined method `dd_export' for #<Array:0x00007fc4836f1798>
I think it's the relationship between the model and controller, but can't work out why this is. I'm using a custom dd_export convention as more csv download may be added, and also using to_csv doesn't seem to export what's required in my model.
day_degree_export.rb
class DayDegreeExport < ApplicationRecord
def self.dd_export
attributes = %w{Date Min_Temp Max_Temp}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |dd|
csv << [
dd['date'],
dd['variables'][1]['value']
dd['variables'][0]['value']
]
end
end
end
end
end
day_degree_export_controller.rb
class DayDegreeExportController < ApplicationController
dd_results
data = HTTParty.get("url_link")
#ddexport = JSON.parse(data.body)
respond_to do |format|
format.html
format.csv { send_data #ddexport['data'].dd_export, filename: "ddexport.csv" }
end
end
end
I have required csv in my application.rb.
If you want to call #ddexport['data'].dd_export, you'd have to patch the Array class for dd_export, but a better way to implement it is to save your array as DayDegreeExport records.
class DayDegreeExportController < ApplicationController
def dd_results
data = HTTParty.get("url_link")
#ddexport = JSON.parse(data.body)
DayDegreeExport.create(#ddexport) # you can do this if your #ddexport is array of hashes with matching keys
respond_to do |format|
format.html
format.csv { send_data DayDegreeExport.dd_export, filename: "ddexport.csv" }
end
end
end
Since you are not intending to store #ddexport['data'], then there is no need for DayDegreeExport to inherit from ApplicationRecord. Instead, I would suggest you do something like:
# app/services/day_degree_export_service.rb
class DayDegreeExportService
CSV_ATTRIBUTES = %w(
Date
Min_Temp
Max_Temp
).freeze
attr_accessor *%w(
args
).freeze
class << self
def call(args={})
new(args).call
end
end
def initialize(args)
#args = args
end
def call
CSV.generate(headers: true) do |csv|
csv << CSV_ATTRIBUTES
dd_export_data.each do |dd|
csv << [
dd['date'],
dd['variables'][1]['value']
dd['variables'][0]['value']
]
end
end
end
private
def dd_export_data
#dd_export_data ||= JSON.parse(HTTParty.get("url_link"))['data']
end
end
Which you would use something like:
class DayDegreeExportController < ApplicationController
def dd_results
respond_to do |format|
format.html
format.csv { send_data DayDegreeExportService.call, filename: "ddexport.csv" }
end
end
end
Now, a few comments:
1) I never use CSV so I have no idea whether the code is going to work. I took a look at the docs and I think this may be close, but you may have to fiddle with it.
2) A PORO (plain old ruby object) gets the job done here. You can see I suggest you place it in a services directory and rails will pick it up automatically.
3) In the class-level call method, I include optional args. That's just for example purposes. Some other time you might want to pass in arguments.
4) For services, I like to use the call method instead of service-specific methods like dd_results. It's just a me thing. But, then, you don't have to think about what to call your methods in your service. As long as your services and their purpose have a 1:1 relationship, then the class name describes what the service does instead of the method name.
5) Using class << self creates class methods. It's basically the same as doing def self.call.
6) This all assumes, I supposed, that JSON.parse(HTTParty.get("url_link"))['data'] returns an array of data and that each datum has the keys that you indicate.
Having followed the RailsCast on importing CSV (http://railscasts.com/episodes/396-importing-csv-and-excel), I am trying to validate that the file being uploaded is a CSV file.
I have used the gem csv_validator to do so, as documented here https://github.com/mattfordham/csv_validator
And so my model looks like this:
class Contact < ActiveRecord::Base
belongs_to :user
attr_accessor :my_csv_file
validates :my_csv_file, :csv => true
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |contact|
csv << contact.attributes.values_at(*column_names)
end
end
end
def self.import(file, user)
allowed_attributes = ["firstname","surname","email","user_id","created_at","updated_at", "title"]
CSV.foreach(file.path, headers: true) do |row|
contact = find_by_email_and_user_id(row["email"], user) || new
contact.user_id = user
contact.attributes = row.to_hash.select { |k,v| allowed_attributes.include? k }
contact.save!
end
end
end
But my system still allows me to select to import non-CSV files (such as .xls), and I receive the resulting error: invalid byte sequence in UTF-8.
Can someone please tell me why and how to resolve this?
Please note that I am using Rails 4.2.6
You can create a new class, let's say ContactCsvRowValidator:
class ContactCsvRowValidator
def initialize(row)
#row = row.with_indifferent_access # allows you to use either row[:a] and row['a']
#errors = []
end
def validate_fields
if #row['firstname'].blank?
#errors << 'Firstname cannot be empty'
end
# etc.
end
def errors
#errors.join('. ')
end
end
And then use it like this:
# contact.rb
def self.import(file, user)
allowed_attributes = ["firstname","surname","email","user_id","created_at","updated_at", "title"]
if file.path.split('.').last.to_s.downcase != 'csv'
some_method_which_handle_the_fact_the_file_is_not_csv!
end
CSV.foreach(file.path, headers: true) do |row|
row_validator = ContactCsvRowValidator.new(row)
errors = row_validator.errors
if errors.present?
some_method_to_handle_invaid_row!(row)
return
end
# other logic
end
end
This pattern can easily be modified to fit your needs. For example, if you need to have import on several different models, you could create a base CsvRowValidator to provide basic methods such as validate_fields, initialize and errors. Then, you could create a class inheriting from this CsvRowValidator for each model you want, having its own validations implemented.
I am trying to generate a CSV output with data from my database. I would like to provide these data to a third party, so I picture I would give to someone a URL (website.com/api_data/cars) and by accessing this URL the person would be able to work with it - I think I want to access the URL and then to see there (in the action) data displayed and separated by , or ;.
But how to do that?
So far, I am trying following approach:
csv_string = CSV.generate do |csv|
cols = ["column one", "column two", "column three"]
csv << cols
csv << ["A", "B", "C"]
#filename = "data-#{Time.now.to_date.to_s}.csv"
end
send_data(csv_string, :type => 'text/csv; charset=utf-8; header=present', :filename => #filename)
This is in the controller generate_data and action csv_cars.
When I run this action (webste.com/generate_data/csv_cars), it will automatically pop up a window to download the file.
But how to write the CSV content to the action? So when I open the URL, I'll see there written the content from the database?
I know this is an old thread but I came across it in my search so in case someone else does the same, here's my answer and what worked for me.
I think bhanu had a good way of going about it but I did change something. Instead of doing #cars within the respond_to, I just called send_data Cars.to_csv since, as Rob stated, it was made as a class method. It worked beautifully for me.
class Car < ActiveRecord::Base
def self.to_csv(make)
attributes = %w{id name price} #customize columns here
cars = Car.where(maker_name: make)
CSV.generate(headers: true) do |csv|
csv << attributes
cars.each do |car|
csv << attributes.map{ |attr| car.send(attr) }
end
end
end
end
And then in the controller
class CarsController < ApplicationController
def index
send_data Cars.to_csv('Chevy'), filename: "cars-#{Date.today}.csv"
end
end
I understand that this will be called when you go to cars/index but you can put that into any method, if statement or anything you want and just have it called whenever you would like from there. You can also have arguments, as I did above with make, and query for certain fields. It was definitely a lot easier than I thought it was going to be. Hope this helped someone.
You need to do something like this.
def csv_cars
headers = ['column one', 'column two', 'column three']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
csv << ["A", "B", "C"]
end
send_data csv_data, filename: "data-#{Date.today.to_s}.csv", disposition: :attachment
end
define a to_csv method in your model as shown below
class Car < ActiveRecord::Base
def self.to_csv
attributes = %w{id name price} #customize columns here
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |car|
csv << attributes.map{ |attr| car.send(attr) }
end
end
end
end
Later in your controller
class CarsController < ApplicationController
def index
#cars = Car.all
respond_to do |format|
format.html
format.csv { send_data #cars.to_csv, filename: "cars-#{Date.today}.csv" }
end
end
end
I'm not quite sure what I am overlooking when attempting to allow csv download on my Game model and I'm getting a little lost.
On the profile show page I render an index like list of games associated with that user, i.e. their game schedule.
The Profiles Controller-
def show
#user = User.find_by_profile_name(params[:id])
if #user
#listings = #user.listings
#games = #user.games
respond_to do |format|
format.html
format.csv {send_data #games.to_csv}
end
return
render action: :show
else
render file: "public/404", status: 404, formats: [:html]
end
end
Then in the game.rb I define the method to_csv
def self.to_csv
CSV.generate do |csv|
csv << column_names
all.each do |item|
csv << item.attributes.values_at(*column_name)
end
end
end
And on the profile show page to download the expected csv game schedule
<%= link_to "Download my Schedule", profile_path(format: 'csv')%>
I believe this might be my issue lies, but that doesn't quite explain what I get in my csv which is just a game object
file-
Here is my routes.rb
resources :games
match 'friendships/:friend_id' => 'user_friendships#new', :as => :new_friendship
match 'dashboard' => 'dashboard#show', :as => :dashboard
root to: "profiles#index"
get '/players', to: 'profiles#index', as:'players'
get '/players', to: 'profiles#index', as:'users'
get '/:id', to: "profiles#show", as: 'profile'
The file should be formatted with the column names (location, opponent, time, etc) as the header line and the corresponding lines with their respective values for each instance associated to a user.
I think the to_csv method inside game should be re-declared as -
passed in the games array which needed to be converted.
the param passed to values_at is column_names not column_name.
def self.to_csv(games)
CSV.generate do |csv|
csv << column_names
games.each do |item|
csv << item.attributes.values_at(*column_names)
end
end
end
and in the controller, the code should be:
def show
#user = User.find_by_profile_name(params[:id])
if #user
#listings = #user.listings
#games = #user.games
respond_to do |format|
format.html
format.csv {send_data Game.to_csv(#games)}
end
return
render action: :show
else
render file: "public/404", status: 404, formats: [:html]
end
end
otherwise, you will output all the games no matter which user you are using.
Although cenjongh's answer isn't wrong, let me elaborate on it.
The syntax Game.to_csv(#games) goes against Ruby's/Rails' object oriented approach for me.
Since the CSV generation code in your case is totally model independent (you don't make any assumptions of column names, etc.) you could feed this method any array of models, i.e. Game.to_csv(#shampoos) which would still work but wouldn't read very well.
Since Rails scopes the all method according to the criteria attached to the ActiveRelation object, using it in your class method wouldn't result in an output of all the games.
Assuming you're using at least Rails 3.0 the line #games = #user.games would give you an ActiveRelation object, not an array, meaning you can call #games.to_csv (or to make it even clearer #user.games.to_csv) directly, which reads what it is, namely converting a list of games, that belong to a user into CSV.
Oh, and I guess this is just testing code, but the return shouldn't be there. And the render statement should go into the block of format.html.